From 191a3c183ffdc7a48768983979a5e8509f4e8a6f Mon Sep 17 00:00:00 2001 From: yowl Date: Sun, 19 Apr 2020 16:28:37 -0500 Subject: [PATCH] Wasm: change NRE handling from trap to exception (#8096) --- .../src/CodeGen/ILToWebAssemblyImporter.cs | 22 +++++++--- tests/src/Simple/HelloWasm/Program.cs | 43 +++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs index bcce9d469fd..1cf2787e4b6 100644 --- a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs +++ b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs @@ -4210,20 +4210,30 @@ private void ThrowIfNull(LLVMValueRef entry) var resultAddress = builder.BuildIntCast(builder.BuildAlloca(LLVMTypeRef.Int32, "resultAddress"), LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0), "castResultAddress"); HandleDirectCall(helperMethod, helperMethod.Signature, arguments, null, default(LLVMValueRef), 0, NullRefFunction.GetParam(0), builder, true, resultAddress, helperMethod); - var exceptionEntry = new ExpressionEntry(GetStackValueKind(nullRefType), "RhNewObject_return", resultAddress, nullRefType); + var exceptionEntry = new LoadExpressionEntry(GetStackValueKind(nullRefType), "RhNewObject_return", resultAddress, nullRefType); var ctorDef = nullRefType.GetDefaultConstructor(); - var constructedExceptionObject = HandleDirectCall(ctorDef, ctorDef.Signature, new StackEntry[] { exceptionEntry }, null, default(LLVMValueRef), 0, NullRefFunction.GetParam(0), builder, false, default(LLVMValueRef), ctorDef); + HandleDirectCall(ctorDef, ctorDef.Signature, new StackEntry[] { exceptionEntry }, null, default(LLVMValueRef), 0, NullRefFunction.GetParam(0), builder, false, default(LLVMValueRef), ctorDef); - EmitTrapCall(builder); + EnsureRhpThrowEx(); + LLVMValueRef[] args = new LLVMValueRef[] { exceptionEntry.ValueAsType(LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0), builder) }; + builder.BuildCall(RhpThrowEx, args, ""); + builder.BuildUnreachable(); builder.PositionAtEnd(retBlock); builder.BuildRetVoid(); } - LLVMValueRef shadowStack = _builder.BuildGEP(_currentFunclet.GetParam(0), new LLVMValueRef[] { LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, (uint)(GetTotalLocalOffset() + GetTotalParameterOffset()), false) }, String.Empty); + LLVMBasicBlockRef nextInstrBlock = default; + CallOrInvoke(false, _builder, GetCurrentTryRegion(), NullRefFunction, new List { GetShadowStack(), entry }, ref nextInstrBlock); + } - _builder.BuildCall(NullRefFunction, new LLVMValueRef[] { shadowStack, entry }, string.Empty); + void EnsureRhpThrowEx() + { + if (RhpThrowEx.Handle.Equals(IntPtr.Zero)) + { + RhpThrowEx = Module.AddFunction("RhpThrowEx", LLVMTypeRef.CreateFunction(LLVMTypeRef.Void, new LLVMTypeRef[] { LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0) }, false)); + } } private LLVMValueRef GetInstanceFieldAddress(StackEntry objectEntry, FieldDesc field) @@ -4248,7 +4258,7 @@ private LLVMValueRef GetInstanceFieldAddress(StackEntry objectEntry, FieldDesc f { untypedObjectValue = objectEntry.ValueAsType(LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0), _builder); } - + ThrowIfNull(untypedObjectValue); if (field.Offset.AsInt == 0) { return untypedObjectValue; diff --git a/tests/src/Simple/HelloWasm/Program.cs b/tests/src/Simple/HelloWasm/Program.cs index c1269e05a0a..7f53c9b7ea6 100644 --- a/tests/src/Simple/HelloWasm/Program.cs +++ b/tests/src/Simple/HelloWasm/Program.cs @@ -342,6 +342,8 @@ private static unsafe int Main(string[] args) TestInterlockedExchange(); + TestThrowIfNull(); + // This test should remain last to get other results before stopping the debugger PrintLine("Debugger.Break() test: Ok if debugger is open and breaks."); System.Diagnostics.Debugger.Break(); @@ -1633,6 +1635,42 @@ static void TestInterlockedExchange() EndTest(exInt1 == 2 && exLong1 == 3); } + static void TestThrowIfNull() + { + StartTest("TestThrowIfNull"); + ClassForNre c = null; + var success = true; + try + { + var f = c.F; //field access + PrintLine("NRE Field access failed"); + success = false; + } + catch(NullReferenceException) + { + } + catch(Exception) + { + success = false; + } + + try + { + var f = c.ToString(); //method access + PrintLine("NRE method access failed"); + success = false; + } + catch (NullReferenceException) + { + } + catch (Exception) + { + success = false; + } + + EndTest(success); + } + static ushort ReadUInt16() { // something with MSB set @@ -1649,6 +1687,11 @@ static ushort ReadUInt16() private static unsafe extern int printf(byte* str, byte* unused); } +public class ClassForNre +{ + public int F; +} + public class ClassWithFloat { public static float F;