为什么这段代码无法验证?

Why is this code failing to verify?

我有一个可以正确构建和运行的编译器,但 PEVerify 在某个时候称它无法验证。仔细查看错误、相应的源代码和问题点的 ILDasm 输出后,我找不到问题,以至于我怀疑 PEVerify 中的错误,除了 .NET 和 Mono 版本在同一个地方报同样的错误。

The problematic method内容如下:

internal static bool InAsyncMethod(Expression value)
{
    INodeWithBody ancestor = value.GetAncestor<BlockExpression>() ?? (INodeWithBody) value.GetAncestor<Method>();
    return ContextAnnotations.IsAsync(ancestor);
}

报错为:

[IL]: Error: [D:\SDL-1.3.0-4423\boo\build\Boo.Lang.Compiler.dll : Boo.Lang.Compiler.TypeSystem.AsyncHelper::InAsyncMethod][offset 0x00000011][found ref 'Boo.Lang.Compiler.Ast.Node'][expected ref Boo.Lang.Compiler.Ast.INodeWithBody'] Unexpected type on the stack.

Offsest 0x11 对应于 ?? 表达式的后半部分。来自 ILDasm:

.method assembly hidebysig static bool  InAsyncMethod(class Boo.Lang.Compiler.Ast.Expression 'value') cil managed
{
  // Code size       29 (0x1d)
  .maxstack  2
  .locals init ([0] class Boo.Lang.Compiler.Ast.INodeWithBody ancestor,
           [1] bool CS[=11=]00)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  callvirt   instance !!0 Boo.Lang.Compiler.Ast.Node::GetAncestor<class Boo.Lang.Compiler.Ast.BlockExpression>()
  IL_0007:  dup
  IL_0008:  brtrue.s   IL_0011
  IL_000a:  pop
  IL_000b:  ldarg.0
  IL_000c:  callvirt   instance !!0 Boo.Lang.Compiler.Ast.Node::GetAncestor<class Boo.Lang.Compiler.Ast.Method>()
  IL_0011:  stloc.0
  IL_0012:  ldloc.0
  IL_0013:  call       bool Boo.Lang.Compiler.Steps.ContextAnnotations::IsAsync(class Boo.Lang.Compiler.Ast.INodeWithBody)
  IL_0018:  stloc.1
  IL_0019:  br.s       IL_001b
  IL_001b:  ldloc.1
  IL_001c:  ret
} // end of method AsyncHelper::InAsyncMethod

Boo.Lang.Compiler.Ast.Node class 是所有 AST 节点的基础 class。 BlockExpressionMethod 分别是 lambda 和方法的节点 classes,它们都实现了 INodeWithBody 接口。一切看起来都是正确的,无论是在 C# 中(如果存在类型问题就不会构建)和 IL 中(它在 000c 处表示 GetAncestor<Method> 调用的 return 类型是!!0,方法调用的第一个类型参数。)

是什么导致 PEVerify 认为它正在处理一个 Node 类型的值,而它显然具有 Method 类型的值?有什么办法可以解决吗?

目前尚不清楚它是否在 0x11 上失败,因为 ?? 的第一部分或右侧,因为 0x08 正在分支到那里。

我倾向于认为 GetAncestor<> returns Node 并且 ?? 的左侧缺少显式转换。

What is causing PEVerify to think it's dealing with a value of type Node here when it clearly has a value of type Method?

正如 Stephane Delcroix 所指出的,有两条代码路径到达 IL_0011,因为它是分支的目标。它显然不一定具有 Method.

类型的值

GetAncestor<TAncestor>returnsTAncestor。您得到了 GetAncestor<BlockExpression> 的结果,或 GetAncestor<Method> 的结果,所以 BlockExpressionMethod。都实现了INodeWithBody,所以逻辑上,代码还是可以的。

不幸的是,“BlockExpressionMethod”对于验证来说太多了。这被简化为 Node(公共基础),它 而不是 实现 INodeWithBody。参见 ECMA-335 §III.1.8.1.3:

The merged type, U, shall be computed as follows (recall that S := T is the compatibility function defined in §III.1.8.1.2.2):

  1. if S := T then U=S

  2. Otherwise, if T := S then U=T

  3. Otherwise, if S and T are both object types, then let V be the closest common supertype of S and T then U=V.

  4. Otherwise, the merge shall fail.

如果你检查 C# 编译器做了什么,你会发现它在 dup.这使得一切正常,因为 INodeWithBodyMethod 的常见类型是 INodeWithBody.