runtime: ".?" operator with foreach doesn't creates nullptr check assembly. also no compile-time exception

(first please understand about my english skills. Im really bad at english)

this is example.

class Program
{

        class Foo
        {
            public Bar bar = null;
        }

        class Bar
        {
            public List<int> list = new List<int>(2);
        }

        static void Main(string[] args)
        {
            Foo foo = new Foo();

            foreach (int j in foo.bar?.list)
            {
                System.Console.WriteLine(j);
            }
        }
}

this should throws compile-time error because there is no things to handle is foo.bar?. that is nullptr also. even assembly JIT codegen doesn’t even creates nullptr check assembly.

See dump here.

25:             Foo foo = new Foo();
00007FF87C7C4941 48 B9 38 55 80 7C F8 7F 00 00 mov         rcx,7FF87C805538h  
00007FF87C7C494B E8 F0 02 65 5F       call        00007FF8DBE14C40  
00007FF87C7C4950 48 89 45 68          mov         qword ptr [rbp+68h],rax  
00007FF87C7C4954 48 8B 4D 68          mov         rcx,qword ptr [rbp+68h]  
00007FF87C7C4958 E8 F3 C6 FF FF       call        00007FF87C7C1050  
00007FF87C7C495D 48 8B 55 68          mov         rdx,qword ptr [rbp+68h]  
00007FF87C7C4961 48 89 95 90 00 00 00 mov         qword ptr [rbp+90h],rdx  


    27:             foreach (int j in foo.bar?.list)
00007FF87C7C4969 48 8B 95 90 00 00 00 mov         rdx,qword ptr [rbp+90h]  << here is where that exception throws in run-time
00007FF87C7C4970 48 8B 52 08          mov         rdx,qword ptr [rdx+8]  
00007FF87C7C4974 48 89 55 60          mov         qword ptr [rbp+60h],rdx  
00007FF87C7C4978 48 8B 55 60          mov         rdx,qword ptr [rbp+60h]  
00007FF87C7C497C 48 89 55 58          mov         qword ptr [rbp+58h],rdx  
00007FF87C7C4980 48 8B 55 60          mov         rdx,qword ptr [rbp+60h]  
00007FF87C7C4984 48 85 D2             test        rdx,rdx  
00007FF87C7C4987 75 09                jne         00007FF87C7C4992  
00007FF87C7C4989 90                   nop  
00007FF87C7C498A 33 D2                xor         edx,edx  
00007FF87C7C498C 48 89 55 50          mov         qword ptr [rbp+50h],rdx  
00007FF87C7C4990 EB 0C                jmp         00007FF87C7C499E  
00007FF87C7C4992 48 8B 55 58          mov         rdx,qword ptr [rbp+58h]  
00007FF87C7C4996 48 8B 52 08          mov         rdx,qword ptr [rdx+8]  
00007FF87C7C499A 48 89 55 50          mov         qword ptr [rbp+50h],rdx  
00007FF87C7C499E 48 8D 55 38          lea         rdx,[rbp+38h]  
00007FF87C7C49A2 48 8B 4D 50          mov         rcx,qword ptr [rbp+50h]  
00007FF87C7C49A6 39 09                cmp         dword ptr [rcx],ecx  
00007FF87C7C49A8 E8 A3 C4 E8 5E       call        00007FF8DB650E50  
00007FF87C7C49AD C4 E1 7A 6F 45 38    vmovdqu     xmm0,xmmword ptr [rbp+38h]  
00007FF87C7C49B3 C4 E1 7A 7F 45 78    vmovdqu     xmmword ptr [rbp+78h],xmm0  
00007FF87C7C49B9 48 8B 4D 48          mov         rcx,qword ptr [rbp+48h]  
00007FF87C7C49BD 48 89 8D 88 00 00 00 mov         qword ptr [rbp+88h],rcx  
00007FF87C7C49C4 90                   nop  
00007FF87C7C49C5 EB 1D                jmp         00007FF87C7C49E4  
00007FF87C7C49C7 48 8D 4D 78          lea         rcx,[rbp+78h]  
00007FF87C7C49CB E8 D0 D1 E8 5E       call        00007FF8DB651BA0  
00007FF87C7C49D0 89 45 30             mov         dword ptr [rbp+30h],eax  
00007FF87C7C49D3 8B 4D 30             mov         ecx,dword ptr [rbp+30h]  
00007FF87C7C49D6 89 4D 74             mov         dword ptr [rbp+74h],ecx 

00007FF87C7C4969 48 8B 95 90 00 00 00 mov rdx,qword ptr [rbp+90h] << doesn’t even check nullptr before. it should be like.

lea rdx, qword ptr[rbp+90h]
test rdx, rdx
jne ~ nullptr handler ~
mov rdx, [rdx]

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 15 (7 by maintainers)

Most upvoted comments

An allocating null check seems wrong here. Use of foreach (int j in foo.bar?.list ?? new List<int>()) to simply not NRE is ridiculous. I agree with @ArtBlnd here, either the compiler should emit a warning or actually handle the GetEnumerator() -> null issue correctly.

IMO the compiler should do the equivalent of if (iter != null) { while (iter.MoveNext()) { ... } } when the enumerable is referenced via ?..

The suggestion that developers should know the “secret handshake” or that the operator doesn’t work as advertised so developers should know better is incorrect. Any time “tribal knowledge” is required, we’ve failed the consumers of the C# language.

Well, that’s my two-cents.