Выражение объединения, вызывающее проблему в операторах return в Fody

Рассмотрим следующий фрагмент кода:

private string GetFieldValueAsString(string nonAliasedEntityName = null, string nonAliasedName = null)
{
    return nonAliasedEntityName ?? nonAliasedName;   // simplified code of course!
}

Он скомпилирован в следующий код IL:

.method private hidebysig 
    instance string GetFieldValueAsString (
        [opt] string nonAliasedEntityName,
        [opt] string nonAliasedName
    ) cil managed 
{
    .param [1] = nullref
    .param [2] = nullref
    // Method begins at RVA 0x2cb6f
    // Code size 7 (0x7)
    .maxstack 8

    IL_0000: ldarg.1
    IL_0001: dup
    IL_0002: brtrue.s IL_0006

    IL_0004: pop
    IL_0005: ldarg.2

    IL_0006: ret
} // end of method MessageBuilder::GetFieldValueAsString

Если я применяю «исправить возврат» в Fody, я получаю следующий код IL:

.method private hidebysig 
    instance string GetFieldValueAsString (
        [opt] string nonAliasedEntityName,
        [opt] string nonAliasedName
    ) cil managed 
{
    .param [1] = nullref
    .param [2] = nullref
    // Method begins at RVA 0x2cb70
    // Code size 15 (0xf)
    .maxstack 3
    .locals (
        [0] string $returnVariable
    )

    IL_0000: ldarg.1
    IL_0001: dup
    IL_0002: dup
    IL_0003: stloc.0
    IL_0004: brtrue.s IL_000b

    IL_0006: pop
    IL_0007: ldarg.2
    IL_0008: stloc.0
    IL_0009: br.s IL_000b

    IL_000b: nop
    IL_000c: nop
    IL_000d: ldloc.0
    IL_000e: ret
} // end of method MessageBuilder::GetFieldValueAsString

Это дает мне следующую ошибку при декомпиляции в ILSpy и не запускается:

ICSharpCode.Decompiler.DecompilerException: Error decompiling System.String LinkDev.Notifications.Steps.MessageBuilder::GetFieldValueAsString(System.String,System.String)
 ---> System.Exception: Inconsistent stack size at IL_09
   at ICSharpCode.Decompiler.ILAst.ILAstBuilder.StackAnalysis(MethodDefinition methodDef)
   at ICSharpCode.Decompiler.ILAst.ILAstBuilder.Build(MethodDefinition methodDef, Boolean optimize, DecompilerContext context)
   at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(IEnumerable`1 parameters)
   at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(MethodDefinition methodDef, DecompilerContext context, IEnumerable`1 parameters)
   --- End of inner exception stack trace ---
   at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(MethodDefinition methodDef, DecompilerContext context, IEnumerable`1 parameters)
   at ICSharpCode.Decompiler.Ast.AstBuilder.CreateMethod(MethodDefinition methodDef)
   at ICSharpCode.Decompiler.Ast.AstBuilder.AddMethod(MethodDefinition method)
   at ICSharpCode.ILSpy.CSharpLanguage.DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options)
   at ICSharpCode.ILSpy.TextView.DecompilerTextView.DecompileNodes(DecompilationContext context, ITextOutput textOutput)
   at ICSharpCode.ILSpy.TextView.DecompilerTextView.<>c__DisplayClass31_0.<DecompileAsync>b__0()

Я проследил размер стека, и он, кажется, держится. Есть идеи, что может быть причиной этой проблемы?

Обновление 1:

Я добавил временное быстрое решение проблемы:

Instruction doubleDupInstruction = null;

for (var index = 0; index < instructions.Count; index++)
{
    var instruction = instructions[index];

    if (instruction.OpCode == OpCodes.Dup && instructions[index + 1].OpCode == OpCodes.Dup)
    {
        doubleDupInstruction = instructions[index + 1];
    }

    if (instruction.OpCode == OpCodes.Pop && doubleDupInstruction != null)
    {
        var extraPopInstruction = instructions[index];
        ilProcessor.Remove(extraPopInstruction);
        ilProcessor.InsertAfter(doubleDupInstruction, extraPopInstruction);
        doubleDupInstruction = null;
    }
}

Пока это работает в программе приличного размера. Я буду следить за ним, и буду обновлять, если что-то изменится. Было бы намного лучше, если бы я мог найти источник проблемы в «фиксаторе возврата».


person Ahmed Elsawalhy    schedule 15.08.2017    source источник


Ответы (1)


Я думаю, что у вас непостоянный размер стека при достижении IL_000b в зависимости от исхода из IL0004 или IL0009.

Вот мой анализ, значение справа — это размер стека после выполнения соответствующей строки.

                            [0]
IL_0000: ldarg.1            [1]
IL_0001: dup                [2]
IL_0002: dup                [3]
IL_0003: stloc.0            [2]
IL_0004: brtrue.s IL_000b   [1]---
                                 |
IL_0006: pop                [0]  |
IL_0007: ldarg.2            [1]  |
IL_0008: stloc.0            [0]  |
IL_0009: br.s IL_000b       [0]  |
                                 v
IL_000b: nop                [?] arriving here is inconsistent
IL_000c: nop
IL_000d: ldloc.0
IL_000e: ret

Может быть, вы можете запустить PEVerify для этого метода и посмотреть, что там на выходе.

person thehennyy    schedule 16.08.2017
comment
Я проверил несколько примеров скомпилированного кода и во всех них обнаружил, что stloc, за которым следует br, очень распространены. Итак, я не думаю, что br использует что-либо из стека для оценки. - person Ahmed Elsawalhy; 16.08.2017
comment
Да, ты прав. Это безусловная ветвь. Нет переполнения, но все еще несовместимо - person thehennyy; 16.08.2017
comment
Не могли бы вы уточнить, как это несовместимо в этом случае? Что я мог сделать, чтобы решить эту проблему? - person Ahmed Elsawalhy; 16.08.2017
comment
При переходе от IL_0004 к IL_000b в стеке находится один элемент. При переходе от IL_0009 к IL_000b в стеке нет предметов. - person thehennyy; 16.08.2017
comment
Вероятно, это скорее ошибка на стороне fody. Что делать, больше зависит от общего варианта использования. Исправление этого метода вручную или использование других конструкций на стороне С# могут быть вариантами. - person thehennyy; 16.08.2017
comment
Спасибо. Ваша подсказка помогла мне найти временное решение. - person Ahmed Elsawalhy; 16.08.2017