Closed
Description
An unnecessary null check of this
is generated even if it is known to be non-null due to a previous explicit check:
using System.Collections.Generic;
class Info {
List<int> slice = new List<int>();
public bool IsTracked(int x) {
return slice != null && slice.Contains(x);
}
}
class Visitor {
Info info = new Info();
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public int Test(int x) {
if (info.IsTracked(x))
return 0;
return x;
}
}
class Program {
static int Main(string[] args) {
var v = new Visitor();
return v.Test(args.Length);
}
}
Generated code for the relevant block in the Test
method:
G_M2972_IG02:
488B4908 mov rcx, gword ptr [rcx+8]
488B4908 mov rcx, gword ptr [rcx+8]
4885C9 test rcx, rcx ;explicit null check
740B je SHORT G_M2972_IG03
8BD6 mov edx, esi
3909 cmp dword ptr [rcx], ecx ;redundant null check
E85389AA5E call System.Collections.Generic.List`1[Int32][System.Int32]:Contains(int):bool:this
EB02 jmp SHORT G_M2972_IG04
This does not happen if info
is copied into a local variable and used for both the null check and the Contains
call. The null check optimization behaves as if the slice
field is loaded twice and in that case the null check would be required to avoid a race condition. Probably there's another part of the optimizer which later decides to (correctly) eliminate the second load but it's too late for the null check to be removed.
category:cq
theme:cse
skill-level:intermediate
cost:small
impact:small