在语言解释器中防止StackOverflow

 左手抽烟吹寂寞 发布于 2023-02-13 22:42

F#作为一种语言非常适合编写语言解释器或编译器,但是,有一件事情让我们不知所措:StackOverflowException.

众所周知,无法捕获SO异常,无法从中恢复.防止这种异常的一种显而易见的技术是在你进行时计算堆栈的深度.开销,是的,但是可行,也许在每个功能中都没有必要.

使用F#,这种技术虽然没有带来太多好处.我们在解释器的动态生成表达式中大量使用尾调用优化技术.我们在SO异常中遇到的问题是:

我们如何通知用户,而不是崩溃整个当前的AppDomain?

如果我们计算堆栈深度,我们如何知道函数是TCO还是内联,所以我们不必计算?

如果我们采用另一种方法(比如以给定的深度间隔检查堆栈本身),是否有任何(已知的)方法可以在不严重影响性能的情况下执行此操作?

只是增加堆栈大小不会有足够的帮助,我们希望给用户一个可记录的错误,最好是由调用应用程序捕获.为此,我们需要能够手动抛出异常,这使它可以捕获.但我们如何确定合适的时机?

更新:
Hans Passant在这里正确地提出了可预测性.但是,使用此DSL的程序员期望(某些)调用获得TCO,因此他们不希望有强大的堆栈限制.他们知道自己在做什么.尽管如此,他们的程序仍然需要能够优雅地消亡,至少在任何调用应用程序(即使用我们的库的C#程序)都不会受到损害的情况下.

1 个回答
  • 我不熟悉F#,但我确实在C#中编写了一个ECMAScript-262 v.5解释器,所以我可以解决你的一些问题.如您所知,自v2.0起,无法在.NET应用程序中捕获StackOverFlowException.虽然有一个相当可靠的解决方法,但速度很快.

    如果在堆栈上声明变量,例如int,则该变量的地址代表堆栈的顶部,并让您知道剩余多少空间.因此,如果在堆栈基本为空时在启动时记录该变量,则每次输入新的执行上下文时都可以引用该变量.

    以下是我的口译员解决此问题的一些例外情况.

    C#:

    这些是在主Interpreter类中声明的静态变量.

    private static int TopOfStack;
    private const int STACK_SIZE = 1000000;
    

    这是主Interpreter类的静态构造函数.

    static Interpreter() {
        InitializeGlobalEnvironment();
    
        //---------------------------------------------------
        // Get the address of a new variable allocated on the stack 
        // to represent the amount of memory available. Record 
        // the address.
        //---------------------------------------------------
        int stackVariable;
        TopOfStack = (int)&stackVariable;
    }
    

    在解释ECMAScript函数之前调用此代码.如果新堆栈分配变量的地址小于short.Max,则抛出可捕获的异常.您需要为调用堆栈留出一些空间来放松.

    internal static ExecutionContext EnterFunctionContext(IValue thisArg, LIST args, FUNCTION function) {
    ...
        LexicalEnvironment localEnv = ECMA.NewDeclarativeEnvironment(function.Scope);
    
        ExecutionContext context = new ExecutionContext() {
            Strict = function.IsStrict,
            VariableEnvironment = localEnv,
            LexicalEnvironment = localEnv
        };
    
        int remainingStackSpace;
    
        if (STACK_SIZE - (TopOfStack - (int)&remainingStackSpace) < short.MaxValue) 
                throw new ECMARuntimeException("stack overflow", RuntimeErrorType.RangeError);
    
    
        CallStack.Push(context);
        LexicalEnvironment env = CurrentContext.VariableEnvironment;
    ...
    }
    

    解释以下代码时,将在迭代1200周围抛出异常.

    更新:在发布版本中,它大约是4100次迭代.

    ECMAScript中:

    RecursiveCall(0);
    
    function RecursiveCall(counter){
        return RecursiveCall(++counter);
    }
    

    输出: RangeError: stack overflow

    您可以使用Thread(ParameterizedThreadStart, Int32)构造函数增加Thread中的堆栈大小.我只是觉得不需要.

    祝你的项目好运.我希望这有帮助.

    2023-02-13 22:58 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有