verificar variable valor validar tipo tiene referencia que objeto net hace excepción error controlada capapresentacion asp .net reflection nullreferenceexception .net-4.5 typeinitializer

.net - variable - ¿Por qué encontrar un inicializador de tipo arrojaría una NullReferenceException?



vb net nullreferenceexception (2)

Esto me tiene perplejo. Intentaba optimizar algunas pruebas para Noda Time, donde tenemos algún tipo de verificación de inicializador. Pensé que averiguaría si un tipo tiene un inicializador de tipo (constructor estático o variables estáticas con inicializadores) antes de cargar todo en un nuevo AppDomain . Para mi sorpresa, una pequeña prueba de esto arrojó NullReferenceException , a pesar de que no haya valores nulos en mi código. Solo arroja la excepción cuando se compila sin información de depuración.

Aquí hay un programa breve pero completo para demostrar el problema:

using System; class Test { static Test() {} static void Main() { var cctor = typeof(Test).TypeInitializer; Console.WriteLine("Got initializer? {0}", cctor != null); } }

Y una transcripción de compilación y salida:

c:/Users/Jon/Test>csc Test.cs Microsoft (R) Visual C# Compiler version 4.0.30319.17626 for Microsoft (R) .NET Framework 4.5 Copyright (C) Microsoft Corporation. All rights reserved. c:/Users/Jon/Test>test Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object. at System.RuntimeType.GetConstructorImpl(BindingFlags bindingAttr, Binder bin der, CallingConventions callConvention, Type[] types, ParameterModifier[] modifi ers) at Test.Main() c:/Users/Jon/Test>csc /debug+ Test.cs Microsoft (R) Visual C# Compiler version 4.0.30319.17626 for Microsoft (R) .NET Framework 4.5 Copyright (C) Microsoft Corporation. All rights reserved. c:/Users/Jon/Test>test Got initializer? True

Ahora notará que estoy usando .NET 4.5 (el candidato de la versión), que puede ser relevante aquí. Es un poco complicado para mí probarlo con los otros frameworks originales (en particular, "vanilla" .NET 4) pero si alguien más tiene fácil acceso a máquinas con otros frameworks, me interesarían los resultados.

Otros detalles:

  • Estoy en una máquina x64, pero este problema ocurre con los ensambles x86 y x64
  • Es la "depuración" del código de llamada lo que hace la diferencia, aunque en el caso de prueba anterior lo estoy probando en su propio ensamblado, cuando probé esto en Noda Time no tuve que NodaTime.dll a compilar NodaTime.dll para ver las diferencias, solo Test.cs que se refiere a eso.
  • Ejecutar el ensamblado "roto" en Mono 2.10.8 no arroja

¿Algunas ideas? ¿Error de Framework?

EDITAR: Más curioso y curioso. Si saca la llamada Console.WriteLine :

using System; class Test { static Test() {} static void Main() { var cctor = typeof(Test).TypeInitializer; } }

Ahora solo falla cuando se compila con csc /o- /debug- . Si activa las optimizaciones, ( /o+ ) funciona. Pero si incluye la llamada Console.WriteLine según el original, ambas versiones fallarán.


Como creo que he encontrado algunos hallazgos interesantes sobre el problema, decidí agregarlos como respuesta, reconociendo al mismo tiempo que no están abordando el "por qué sucede" en la pregunta original. Quizás alguien que sepa más sobre el funcionamiento interno de los tipos implicados podría publicar una respuesta edificante basada también en las observaciones que estoy publicando.

También he logrado reproducir el problema en mi máquina y he rastreado una conexión con la interfaz System.Runtime.InteropServices._Type , que se implementa mediante la clase System.Type .

Inicialmente, he encontrado al menos 3 enfoques alternativos para solucionar el problema:

  1. Simplemente lanzando Type a _Type dentro del método Main :

    var cctor = ((_Type)typeof(Test)).TypeInitializer;

  2. O asegúrate de que el método 1 se usó anteriormente dentro del método:

    var warmUp = ((_Type)typeof(Test)).TypeInitializer; var cctor = ((Type)typeof(Test)).TypeInitializer;

  3. O agregando un campo estático a la clase Test e inicializándola (con conversión a _Type ):

    static ConstructorInfo _dummy1 = (typeof(object) as _Type).TypeInitializer;

