我想阻止或处理在 Xsl Editor
中调用 XslCompiledTransform.Transform
方法时遇到的 StackOverflowException
。写作。 问题似乎是用户可以编写无限递归的 Xsl 脚本
,并且它在调用 Transform
方法时就会崩溃。 (也就是说,问题不仅仅是典型的编程错误,这通常是导致此类异常的原因。)
有没有办法检测和/或限制允许的递归次数? 或者还有其他想法可以防止这段代码在我身上爆炸吗?
I would like to either prevent or handle a StackOverflowException
that I am getting from a call to the XslCompiledTransform.Transform
method within an Xsl Editor
I am writing. The problem seems to be that the user can write an Xsl script
that is infinitely recursive, and it just blows up on the call to the Transform
method. (That is, the problem is not just the typical programmatic error, which is usually the cause of such an exception.)
Is there a way to detect and/or limit how many recursions are allowed? Or any other ideas to keep this code from just blowing up on me?
发布评论
评论(10)
来自微软:
我假设异常发生在内部 .NET 方法中,而不是在您的代码中。
你可以做几件事。
您可以使用 Process 类加载程序集,该程序集会将转换应用到单独的进程中,并在进程终止时提醒用户失败,而无需终止您的主应用程序。
编辑:我刚刚测试过,这是如何做到的:
MainProcess:
ApplyTransform Process:
From Microsoft:
I'm assuming the exception is happening within an internal .NET method, and not in your code.
You can do a couple things.
You can use the Process class to load the assembly that will apply the transform into a separate process, and alert the user of the failure if it dies, without killing your main app.
EDIT: I just tested, here is how to do it:
MainProcess:
ApplyTransform Process:
堆栈溢出是由于堆栈上的数据超出一定限制(以字节为单位)而发生的。 有关此检测工作原理的详细信息,请参阅此处。
正如我在链接中提到的,从静态代码分析中检测堆栈溢出需要解决不可判定的停止问题。 既然我们已经确定没有灵丹妙药,我可以向您展示一些我认为有助于解决问题的技巧。
我认为这个问题可以用不同的方式解释,因为我有点无聊:-),我会将它分解为不同的变体。
在测试环境中检测堆栈溢出
基本上,这里的问题是您有一个(有限的)测试环境,并且想要在(扩展的)生产环境中检测堆栈溢出。
我没有检测 SO 本身,而是通过利用堆栈深度可以设置的事实来解决这个问题。 调试器将为您提供所需的所有信息。 大多数语言允许您指定堆栈大小或最大递归深度。
基本上,我尝试通过使堆栈深度尽可能小来强制实现 SO。 如果它没有溢出,我总是可以将其增大(=在本例中:更安全)以适应生产环境。 当出现堆栈溢出时,您可以手动决定它是否是“有效”的。
为此,请将堆栈大小(在我们的例子中:一个小值)传递给线程参数,然后看看会发生什么。 .NET 中的默认堆栈大小为 1 MB,我们将使用更小的值:
注意:我们也将使用下面的代码。
一旦溢出,您可以设置它到一个更大的值,直到你得到一个有意义的SO。
在执行此操作之前创建异常
StackOverflowException
是不可捕获的。 这意味着当事情发生时你无能为力。 因此,如果您认为代码中肯定会出现问题,那么在某些情况下您可以自己制定例外情况。 为此,您唯一需要的是当前堆栈深度; 不需要计数器,您可以使用 .NET 中的实际值:请注意,如果您正在处理使用回调机制的第三方组件,则此方法也适用。 唯一需要的是您可以拦截堆栈跟踪中的一些调用。
在单独的线程中进行检测
您明确建议了这一点,所以这里是这个。
您可以尝试在单独的线程中检测SO..但这可能不会对您有任何好处。 堆栈溢出可能很快发生,甚至在上下文切换之前也是如此。 这意味着这种机制根本不可靠...我不建议实际使用它。 不过构建起来很有趣,所以这里是代码:-)
在调试器中运行它并享受所发生的事情。
利用堆栈溢出的特征
您的问题的另一种解释是:“可能导致堆栈溢出异常的代码片段在哪里?”。 显然,答案是:所有代码都带有递归。 对于每段代码,您都可以进行一些手动分析。
也可以使用静态代码分析来确定这一点。 为此,您需要做的是反编译所有方法并确定它们是否包含无限递归。 下面是一些为您执行此操作的代码:
现在,方法循环包含递归这一事实绝不保证会发生堆栈溢出 - 它只是堆栈溢出异常最有可能的先决条件。 简而言之,这意味着此代码将确定可能发生堆栈溢出的代码片段,这将大大缩小大多数代码的范围。
其他方法
您还可以尝试一些我未在此处描述的其他方法。
Stack overflows happen because the data on the stack exceeds a certain limit (in bytes). The details of how this detection works can be found here.
As I mentioned in the link, detecting a stack overflow from static code analysis would require solving the halting problem which is undecidable. Now that we've established that there is no silver bullet, I can show you a few tricks that I think helps track down the problem.
I think this question can be interpreted in different ways, and since I'm a bit bored :-), I'll break it down into different variations.
Detecting a stack overflow in a test environment
Basically the problem here is that you have a (limited) test environment and want to detect a stack overflow in an (expanded) production environment.
Instead of detecting the SO itself, I solve this by exploiting the fact that the stack depth can be set. The debugger will give you all the information you need. Most languages allow you to specify the stack size or the max recursion depth.
Basically I try to force a SO by making the stack depth as small as possible. If it doesn't overflow, I can always make it bigger (=in this case: safer) for the production environment. The moment you get a stack overflow, you can manually decide if it's a 'valid' one or not.
To do this, pass the stack size (in our case: a small value) to a Thread parameter, and see what happens. The default stack size in .NET is 1 MB, we're going to use a way smaller value:
Note: we're going to use this code below as well.
Once it overflows, you can set it to a bigger value until you get a SO that makes sense.
Creating exceptions before you SO
The
StackOverflowException
is not catchable. This means there's not much you can do when it has happened. So, if you believe something is bound to go wrong in your code, you can make your own exception in some cases. The only thing you need for this is the current stack depth; there's no need for a counter, you can use the real values from .NET:Note that this approach also works if you are dealing with third-party components that use a callback mechanism. The only thing required is that you can intercept some calls in the stack trace.
Detection in a separate thread
You explicitly suggested this, so here goes this one.
You can try detecting a SO in a separate thread.. but it probably won't do you any good. A stack overflow can happen fast, even before you get a context switch. This means that this mechanism isn't reliable at all... I wouldn't recommend actually using it. It was fun to build though, so here's the code :-)
Run this in the debugger and have fun of what happens.
Using the characteristics of a stack overflow
Another interpretation of your question is: "Where are the pieces of code that could potentially cause a stack overflow exception?". Obviously the answer of this is: all code with recursion. For each piece of code, you can then do some manual analysis.
It's also possible to determine this using static code analysis. What you need to do for that is to decompile all methods and figure out if they contain an infinite recursion. Here's some code that does that for you:
Now, the fact that a method cycle contains recursion, is by no means a guarantee that a stack overflow will happen - it's just the most likely precondition for your stack overflow exception. In short, this means that this code will determine the pieces of code where a stack overflow can occur, which should narrow down most code considerably.
Yet other approaches
There are some other approaches you can try that I haven't described here.
我建议围绕 XmlWriter 对象创建一个包装器,这样它就会计算对 WriteStartElement/WriteEndElement 的调用数量,如果将标签数量限制为某个数字(fe 100),您将能够抛出不同的异常,例如 -无效操作。
这应该可以解决大多数情况下的问题
I would suggest creating a wrapper around XmlWriter object, so it would count amount of calls to WriteStartElement/WriteEndElement, and if you limit amount of tags to some number (f.e. 100), you would be able to throw a different exception, for example - InvalidOperation.
That should solve the problem in the majority of the cases
这个答案是给@WilliamJockusch 的。
听起来您很想听到一些调试技术来捕获此 StackOverflow,所以我想我会分享一些供您尝试。
1. 内存转储。
专业:内存转储是解决堆栈溢出原因的可靠方法。 AC# MVP 和 我一起对一个 SO 进行故障排除,他继续在博客上介绍它 此处。
此方法是追踪问题的最快方法。
此方法不需要您按照日志中看到的步骤来重现问题。
缺点:内存转储非常大,您必须附加 AdPlus/procdump 进程。
2.面向方面的编程。
专业人士:这可能是您实现从任何方法检查调用堆栈大小的代码的最简单方法,而无需在应用程序的每个方法中编写代码。 有很多 AOP 框架< /a> 允许您在调用之前和之后进行拦截。
会告诉你导致堆栈溢出的方法。
允许您在应用程序中所有方法的入口和出口处检查
StackTrace().FrameCount
。缺点:它会对性能产生影响 - 每个方法的钩子都嵌入到 IL 中,您无法真正“停用”它。
这在某种程度上取决于您的开发环境工具集。
3. 记录用户活动。
一周前,我试图找出几个难以重现的问题。 我发布了此 QA 用户活动日志记录、遥测(和变量)在全局异常处理程序中) 。 我得出的结论是一个非常简单的用户操作记录器,用于了解当发生任何未处理的异常时如何在调试器中重现问题。
专业版:您可以随意打开或关闭它(即订阅事件)。
跟踪用户操作不需要拦截每个方法。
您可以计算订阅的事件方法的数量,比 AOP 简单得多。
日志文件相对较小,重点关注重现问题所需执行的操作。
它可以帮助您了解用户如何使用您的应用程序。
缺点:不适合 Windows 服务,而且我确信对于 Web 应用程序有更好的工具。
不一定告诉您导致堆栈溢出的方法。
需要您手动逐步查看日志以重现问题,而不是内存转储,您可以在内存转储中立即获取并调试它。
也许您可以尝试我上面提到的所有技术以及 @atlaste 发布的一些技术,并告诉我们您发现哪一个是在 PROD 环境等中运行最简单/最快/最脏/最可接受的技术。
不管怎样,祝你好运,找到这个SO。
This answer is for @WilliamJockusch.
It sounds like you're keen to hear some debugging techniques to catch this StackOverflow so I thought I would share a couple for you to try.
1. Memory Dumps.
Pro's: Memory Dumps are a sure fire way to work out the cause of a Stack Overflow. A C# MVP & I worked together troubleshooting a SO and he went on to blog about it here.
This method is the fastest way to track down the problem.
This method wont require you to reproduce problems by following steps seen in logs.
Con's: Memory Dumps are very large and you have to attach AdPlus/procdump the process.
2. Aspect Orientated Programming.
Pro's: This is probably the easiest way for you to implement code that checks the size of the call stack from any method without writing code in every method of your application. There are a bunch of AOP Frameworks that allow you to Intercept before and after calls.
Will tell you the methods that are causing the Stack Overflow.
Allows you to check the
StackTrace().FrameCount
at the entry and exit of all methods in your application.Con's: It will have a performance impact - the hooks are embedded into the IL for every method and you cant really "de-activate" it out.
It somewhat depends on your development environment tool set.
3. Logging User Activity.
A week ago I was trying to hunt down several hard to reproduce problems. I posted this QA User Activity Logging, Telemetry (and Variables in Global Exception Handlers) . The conclusion I came to was a really simple user-actions-logger to see how to reproduce problems in a debugger when any unhandled exception occurs.
Pro's: You can turn it on or off at will (ie subscribing to events).
Tracking the user actions doesn't require intercepting every method.
You can count the number of events methods are subscribed too far more simply than with AOP.
The log files are relatively small and focus on what actions you need to perform to reproduce the problem.
It can help you to understand how users are using your application.
Con's: Isn't suited to a Windows Service and I'm sure there are better tools like this for web apps.
Doesn't necessarily tell you the methods that cause the Stack Overflow.
Requires you to step through logs manually reproducing problems rather than a Memory Dump where you can get it and debug it straight away.
Maybe you might try all techniques I mention above and some that @atlaste posted and tell us which one's you found were the easiest/quickest/dirtiest/most acceptable to run in a PROD environment/etc.
Anyway good luck tracking down this SO.
如果您的应用程序依赖于 3d 方代码(在 Xsl 脚本中),那么您必须首先决定是否要防御其中的错误。
如果你真的想防御,那么我认为你应该在单独的应用程序域中执行容易出现外部错误的逻辑。
捕获 StackOverflowException 并不好。
另请检查这个问题。
If you application depends on 3d-party code (in Xsl-scripts) then you have to decide first do you want to defend from bugs in them or not.
If you really want to defend then I think you should execute your logic which prone to external errors in separate AppDomains.
Catching StackOverflowException is not good.
Check also this question.
在 .NET 4.0 中,您可以将 System.Runtime.ExceptionServices 中的
HandleProcessCorruptedStateExceptions
属性添加到包含 try/catch 块的方法中。 这真的有效! 也许不推荐但有效。With .NET 4.0 You can add the
HandleProcessCorruptedStateExceptions
attribute from System.Runtime.ExceptionServices to the method containing the try/catch block. This really worked! Maybe not recommended but works.我今天有一个 stackoverflow,我读了你的一些帖子,并决定帮助垃圾收集器。
我曾经有一个近乎无限的循环,如下所示:
相反,让资源超出范围,如下所示:
它对我有用,希望它对某人有帮助。
I had a stackoverflow today and i read some of your posts and decided to help out the Garbage Collecter.
I used to have a near infinite loop like this:
Instead let the resource run out of scope like this:
It worked for me, hope it helps someone.
@WilliamJockusch,如果我正确理解你的担忧,那么(从数学的角度来看)不可能总是识别无限递归,因为这意味着解决停止问题。 要解决这个问题,您需要一个超级递归算法(例如试错谓词)或可以超级计算(以下部分 - 可作为预览 - 这本书)。
从实际的角度来看,您必须知道:
请记住,对于当前的机器,由于多任务处理,这些数据非常可变,而且我还没有听说过可以执行该任务的软件。
如果有不清楚的地方请告诉我。
@WilliamJockusch, if I understood correctly your concern, it's not possible (from a mathematical point of view) to always identify an infinite recursion as it would mean to solve the Halting problem. To solve it you'd need a Super-recursive algorithm (like Trial-and-error predicates for example) or a machine that can hypercompute (an example is explained in the following section - available as preview - of this book).
From a practical point of view, you'd have to know:
Keep in mind that, with the current machines, this data is extremely mutable due to multitasking and I haven't heard of a software that does the task.
Let me know if something is unclear.
从表面上看,除了启动另一个进程之外,似乎没有任何方法可以处理 StackOverflowException。 在其他人询问之前,我尝试使用
AppDomain
,但这不起作用:但是,如果您最终使用单独进程解决方案,我建议使用
Process.Exited
code> 和Process.StandardOutput
并自行处理错误,以便为用户提供更好的体验。By the looks of it, apart from starting another process, there doesn't seem to be any way of handling a
StackOverflowException
. Before anyone else asks, I tried usingAppDomain
, but that didn't work:If you do end up using the separate-process solution, however, I would recommend using
Process.Exited
andProcess.StandardOutput
and handle the errors yourself, to give your users a better experience.您可以每隔几次调用
Environment.StackTrace
读取此属性,如果堆栈跟踪超出您预设的特定阈值,您可以返回该函数。您还应该尝试用循环替换一些递归函数。
You can read up this property every few calls,
Environment.StackTrace
, and if the stacktrace exceded a specific threshold that you preset, you can return the function.You should also try to replace some recursive functions with loops.