{"record":{"id":"5c8a14a050d5e57b","repo":"pardeike/Harmony","slug":"fail","errorCode":null,"errorMessage":"fail","messagePattern":"fail","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"info","filePath":"HarmonyTests/Patching/Assets/Specials.cs","lineNumber":176,"sourceCode":"\t\t{\r\n\t\t\tyield return new CodeInstruction(OpCodes.Call, null);\r\n\t\t}\r\n\r\n\t\tstatic Exception Cleanup() => null;\r\n\t}\r\n\r\n\t// -----------------------------------------------------\r\n\r\n\tpublic class LateThrowClass1\r\n\t{\r\n\t\t[MethodImpl(MethodImplOptions.NoInlining)]\r\n\t\tpublic void Method(string str)\r\n\t\t{\r\n\t\t\tif (str.Length == 2)\r\n\t\t\t\treturn;\r\n\r\n\t\t\t// this throw is the last IL code before 'ret' in this method\r\n\t\t\tthrow new ArgumentException(\"fail\");\r\n\t\t}\r\n\t}\r\n\r\n\t[HarmonyPatch(typeof(LateThrowClass1), nameof(LateThrowClass1.Method))]\r\n\tpublic class LateThrowClass_Patch1\r\n\t{\r\n\t\tpublic static bool prefixCalled = false;\r\n\t\tpublic static bool postfixCalled = false;\r\n\r\n\t\tstatic void Prefix() => prefixCalled = true;\r\n\r\n\t\tstatic void Postfix() => postfixCalled = true;\r\n\t}\r\n\r\n\t// -----------------------------------------------------\r\n\r\n\tpublic class LateThrowClass2\r\n\t{\r","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/pardeike/Harmony/blob/e7872dc17008bc0ca2b3de1f53fd4120dbd7a17c/HarmonyTests/Patching/Assets/Specials.cs#L158-L194","documentation":"This ArgumentException with message 'fail' is deliberately thrown by the last IL instruction of LateThrowClass1.Method in Harmony's own test assets (Specials.cs:176). It exists to verify that Harmony's patching (especially finalizers / exception handlers appended by patches) correctly intercepts an exception thrown as the very last instruction before 'ret'. Seeing this error means the patched method executed and threw as designed; it only indicates a test failure when it escapes and is not caught by the expected finalizer patch.","triggerScenarios":"Calling LateThrowClass1.Method(string) with an argument whose Length != 2, either directly or via the generated patch delegate, while HarmonyPatch classes like LateThrowClass_Patch1 are applied. Any code path that invokes this target method during the 'Late throw' test scenario produces it; a finalizer patch that fails to rethrow or swallow it surfaces the raw exception to the caller.","commonSituations":"Developers encounter this pattern when writing their own Harmony patches against methods whose last IL instruction is a throw: the finalizer (exception handler) inserted by Harmony must handle it, otherwise the exception propagates. Copying this test fixture into user code and calling Method without the expected patch applied also yields the raw throw. It is also hit when a patch author forgets that finalizers run for throws occurring anywhere in the method body, including the tail position.","solutions":["Ensure the intended Harmony finalizer patch (e.g. LateThrowClass_Patch1) is actually applied before calling LateThrowClass1.Method — verify with PatchProcessor.Patch() return value or Harmony.GetPatchInfo.","If your own finalizer sees this exception, return normally (or handle ex) instead of rethrowing, so the 'last-IL throw' is consumed as the test expects.","Pass a string of Length == 2 to avoid entering the throw path during unrelated debugging.","When reproducing in user code, wrap the patched call in try/catch to confirm Harmony's finalizer ordering behavior.","Update or re-run with the matching Harmony version if patch application silently no-ops due to target resolution changes."],"exampleFix":"// before: raw call, exception escapes\nLateThrowClass1 lc = new LateThrowClass1();\nlc.Method(\"abc\"); // throws ArgumentException(\"fail\")\n\n// after: let the applied finalizer patch handle it, or guard explicitly\ntry\n{\n    lc.Method(\"abc\");\n}\ncatch (ArgumentException ex) when (ex.Message == \"fail\")\n{\n    // finalizer patch expected to handle this; log if it escaped\n}","handlingStrategy":"try-catch","validationCode":"var patches = Harmony.GetPatchInfo(typeof(LateThrowClass1).GetMethod(nameof(LateThrowClass1.Method)));\nif (patches == null || patches.Finalizers.Count == 0)\n    throw new InvalidOperationException(\"LateThrowClass1.Method is not patched with a finalizer; the trailing throw will escape.\");","typeGuard":"static bool IsExpectedThrow(Exception ex) => ex is ArgumentException ae && ae.Message == \"fail\";","tryCatchPattern":"try\n{\n    instance.Method(str);\n}\ncatch (ArgumentException ex) when (ex.Message == \"fail\")\n{\n    // expected: last-IL throw consumed here if no finalizer patch ran\n}","preventionTips":["Always pair patches of throwing methods with a finalizer (Exception __exception parameter).","Check Harmony.GetPatchInfo after patching to confirm finalizers were registered.","Use guard clauses (early return) to skip throw paths when debugging patched methods.","Write an assertion in tests that the finalizer saw the expected exception message.","Keep Harmony version in sync with test-asset expectations for finalizer semantics."],"tags":["harmony","exception-handling","finalizer-patch","argumentexception","test-fixture"],"backgroundTag":"invalid-argument-value","analyzedSha":"e7872dc17008bc0ca2b3de1f53fd4120dbd7a17c","analyzedAt":"2026-09-15T22:47:11.550Z","contentChangedAt":"2026-09-15T22:47:11.550Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}