Más tarde, descubrí que si no queremos involucrar a la interfaz System.Runtime.InteropServices._Type en las soluciones, el problema no ocurre ni por:

  1. Agregar un campo estático a la clase Test e inicializarlo (sin convertirlo a _Type ):

    static ConstructorInfo _dummy2 = typeof(object).TypeInitializer;

  2. O inicializando la variable cctor sí misma como un campo estático de la clase:

    static ConstructorInfo cctor = typeof(Test).TypeInitializer;

Espero sus comentarios.


con csc test.cs :

(196c.1874): Access violation - code c0000005 (first chance) mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xa3: 000007fe`e5735403 488b4608 mov rax,qword ptr [rsi+8] ds:00000000`00000008=????????????????

[rsi+8] cargar desde [rsi+8] cuando @rsi es NULL. Vamos a inspeccionar la función:

0:000> ln 000007fe`e5735403 (000007fe`e5735360) mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xa3 0:000> uf 000007fe`e5735360 Flow analysis was incomplete, some code may be missing mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[]): 000007fe`e5735360 53 push rbx 000007fe`e5735361 55 push rbp 000007fe`e5735362 56 push rsi 000007fe`e5735363 57 push rdi 000007fe`e5735364 4154 push r12 000007fe`e5735366 4883ec30 sub rsp,30h 000007fe`e573536a 498bf8 mov rdi,r8 000007fe`e573536d 8bea mov ebp,edx 000007fe`e573536f 48c744242800000000 mov qword ptr [rsp+28h],0 000007fe`e5735378 488bb42480000000 mov rsi,qword ptr [rsp+80h] 000007fe`e5735380 4889742420 mov qword ptr [rsp+20h],rsi 000007fe`e5735385 41b903000000 mov r9d,3 ... mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0x97: 000007fe`e57353f7 488b4b08 mov rcx,qword ptr [rbx+8] 000007fe`e57353fb 85c9 test ecx,ecx 000007fe`e57353fd 0f848e000000 je mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0x131 (000007fe`e5735491) mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xa3: 000007fe`e5735403 488b4608 mov rax,qword ptr [rsi+8] 000007fe`e5735407 85c0 test eax,eax 000007fe`e5735409 7545 jne mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xf0 (000007fe`e5735450) ...

@rsi se carga al principio desde [rsp+20h] por lo que la persona que llama debe pasarlo. Veamos a la persona que llama:

0:000> k3 Child-SP RetAddr Call Site 00000000`001fec70 000007fe`8d450110 mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xa3 00000000`001fecd0 000007fe`ecb6e073 image00000000_01120000!Test.Main()+0x60 00000000`001fed20 000007fe`ecb6dcb2 clr!CoUninitializeEE+0x7ae1f 0:000> ln 000007fe`8d450110 (000007fe`8d4500b0) image00000000_01120000!Test.Main()+0x60 0:000> uf 000007fe`8d4500b0 image00000000_01120000!Test.Main(): 000007fe`8d4500b0 53 push rbx 000007fe`8d4500b1 4883ec40 sub rsp,40h 000007fe`8d4500b5 e8a69ba658 call mscorlib_ni!System.Console.get_In() (000007fe`e5eb9c60) 000007fe`8d4500ba 4c8bd8 mov r11,rax 000007fe`8d4500bd 498b03 mov rax,qword ptr [r11] 000007fe`8d4500c0 488b5048 mov rdx,qword ptr [rax+48h] 000007fe`8d4500c4 498bcb mov rcx,r11 000007fe`8d4500c7 ff5238 call qword ptr [rdx+38h] 000007fe`8d4500ca 488d0d7737eeff lea rcx,[000007fe`8d333848] 000007fe`8d4500d1 e88acb715f call clr!CoUninitializeEE+0x79a0c (000007fe`ecb6cc60) 000007fe`8d4500d6 4c8bd8 mov r11,rax 000007fe`8d4500d9 48b92012531200000000 mov rcx,12531220h 000007fe`8d4500e3 488b09 mov rcx,qword ptr [rcx] 000007fe`8d4500e6 498b03 mov rax,qword ptr [r11] 000007fe`8d4500e9 4c8b5068 mov r10,qword ptr [rax+68h] 000007fe`8d4500ed 48c744242800000000 mov qword ptr [rsp+28h],0 000007fe`8d4500f6 48894c2420 mov qword ptr [rsp+20h],rcx 000007fe`8d4500fb 41b903000000 mov r9d,3 000007fe`8d450101 4533c0 xor r8d,r8d 000007fe`8d450104 ba38000000 mov edx,38h 000007fe`8d450109 498bcb mov rcx,r11 000007fe`8d45010c 41ff5228 call qword ptr [r10+28h] 000007fe`8d450110 48bb1032531200000000 mov rbx,12533210h 000007fe`8d45011a 488b1b mov rbx,qword ptr [rbx] 000007fe`8d45011d 33d2 xor edx,edx 000007fe`8d45011f 488bc8 mov rcx,rax 000007fe`8d450122 e829452e58 call mscorlib_ni!System.Reflection.ConstructorInfo.op_Equality(System.Reflection.ConstructorInfo, System.Reflection.ConstructorInfo) (000007fe`e5734650) 000007fe`8d450127 0fb6c8 movzx ecx,al 000007fe`8d45012a 33c0 xor eax,eax 000007fe`8d45012c 85c9 test ecx,ecx 000007fe`8d45012e 0f94c0 sete al 000007fe`8d450131 0fb6c8 movzx ecx,al 000007fe`8d450134 894c2430 mov dword ptr [rsp+30h],ecx 000007fe`8d450138 488d542430 lea rdx,[rsp+30h] 000007fe`8d45013d 488d0d24224958 lea rcx,[mscorlib_ni+0x682368 (000007fe`e58e2368)] 000007fe`8d450144 e807246a5f call clr+0x2550 (000007fe`ecaf2550) 000007fe`8d450149 488bd0 mov rdx,rax 000007fe`8d45014c 488bcb mov rcx,rbx 000007fe`8d45014f e81cab2758 call mscorlib_ni!System.Console.WriteLine(System.String, System.Object) (000007fe`e56cac70) 000007fe`8d450154 90 nop 000007fe`8d450155 4883c440 add rsp,40h 000007fe`8d450159 5b pop rbx 000007fe`8d45015a c3 ret

(Mi desmontaje muestra System.Console.get_In porque agregué Console.GetLine() en test.cs para tener la oportunidad de romper el depurador. Validé que no cambia el comportamiento).

Estamos en esta llamada: 000007fe8d45010c 41ff5228 call qword ptr [r10+28h] (nuestra dirección de 000007fe8d45010c 41ff5228 call qword ptr [r10+28h] AV es la instrucción inmediatamente después de esta call ).

Comparemos esto con lo que ocurre cuando csc /debug test.cs Podemos configurar un bp 000007fee5735360 , afortunadamente el módulo se carga en la misma dirección. En la instrucción que carga @rsi :

0:000> r rax=000007fee58e2f30 rbx=00000000027c6258 rcx=00000000027c6258 rdx=0000000000000038 rsi=00000000002debd8 rdi=0000000000000000 rip=000007fee5735378 rsp=00000000002de990 rbp=0000000000000038 r8=0000000000000000 r9=0000000000000003 r10=000007fee58831c8 r11=00000000002de9c0 r12=0000000000000000 r13=00000000002dedc0 r14=00000000002dec58 r15=0000000000000004 iopl=0 nv up ei pl nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0x18: 000007fe`e5735378 488bb42480000000 mov rsi,qword ptr [rsp+80h] ss:00000000`002dea10=a0627c0200000000

Tenga en cuenta que @rsi es 00000000002debd8. Pasar por la función muestra que esta es la dirección que será desreferenciada más tarde en el lugar cuando las bombas bad exe (es decir, @rsi no cambia). La pila es muy interesante porque muestra un marco extra :

0:000> k3 Child-SP RetAddr Call Site 00000000`002de990 000007fe`e5eddf68 mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0x18 00000000`002de9f0 000007fe`8d460119 mscorlib_ni!System.Type.get_TypeInitializer()+0x48 00000000`002dea30 000007fe`ecb6e073 good!Test.Main()+0x49*** WARNING: Unable to verify checksum for good.exe 0:000> ln 000007fe`e5eddf68 (000007fe`e5eddf20) mscorlib_ni!System.Type.get_TypeInitializer()+0x48 0:000> uf 000007fe`e5eddf20 mscorlib_ni!System.Type.get_TypeInitializer(): 000007fe`e5eddf20 53 push rbx 000007fe`e5eddf21 4883ec30 sub rsp,30h 000007fe`e5eddf25 488bd9 mov rbx,rcx 000007fe`e5eddf28 ba22010000 mov edx,122h 000007fe`e5eddf2d b901000000 mov ecx,1 000007fe`e5eddf32 e8d1a075ff call CORINFO_HELP_GETSHARED_GCSTATIC_BASE (000007fe`e5638008) 000007fe`e5eddf37 488b88f0010000 mov rcx,qword ptr [rax+1F0h] 000007fe`e5eddf3e 488b03 mov rax,qword ptr [rbx] 000007fe`e5eddf41 4c8b5068 mov r10,qword ptr [rax+68h] 000007fe`e5eddf45 48c744242800000000 mov qword ptr [rsp+28h],0 000007fe`e5eddf4e 48894c2420 mov qword ptr [rsp+20h],rcx 000007fe`e5eddf53 41b903000000 mov r9d,3 000007fe`e5eddf59 4533c0 xor r8d,r8d 000007fe`e5eddf5c ba38000000 mov edx,38h 000007fe`e5eddf61 488bcb mov rcx,rbx 000007fe`e5eddf64 41ff5228 call qword ptr [r10+28h] 000007fe`e5eddf68 90 nop 000007fe`e5eddf69 4883c430 add rsp,30h 000007fe`e5eddf6d 5b pop rbx 000007fe`e5eddf6e c3 ret 0:000> ln 000007fe`8d460119

La llamada es la misma call qword ptr [r10+28h] que hemos visto antes, por lo que en el caso grave esta función probablemente esté en línea en Main() , por lo que el hecho de que haya un fotograma adicional es una pista falsa. Si miramos la preparación de esta call qword ptr [r10+28h] notamos esta instrucción: mov qword ptr [rsp+20h],rcx . Esto es lo que carga la dirección que finalmente se desreferencia como @rsi . En el buen caso, así es como se carga @rcx :

000007fe`e5eddf32 e8d1a075ff call CORINFO_HELP_GETSHARED_GCSTATIC_BASE (000007fe`e5638008) 000007fe`e5eddf37 488b88f0010000 mov rcx,qword ptr [rax+1F0h]

En el caso malo, se ve muy diferente:

000007fe`8d4600d9 48b92012721200000000 mov rcx,12721220h 000007fe`8d4600e3 488b09 mov rcx,qword ptr [rcx]

Esto es muy diferente. A diferencia del buen caso que llama CORINFO_HELP_GETSHARED_GCSTATIC_BASE y lee lo que termina como el puntero crítico que causa el AV de algún miembro en el desplazamiento 1F0 en una estructura de retorno, el código optimizado lo carga desde una dirección estática. Y, por supuesto, 12721220h contiene NULL:

0:000> dp 12721220h L8 00000000`12721220 00000000`00000000 00000000`00000000 00000000`12721230 00000000`00000000 00000000`02722198 00000000`12721240 00000000`027221c8 00000000`027221f8 00000000`12721250 00000000`02722228 00000000`02722258

Lamentablemente, ya es demasiado tarde para poder cavar más profundo en este momento, la disgregación de CORINFO_HELP_GETSHARED_GCSTATIC_BASE está lejos de ser trivial. Estoy publicando esto con la esperanza de que alguien más conocedor de las actividades internas de CLR pueda tener sentido (como puede ver, realmente consideré el problema solo a partir del POV de instrucciones nativas e ignoré por completo a IL).