强制“Set Next Statement"进入“if"块时的 CLR System.NullReferenceException [英] CLR System.NullReferenceException when forcing 'Set Next Statement' into 'if' block

查看:14
本文介绍了强制“Set Next Statement"进入“if"块时的 CLR System.NullReferenceException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我承认这不会在正常代码执行过程中发生,但我在调试时发现了它,并认为分享很有趣.

I accept this isn't something that can occur during normal code execution but I discovered it while debugging and thought it interesting to share.

我认为这是由 JIT 编译器引起的,但欢迎您提出进一步的想法.

I think this is caused by the JIT compiler, but would welcome any further thoughts.

我已经使用 VS2013 针对 4.5 和 4.5.1 框架复制了这个问题:

I have replicated this issue targeting the 4.5 and 4.5.1 framework using VS2013:

要查看此异常 Common Language Runtime Exceptions 必须启用:DEBUG > 异常...

To see this exception Common Language Runtime Exceptions must be enabled: DEBUG > Exceptions...

我已将问题的原因提炼为以下示例:

I have distilled the cause of the issue to the following example:

using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication6
{
    public class Program
    {
        static void Main()
        {
            var myEnum = MyEnum.Good;

            var list = new List<MyData>
            {
                new MyData{ Id = 1, Code = "1"},
                new MyData{ Id = 2, Code = "2"},
                new MyData{ Id = 3, Code = "3"}
            };

            // Evaluates to false
            if (myEnum == MyEnum.Bad) // BREAK POINT 
            {
                /*
                 * A first chance exception of type 'System.NullReferenceException' occurred in ConsoleApplication6.exe

                   Additional information: Object reference not set to an instance of an object.
                 */
                var x = new MyClass();

                MyData result;
                //// With this line the 'System.NullReferenceException' gets thrown in the line above:
                result = list.FirstOrDefault(r => r.Code == x.Code);

                //// But with this line, with 'x' not referenced, the code above runs ok:
                //result = list.FirstOrDefault(r => r.Code == "x.Code");
            }
        }
    }

    public enum MyEnum
    {
        Good,
        Bad
    }

    public class MyClass
    {
        public string Code { get; set; }
    }

    public class MyData
    {
        public int Id { get; set; }
        public string Code { get; set; }
    }
}

<小时>

复制

if (myEnum == MyEnum.Bad) 上放置一个断点并运行代码.当遇到断点时,Set Next Statement(Ctrl+Shift+F10) 为左大括号if 语句并运行直到:


To Replicate

Place a breakpoint on if (myEnum == MyEnum.Bad) and run the code. When the break point is hit, Set Next Statement(Ctrl+Shift+F10) to be the opening brace of the if statement and run until:

接下来,注释第一个 lamda 语句并注释第二个 - 这样 MyClass 实例不被使用.重新运行该过程(点击中断,强制进入 if 语句并运行).您会看到代码正常工作:

Next, comment out the first lamda statement and comment in the second - so the MyClass instance isn't used. Rerun the process (hitting the break, forcing into the if statement and running). You'll see the code works correctly:

最后,注释in第一个lamda语句并注释out第二个 - 所以MyClass实例用过的.然后将if语句的内容重构为一个新的方法:

Finally, comment in the first lamda statement and comment out the second - so the MyClass instance is used. Then refactor the contents of the if statement into a new method:

using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication6
{
    public class Program
    {
        static void Main()
        {
            var myEnum = MyEnum.Good;

            var list = new List<MyData>
            {
                new MyData{ Id = 1, Code = "1"},
                new MyData{ Id = 2, Code = "2"},
                new MyData{ Id = 3, Code = "3"}
            };

            // Evaluates to false
            if (myEnum == MyEnum.Bad) // BREAK POINT 
            {
                MyMethod(list);
            }
        }

        private static void MyMethod(List<MyData> list)
        {
            // When the code is in this method, it works fine
            var x = new MyClass();

            MyData result;

            result = list.FirstOrDefault(r => r.Code == x.Code);
        }
    }

    public enum MyEnum
    {
        Good,
        Bad
    }

    public class MyClass
    {
        public string Code { get; set; }
    }

    public class MyData
    {
        public int Id { get; set; }
        public string Code { get; set; }
    }
}

重新运行测试,一切正常:

Rerun the test and everything works correctly:

我的假设是 JIT 编译器已将 lamda 优化为始终为空,并且在初始化实例之前运行了一些进一步优化的代码.

My assumption is the JIT compiler has optimized out the lamda to always be null, and some further optimized code is running prior to the instance being initialized.

