/// <summary> /// Emit the PhpType finalizer. The finalizer is emitted only if there is __destruct() function /// and there is no finalizer in any base class already. The finalizer calls this.Dispose() which /// calls __destruct() function directly. /// </summary> /// <param name="phpType"></param> private static void EmitFinalizer(PhpType /*!*/ phpType) { // only if __destruct was now defined in some base class, no need to override existing definition on Finalize DRoutine basedestruct; DRoutineDesc destruct; if ((destruct = phpType.TypeDesc.GetMethod(DObject.SpecialMethodNames.Destruct)) != null && (phpType.Base == null || phpType.Base.GetMethod(DObject.SpecialMethodNames.Destruct, phpType, out basedestruct) == GetMemberResult.NotFound)) { MethodBuilder finalizer_builder = phpType.RealTypeBuilder.DefineMethod("Finalize", MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Family, typeof(void), Type.EmptyTypes); ILEmitter dil = new ILEmitter(finalizer_builder); // exact Finalize() method pattern follows: // try dil.BeginExceptionBlock(); // this.Dispose(false) dil.Emit(OpCodes.Ldarg_0); dil.Emit(OpCodes.Ldc_I4_0); dil.Emit(OpCodes.Callvirt, PHP.Core.Emit.Methods.DObject_Dispose); // finally dil.BeginFinallyBlock(); // Object.Finalize() dil.Emit(OpCodes.Ldarg_0); dil.Emit(OpCodes.Call, PHP.Core.Emit.Methods.Object_Finalize); dil.EndExceptionBlock(); dil.Emit(OpCodes.Ret); } }
/// <summary> /// Emits the try block and the catch blocks. /// </summary> /// <param name="codeGenerator">A code generator.</param> /// <remarks> /// <code> /// try /// { /// // guarded code // /// } /// catch(E1 $e1) /// { /// // E1 // /// } /// catch(E2 $e2) /// { /// // E2 // /// } /// </code> /// is translated as follows: /// <code> /// try /// { /// // guarded code // /// } /// catch(PhpUserException _e) /// { /// PhpObject _o = _e.UserException; /// if (_o instanceOf E1) /// { /// $e1 = _o; /// // E1 // /// } /// else if (_o instanceOf E2) /// { /// $e2 = _o; /// // E2 // /// } /// else /// { /// throw; /// } /// } /// </code> /// </remarks> internal override void Emit(CodeGenerator /*!*/ codeGenerator) { Statistics.AST.AddNode("TryStmt"); // emit try block without CLR exception block if possible if (!HasCatches && !HasFinallyStatements) { this.Statements.Emit(codeGenerator); return; } // emit CLR exception block ILEmitter il = codeGenerator.IL; codeGenerator.ExceptionBlockNestingLevel++; // TRY Label end_label = il.BeginExceptionBlock(); this.Statements.Emit(codeGenerator); // catches if (HasCatches) { // catch (PHP.Core.ScriptDiedException) // { throw; } il.BeginCatchBlock(typeof(PHP.Core.ScriptDiedException)); il.Emit(OpCodes.Rethrow); // catch (System.Exception ex) il.BeginCatchBlock(typeof(System.Exception)); // <exception_local> = (DObject) (STACK is PhpUserException) ? ((PhpUserException)STACK).UserException : ClrObject.WrapRealObject(STACK) Label clrExceptionLabel = il.DefineLabel(); Label wrapEndLabel = il.DefineLabel(); LocalBuilder exception_local = il.GetTemporaryLocal(typeof(DObject)); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Isinst, typeof(PHP.Core.PhpUserException)); // <STACK> as PhpUserException il.Emit(OpCodes.Brfalse, clrExceptionLabel); // if (<STACK> as PhpUserException != null) { il.Emit(OpCodes.Ldfld, Fields.PhpUserException_UserException); il.Emit(OpCodes.Br, wrapEndLabel); } // else il.MarkLabel(clrExceptionLabel); { il.Emit(OpCodes.Call, Methods.ClrObject_WrapRealObject); } il.MarkLabel(wrapEndLabel); il.Stloc(exception_local); // emits all PHP catch-blocks processing into a single CLI catch-block: foreach (CatchItem c in catches) { Label next_catch_label = il.DefineLabel(); // IF (exception <instanceOf> <type>); c.Emit(codeGenerator, exception_local, end_label, next_catch_label); // ELSE il.MarkLabel(next_catch_label); } il.ReturnTemporaryLocal(exception_local); // emits the "else" branch invoked if the exceptions is not catched: il.Emit(OpCodes.Rethrow); } // finally if (HasFinallyStatements) { finallyItem.Emit(codeGenerator); } // il.EndExceptionBlock(); codeGenerator.ExceptionBlockNestingLevel--; }
private static void WriteFinalizers(ILEmitter il, MethodBase original, ILEmitter.Label returnLabel, Dictionary <string, VariableDefinition> variables, List <MethodInfo> finalizers) { // Finalizer layout: // Create __exception variable to store exception info and a skip flag // Wrap the whole method into a try/catch // Call finalizers at the end of method (simulate `finally`) // If __exception got set, throw it // Begin catch block // Store exception into __exception // If skip flag is set, skip finalizers // Call finalizers // If __exception is still set, rethrow (if new exception set, otherwise throw the new exception) // End catch block if (finalizers.Count == 0) { return; } Logger.Log(Logger.LogChannel.Info, () => "Writing finalizers"); il.emitBefore = il.IL.Body.Instructions[il.IL.Body.Instructions.Count - 1]; // Mark the original method return label here if it hasn't been yet il.MarkLabel(returnLabel); if (!variables.TryGetValue(RESULT_VAR, out var returnValueVar)) { var retVal = AccessTools.GetReturnedType(original); returnValueVar = variables[RESULT_VAR] = retVal == typeof(void) ? null : il.DeclareVariable(retVal); } // Create variables to hold custom exception var skipFinalizersVar = il.DeclareVariable(typeof(bool)); variables[EXCEPTION_VAR] = il.DeclareVariable(typeof(Exception)); // Start main exception block var mainBlock = il.BeginExceptionBlock(il.DeclareLabelFor(il.IL.Body.Instructions[0])); bool WriteFinalizerCalls(bool suppressExceptions) { var canRethrow = true; foreach (var finalizer in finalizers) { var start = il.DeclareLabel(); il.MarkLabel(start); EmitCallParameter(il, original, finalizer, variables, false); il.Emit(OpCodes.Call, finalizer); if (finalizer.ReturnType != typeof(void)) { il.Emit(OpCodes.Stloc, variables[EXCEPTION_VAR]); canRethrow = false; } if (suppressExceptions) { var exBlock = il.BeginExceptionBlock(start); il.BeginHandler(exBlock, ExceptionHandlerType.Catch, typeof(object)); il.Emit(OpCodes.Pop); il.EndExceptionBlock(exBlock); } } return(canRethrow); } // First, store potential result into a variable and empty the stack if (returnValueVar != null) { il.Emit(OpCodes.Stloc, returnValueVar); } // Write finalizers inside the `try` WriteFinalizerCalls(false); // Mark finalizers as skipped so they won't rerun il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Stloc, skipFinalizersVar); // If __exception is not null, throw var skipLabel = il.DeclareLabel(); il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]); il.Emit(OpCodes.Brfalse, skipLabel); il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]); il.Emit(OpCodes.Throw); il.MarkLabel(skipLabel); // Begin a generic `catch(Exception o)` here and capture exception into __exception il.BeginHandler(mainBlock, ExceptionHandlerType.Catch, typeof(Exception)); il.Emit(OpCodes.Stloc, variables[EXCEPTION_VAR]); // Call finalizers or skip them if needed il.Emit(OpCodes.Ldloc, skipFinalizersVar); var postFinalizersLabel = il.DeclareLabel(); il.Emit(OpCodes.Brtrue, postFinalizersLabel); var rethrowPossible = WriteFinalizerCalls(true); il.MarkLabel(postFinalizersLabel); // Possibly rethrow if __exception is still not null (i.e. suppressed) skipLabel = il.DeclareLabel(); il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]); il.Emit(OpCodes.Brfalse, skipLabel); if (rethrowPossible) { il.Emit(OpCodes.Rethrow); } else { il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]); il.Emit(OpCodes.Throw); } il.MarkLabel(skipLabel); il.EndExceptionBlock(mainBlock); if (returnValueVar != null) { il.Emit(OpCodes.Ldloc, returnValueVar); } }