Wednesday, February 17, 2010

F# under the covers XII -- Reflector 6.0 decompilation to C#

A new Reflector release (6.0.0.918), and time to see whether it can approximate in C# some compiled F# code that the 5.1.6.0 version balked at.

The generated IEquatable.Compare method that couldn't be handled before now succeeds -- the section that had problems now resolves to

public sealed override bool Equals(Context obj)
{
if (this <= null)
{
return (obj <= null);
}
if (obj <= null)
{
return false;
}
view raw gistfile1.cs hosted with ❤ by GitHub

and it's not surprising that comparing not-greater-than-null might be an unexpected piece of IL.

However, when there is a problem with decompilation, the new version no longer provides the detail of which instruction it didn't like. So I don't know why this

let (|CSharpConstStub|_|) (fn:Method) =
if fn.Name.ToString() @ "get_" then None
else match MapToTypes fn.Body.Statements with
| [NodeType.Block; NodeType.Block] ->
let block1 = fn.Body.Statements.[0] :?> Block
let block2 = fn.Body.Statements.[1] :?> Block
match (MapToTypes block1.Statements, MapToTypes block2.Statements) with
| ( [NodeType.Nop; NodeType.AssignmentStatement; NodeType.Branch], [NodeType.Return]) ->
let ass = block1.Statements.[1] :?> AssignmentStatement
let branch = block1.Statements.[2] :?> Branch
if branch.Target <> block2 || null <> branch.Condition then None
elif ass.Source.NodeType <> NodeType.MemberBinding && ass.Source.NodeType <> NodeType.Literal then None
else match (block2.Statements.[0], ass.Target) with
| ReturnTemp -> Some()
| _ -> None
| _ -> None
| _ -> None
view raw gistfile1.fs hosted with ❤ by GitHub

raises an error report when trying to decompile to C# (or MC++ or VB); or why this

let (|CSharpGetterInterior|_|) (state:(Statement * Statement * Member)) =
let (a, b, fn) = state
let block = a :?> Block
let block2 = b :?> Block
match MapToTypes block.Statements with
| [NodeType.Nop; NodeType.AssignmentStatement; NodeType.Branch] ->
let ass = block.Statements.[1] :?> AssignmentStatement
let branch = block.Statements.[2] :?> Branch
if (ass.Source.NodeType <> NodeType.MemberBinding ||
null <> branch.Condition ||
branch.Target <> block2) then None
elif block2.Statements.Count <> 1 then None
else match (block2.Statements.[0], ass.Target) with
| ReturnTemp ->
let n = (ass.Source :?> MemberBinding).BoundMember.Name.ToString()
if fn.Name.ToString().ToUpperInvariant() <> ("get_" + n).ToUpperInvariant() then None
else Some ()
| _ -> None
| _ -> None
view raw gistfile1.fs hosted with ❤ by GitHub

throws a NullPointerException while trying to report an error when asked to decompile it as C# (I was able to get an error report sent when asking for VB instead).

Dropping back to 5.1.6.0, both show the usual "invalid branching condition", and point to

if branch.Target <> block2 || null <> branch.Condition then None
view raw gistfile1.fs hosted with ❤ by GitHub

(the former condition) and

if (ass.Source.NodeType <> NodeType.MemberBinding ||
view raw gistfile1.fs hosted with ❤ by GitHub

respectively, on the path where we are short-circuiting, as in

L_020d: call bool [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/HashCompare::GenericEqualityIntrinsic<class [Microsoft.Cci]Microsoft.FxCop.Sdk.Block>(!!0, !!0)
L_0212: ldc.i4.0
L_0213: ceq
L_0215: brfalse.s L_0219
L_0217: br.s L_021b
L_0219: br.s L_021f
L_021b: ldc.i4.1
L_021c: nop
L_021d: br.s L_0238
view raw gistfile1.cs hosted with ❤ by GitHub

where the last line here is the one that the decompilation faults on.

No comments :