正如我之前提到的,这在生产代码中永远不会发生,但我很想知道发生了什么.

As I previously mentioned this could never happen in production code, but I would be interested to know what was happening.

推荐答案

这是一个不可避免的事故,与优化无关.通过使用 Set Next Statement 命令,您可以绕过 更多 代码,这比您从源代码中容易看到的代码要多.只有当您查看生成的机器代码时,它才会变得明显.在断点处使用 Debug + Windows + Disassembly.你会看到:

This is a pretty inevitable mishap, not related to optimization. By using the Set Next Statement command, you are bypassing more code than you can easily see from the source code. It only becomes obvious when you look at the generated machine code. Use Debug + Windows + Disassembly at the breakpoint. You'll see:

            // Evaluates to false
            if (myEnum == MyEnum.Bad) // BREAK POINT 
0000016c  cmp         dword ptr [ebp-3Ch],1 
00000170  setne       al 
00000173  movzx       eax,al 
00000176  mov         dword ptr [ebp-5Ch],eax 
00000179  cmp         dword ptr [ebp-5Ch],0 
0000017d  jne         00000209 
00000183  mov         ecx,2B02C6Ch               // <== You are bypassing this
00000188  call        FFD6FAE0 
0000018d  mov         dword ptr [ebp-7Ch],eax 
00000190  mov         ecx,dword ptr [ebp-7Ch] 
00000193  call        FFF0A190 
00000198  mov         eax,dword ptr [ebp-7Ch] 
0000019b  mov         dword ptr [ebp-48h],eax 
            {
0000019e  nop 
                /*
                 * A first chance exception of type 'System.NullReferenceException' occurred in ConsoleApplication6.exe

                   Additional information: Object reference not set to an instance of an object.
                 */
                var x = new MyClass();
0000019f  mov         ecx,2B02D04h             // And skipped to this
000001a4  call        FFD6FAE0 
// etc...

那么,那个神秘代码是什么?这不是您在程序中明确编写的任何内容.您可以使用反汇编"窗口中的设置下一条语句"命令来查找.将其移至地址 00000183,即 if() 语句之后的第一个可执行代码.开始步进,您将看到它执行名为 ConsoleApplication1.Program.<>c__DisplayClass5

So, what is that mysterious code? It isn't anything you wrote in your program explicitly. You can find out by using the Set Next Statement command in the Disassembly window. Move it to address 00000183, the first executable code after the if() statement. Start stepping, you'll see it executing the constructor of a class named ConsoleApplication1.Program.<>c__DisplayClass5

在现有的 SO 问题中,除此之外,这是一个为源代码中的 lambda 表达式自动生成的类.需要在程序中存储捕获的变量,list.由于您跳过了它的创建,因此在 lambda 中取消引用 list 总是会被 NRE 轰炸.

Otherwise well covered in existing SO questions, this is an auto-generated class for the lambda expression in your source code. It is required to store captured variables, list in your program. Since you skipped its creation, dereferencing list in the lambda is always going to bomb with NRE.

泄漏抽象"的标准案例,C# 有一些,但并不出人意料.当然,您对此无能为力,您当然可以责怪调试器没有正确猜测这一点,但这是一个非常难以解决的问题.很难找出该代码是属于 if() 语句还是它后面的代码.一个设计问题,调试信息是基于行号的,没有代码行.总的来说,x64 抖动也是一个问题,即使在简单的情况下也会出错.应该在 VS2015 中修复.

A standard case of a "leaky abstraction", C# has some of it but not outrageously so. Nothing much you can do about it of course, you can certainly blame the debugger for not guessing at this correctly but it is a very difficult problem to solve. It cannot easily find out if that code belongs to the if() statement or the code that follows it. A design issue, debug info is line number based and there is no line of code. Also in general a problem with the x64 jitter, it fumbles even in simple cases. Which should be fixed in VS2015.

这是您必须学习 Hard Way™ 的内容.如果它真的非常重要,那么我向您展示了如何正确设置下一个语句,您必须在反汇编视图中执行它才能使其工作.请随时在 connect.microsoft.com 报告此问题,如果他们还不知道,我会感到惊讶.

This is something you have to learn the Hard Way™. If it is really, really important then I showed you how to set the next statement properly, you have to do it in the Disassembly view to make it work. Feel free to report this issue at connect.microsoft.com, I'd be surprised if they didn't already know about it however.

这篇关于强制“Set Next Statement"进入“if"块时的 CLR System.NullReferenceException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