我想要么防止或处理 StackOverflowException 我得到从一个电话的 XslCompiledTransform.Transform 方法内的一个 Xsl Editor 我写作。该问题似乎是,用户可以编写一个 Xsl script 也就是无限的递归,它只是吹的呼吁 Transform 法。(是的,问题不仅仅是典型的方案错误,这通常是因为这样的一个例外。)

有没有方法检测和/或限制多少递归是允许的?或任何其他的想法保持这种代码只是吹在我身上?

有帮助吗?

解决方案

从Microsoft:

开始的。净框架 2.0版本,StackOverflowException 对象无法抓获一个试试-抓住 方框和相应的过程 终止的默认。因此, 建议用户编写自己的代码 侦查和防止一堆 溢出。例如,如果你的 应用取决于递归,使用 一个计数器或一个国家的条件 终止递归的循环。

我假设的例外是发生在内部的。净的方法,并不在你的代码。

你可以做一对夫妇的事情。

  • 编写代码,用于检查xsl为无限递归并向用户通知之前申请转换(唉).
  • 负载的请参见码成一个单独的进程(哈克,但不工作)。

你可以使用进程类载的大会,适用《转变成一个单独的进程,并提醒用户的失败,如果它死了,没有杀害你的主要应用程序。

编辑:我只是测试,这里是如何做到这一点:

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

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

其他提示

  

注意 @WilliamJockusch的赏金问题与原始问题不同。

     

这个答案是关于第三方库的一般情况下的StackOverflow以及您可以/不能使用它们的内容。如果您正在查看XslTransform的特殊情况,请参阅接受的答案。


堆栈溢出的发生是因为堆栈上的数据超过了某个限制(以字节为单位)。有关此检测工作原理的详细信息,请参见此处

  

我想知道是否有一种跟踪StackOverflowExceptions的通用方法。换句话说,假设我的代码中某处有无限递归,但我不知道在哪里。我希望通过一些方法来跟踪它,这比在整个地方逐步执行代码更容易,直到我看到它发生。我不在乎它是多么的黑暗。

正如我在链接中提到的,检测静态代码分析中的堆栈溢出需要解决不可判定的暂停问题。现在我们已经确定没有银弹,我可以向您展示一些我认为有助于追踪问题的技巧。

我认为这个问题可以用不同的方式解释,因为我有点无聊:-),我会把它分解成不同的变体。

检测测试环境中的堆栈溢出

这里的问题基本上是你有一个(有限的)测试环境,并希望在(扩展的)生产环境中检测堆栈溢出。

我没有检测SO本身,而是通过利用可以设置堆栈深度的事实来解决这个问题。调试器将为您提供所需的所有信息。大多数语言允许您指定堆栈大小或最大递归深度。

基本上我尝试通过使堆栈深度尽可能小来强制SO。如果它没有溢出,我总是可以让生产环境更大(在这种情况下:更安全)。当您获得堆栈溢出时,您可以手动确定它是否是“有效”的。

为此,将堆栈大小(在我们的例子中:一个小值)传递给Thread参数,看看会发生什么。 .NET中的默认堆栈大小为1 MB,我们将使用更小的值:

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

注意:我们也将在下面使用此代码。

一旦溢出,你可以将它设置为更大的值,直到你得到一个有意义的SO。

在您之前创建例外

StackOverflowException 无法捕获。这意味着当它发生时你无能为力。因此,如果您认为代码中的某些内容肯定会出错,那么在某些情况下您可以自己做出异常。你唯一需要的是当前的堆栈深度;不需要计数器,你可以使用.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();
    }
}

请注意,如果您正在处理使用回调机制的第三方组件,则此方法也适用。唯一需要的是你可以拦截堆栈跟踪中的一些调用。

在单独的帖子中进行检测

你明确地建议了这一点,所以这就是这个。

您可以尝试在单独的线程中检测SO ..但它可能对您没有任何好处。即使在进行上下文切换之前,堆栈溢出也可能发生 fast 。这意味着这种机制根本不可靠...... 我不建议实际使用它。虽然构建很有趣,所以这里是代码: - )

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

在调试器中运行它,并了解会发生什么。

使用堆栈溢出的特征

您的问题的另一种解释是:“在哪里

我建议创建一个围绕XmlWriter对象的包装器,这样它会计算对WriteStartElement / WriteEndElement的调用量,如果你将标签数量限制为某个数字(fe 100),你就可以抛出一个不同的异常,例如 - InvalidOperation。

这应解决大多数情况下的问题

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

这个答案是给@WilliamJockusch的。

  

