Delphi 当跳出块时,有没有安全的方法来清理基于堆栈的代码?
我一直在研究PascalScript脚本引擎,在该引擎中,使用Goto命令跳出Case块会产生编译器错误,即使这是完全有效的(尽管很难看)对象Pascal代码 结果是编译器中的ProcessCase例程调用HasInvalidJumps,它扫描导致Case块之外的任何GoTo,如果发现一个,则给出编译器错误。如果我对该签出进行注释,它会编译得很好,但最终会在运行时崩溃。字节码的反汇编说明了原因。我已经用原始脚本代码对其进行了注释:Delphi 当跳出块时,有没有安全的方法来清理基于堆栈的代码?,delphi,goto,pascalscript,Delphi,Goto,Pascalscript,我一直在研究PascalScript脚本引擎,在该引擎中,使用Goto命令跳出Case块会产生编译器错误,即使这是完全有效的(尽管很难看)对象Pascal代码 结果是编译器中的ProcessCase例程调用HasInvalidJumps,它扫描导致Case块之外的任何GoTo,如果发现一个,则给出编译器错误。如果我对该签出进行注释,它会编译得很好,但最终会在运行时崩溃。字节码的反汇编说明了原因。我已经用原始脚本代码对其进行了注释: [TYPES] <SNIPPED> [VARS] V
[TYPES]
<SNIPPED>
[VARS]
Var [0]: 27 Class TFORM
Var [1]: 28 Class TAPPLICATION
Var [2]: 11 S32 //i: integer
[PROCS]
Proc [0] Export: !MAIN -1
{begin}
[0] ASSIGN GlobalVar[2], [1]
{ i := 1;}
[15] PUSHTYPE 11(S32) // 1
[20] ASSIGN Base[1], GlobalVar[2]
{ case i of}
[31] PUSHTYPE 25(U8) // 2
{ 0:}
[36] COMPARE into Base[2]: [0] = Base[1]
[57] COND_NOT_GOTO currpos + 5 Base[2] [72]
{ end;}
[67] GOTO currpos + 41 [113]
{ 1:}
[72] COMPARE into Base[2]: [1] = Base[1]
[93] COND_NOT_GOTO currpos + 10 Base[2] [113]
{ goto L1;}
[103] GOTO currpos + 8 [116]
{ end;}
[108] GOTO currpos + 0 [113]
{ end; //<-- case}
[113] POP // 1
[114] POP // 0
{ Exit;}
[115] RET
{L1:
Writeln('Label L1');}
[116] PUSHTYPE 17(WideString) // 1
[121] ASSIGN Base[1], ['????????']
[144] CALL 1
{end.}
[149] POP // 0
[150] RET
Proc [1]: External Decl: \00\00 WRITELN
[类型]
[变量]
变量[0]:27类TFORM
变量[1]:28类
Var[2]:11s32//i:integer
[程序]
过程[0]导出:!MAIN-1
{begin}
[0]分配GlobalVar[2],[1]
{i:=1;}
[15] 推式11(S32)//1
[20] 分配基数[1],全局变量[2]
{案例一}
[31]推式25(U8)//2
{ 0:}
[36]比较到基[2]:[0]=基[1]
[57]COND_NOT_GOTO currpos+5 Base[2][72]
{end;}
[67]转到当前位置+41[113]
{ 1:}
[72]比较基数[2]:[1]=基数[1]
[93]COND_NOT_GOTO currpos+10 Base[2][113]
{转到L1;}
[103]转到当前位置+8[116]
{end;}
[108]转到当前位置+0[113]
{end;//简单的解决方案是:
生成GOTO for GOTO语句时,在GOTO前面加上与RET之前相同的清理代码。简单的解决方案是:
生成GOTO for GOTO语句时,在GOTO前面加上RET之前的相同清除代码。IIRC经典PASCAL中的GOTO规则是:
- 只允许跳出块(在树的“同一”分支上从较高嵌套级别跳出到较低嵌套级别)
- 从当地的程序到他们的父母
后者是afaik,从未得到Borland衍生Pascal的支持,但前者仍然成立
因此,您需要像Martin所说的那样生成退出代码,但它可能适用于多个块级别,因此您不能为每个goto生成一个can代码,但必须生成代码(以退出所需块的精确数量)
典型的测试模式是使用goto退出多个嵌套的ifs(可能在一个循环中),因为这是一个经典的微优化,至少在D7之前更快
请记住,if求值及其分支的begin..end块可能生成了需要清理的temp
----------后来添加
我认为codegenerator需要一种方法来遍历goto和它的端点之间的范围,为沿途的块生成相关的退出代码。这种方法修复程序适用于一般情况,而不仅仅是这个示例。
因为您只能跳出范围,而不能跳入范围,所以这可能没那么难
IOW生成等价于(对于假设的双格块)
Lgoto1gluecode:
//出口代码第一块
流行音乐
波比
//出口代码第一块
弹出一个
流行音乐B
转到真实目的地
可以进行其他分析。例如,如果只有一个作用域,并且它已经有一个清理出口标签,则可以直接跳转。如果您确定上述pop只是丢弃的值(而不是保存的寄存器),则可以使用add$16,%esp(4*4字节值)立即执行等等。IIRC经典帕斯卡中的goto规则是:
- 只允许跳出块(在树的“同一”分支上从较高嵌套级别跳出到较低嵌套级别)
- 从当地的程序到他们的父母
后者是afaik,从未得到Borland衍生Pascal的支持,但前者仍然成立
因此,您需要像Martin所说的那样生成退出代码,但它可能适用于多个块级别,因此您不能为每个goto生成一个can代码,但必须生成代码(以退出所需块的精确数量)
典型的测试模式是使用goto退出多个嵌套的ifs(可能在一个循环中),因为这是一个经典的微优化,至少在D7之前更快
请记住,if求值及其分支的begin..end块可能生成了需要清理的temp
----------后来添加
我认为codegenerator需要一种方法来遍历goto和它的端点之间的范围,为沿途的块生成相关的退出代码。这种方法修复程序适用于一般情况,而不仅仅是这个示例。
因为您只能跳出范围,而不能跳入范围,所以这可能没那么难
IOW生成等价于(对于假设的双格块)
Lgoto1gluecode:
//出口代码第一块
流行音乐
波比
//出口代码第一块
弹出一个
流行音乐B
转到真实目的地
可以进行其他分析。例如,如果只有一个作用域,并且它已经有一个清理出口标签,则可以直接跳转。如果您确定上述pop只是丢弃的值(而不是保存的寄存器),则可以使用add$16,%esp(4*4字节值)立即执行等等。在我看来,向前跳多远的计算似乎是个问题。我必须花一些时间研究解析器的实现,以进一步提供帮助,但我的猜测是,在使用goto时必须执行额外的处理,堆栈上有值,goto将放在这些值之后将从堆栈中删除。当然,要确定这一点,您需要将正在分析的当前位置(goto)和前向分析保存到目标位置,以观察堆栈更改,如果是,则保存到ad