我想知道是否有一般追踪方式   StackOverflowExceptions。换句话说,假设我有无限的   在我的代码中的某处递归,但我不知道在哪里。我想要   通过某种方式跟踪它比通过代码更容易   到处都是,直到我看到它发生。我不在乎多么hackish   它是。例如,拥有一个我可以的模块会很棒   激活,甚至可能从另一个线程激活,轮询堆栈   深度和抱怨,如果它达到我认为“太高”的水平。对于   例如,我可能会设置“太高”至600帧,如果确定的话   堆栈太深,这一定是个问题。是这样的   可能。另一个例子是记录每个第1000个方法调用   在我的代码中调试输出。这可能会得到一些   过低的证据会很好,而且可能不会   把输出放得太厉害了。关键是它不能涉及   在发生溢出的地方写一张支票。因为整个   问题是我不知道那是哪里。最好是解决方案   不应该取决于我的开发环境是什么样的;即,   它不应该假设我通过特定的工具集使用C#(例如   VS)。

听起来你很想听到一些调试技术来捕捉这个StackOverflow所以我想我会分享一些让你尝试的。

1。记忆转储。

Pro的:内存转储是解决堆栈溢出原因的可靠方法。 C#MVP&我一起解决了SO问题,然后他继续写博客这里

此方法是追踪问题的最快方法。

此方法不需要您按照日志中的步骤重现问题。

Con's :内存转储非常大,您必须在此过程中附加AdPlus / procdump。

2。面向方面编程。

Pro :这可能是您实现从任何方法检查调用堆栈大小的代码的最简单方法,而无需在应用程序的每个方法中编写代码。有一堆 AOP Frameworks 允许你在通话前后拦截。

将告诉您导致Stack Overflow的方法。

允许您在应用程序中所有方法的入口和出口处检查 StackTrace()。FrameCount

Con's :它会对性能产生影响 - 每个方法都会将挂钩嵌入到IL中,并且您无法真正“取消激活”它出来了。

这在某种程度上取决于您的开发环境工具集。

3。记录用户活动。

一周前,我试图追捕几个难以重现的问题。我发布了此QA 用户活动记录,遥测(和变量)在全局异常处理程序中)。我得出的结论是一个非常简单的用户操作记录器,用于查看在发生任何未处理的异常时如何在调试器中重现问题。

Pro :您可以随意打开或关闭它(即订阅活动)。

跟踪用户操作不需要拦截每个方法。

您可以计算订阅方法的数量远比使用AOP 更简单。

日志文件相对较小,专注于您的操作

如果您的应用程序依赖于3d-party代码(在Xsl脚本中),那么您必须首先决定是否要防范它们中的错误。 如果你真的想要辩护那么我认为你应该执行你的逻辑,这个逻辑在单独的AppDomain中容易出现外部错误。 捕获StackOverflowException并不好。

另请查看此问题

今天我有一个stackoverflow,我读了你的一些帖子,并决定帮助垃圾收集器。

我曾经有过这样的无限循环:

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

相反,让资源超出范围,如下所示:

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
}

它对我有用,希望它对某人有所帮助。

使用.NET 4.0您可以将System.Runtime.ExceptionServices中的 HandleProcessCorruptedStateExceptions 属性添加到包含try / catch块的方法中。这确实有效!也许不推荐但是有效。

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,如果我理解正确的话你的关心,这是不可能的(从一个数学的角度来看), 总是 确定一个无限回递的,因为它将意味着解决了 停止问题.为了解决这个问题,你会需要 超递归算法 (喜欢 试验和错误谓 例如)或一台机器,可以 hypercompute (一个例子是解释的 以下部分 -可以作为预览的 这本书).

从实际的观点来看,你必须知道:

  • 多少叠的存储你有留给定的时间
  • 多少堆的记忆你的递归的方法将需要在给定的时间,用于具体的产出。

请记住,当前的机,这个数据是非常易变,由于多任务和我还没有听说过一个软件,它的任务。

如果让我知道的东西还不清楚。

从它的外观来看,除了启动另一个进程之外,似乎没有任何方法可以处理 StackOverflowException 。在其他人问之前,我尝试使用 AppDomain ,但这不起作用:

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

但是,如果最终使用单独进程解决方案,我建议使用 Process.Exited Process.StandardOutput 并自行处理错误,以便给出让您的用户获得更好的体验。

您可以每隔几次调用读取此属性, Environment.StackTrace ,如果stacktrace超出您预设的特定阈值,则可以返回该函数。

您还应该尝试用循环替换一些递归函数。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top