{"id":3037,"date":"2020-12-09T16:58:39","date_gmt":"2020-12-09T04:58:39","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=3037"},"modified":"2020-12-09T17:52:21","modified_gmt":"2020-12-09T05:52:21","slug":"reraise-exception-bug-in-10-4-1-x86-compiler","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/3037\/","title":{"rendered":"ReRaise Exception Bug in 10.4.1 x86 Compiler"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span>\n<p>I just ran into a very frustrating issue in Delphi 10.4.1 with long-standing, basic functionality that is now broken in certain circumstances.  Fortunately there is a work-around in those certain circumstances, but it&#8217;s not pretty.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p class=\"has-text-color has-background has-medium-font-size has-very-light-gray-color has-vivid-red-background-color\"><strong>The Problem: <\/strong>Re-Raising an exception causes an immediate Access Violation error at the <code>raise<\/code> statement (in certain circumstances).<\/p>\n\n\n\n<p>An MCVE (Minimum, Complete\/Compilable, Verifiable Example) is difficult to provide, as a stand-alone example.  But the real-world scenario in which I ran into this is itself a CVE, if not entirely &#8220;Minimal&#8221;.<\/p>\n\n\n\n<p>This scenario was in a test case in the <a href=\"https:\/\/github.com\/deltics\/deltics.smoketest\">Smoketest<\/a> unit tests for my <a href=\"https:\/\/github.com\/deltics\/deltics.multicast\">Multicast<\/a> event library, specifically in this test:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-pascal\">  procedure TMulticastNotifyTests.AllHandlersAreCalledIfAnExceptionIsRaisedByOne;\n  begin\n    Test.RaisesException(EHandlerExceptions);\n\n    sut.Add(NotifyException);\n    sut.Add(NotifyA);\n\n    try\n      try\n        sut.DoEvent;\n      except\n        on e: EHandlerExceptions do\n        begin\n          Test(&#039;e.Count&#039;).Assert(e.Count).Equals(1);\n          Test(&#039;e.Exceptions[0] is Exception&#039;).Assert(e[0] is Exception);\n\n          raise;\n        end;\n      end;\n\n    finally\n      Test(&#039;fCallCountA&#039;).Assert(fCallCountA).Equals(1);\n    end;\n  end;<\/code><\/pre>\n\n\n\n<p class=\"has-background has-luminous-vivid-amber-background-color\"><code>sut<\/code> is a unit variable referencing a <code>TMulticastNotify<\/code> event instance, initialised (and reset) by <code>Setup<\/code> and <code>Teardown<\/code> methods that executing before and after each test.<\/p>\n\n\n\n<p>The intent of this test is as follows:<\/p>\n\n\n\n<p><strong>ARRANGE<\/strong><\/p>\n\n\n\n<ul><li>Add two handlers to the sut (subject under test), which is a multicast <code>TNotifyEvent<\/code> (<code>TMulticastNotify<\/code>).<ul><li>The first handler raises an exception when called<\/li><li>The second handler increments a call count variable<\/li><\/ul><\/li><\/ul>\n\n\n\n<p><strong>ACT<\/strong><\/p>\n\n\n\n<ul><li>The event is fired<\/li><\/ul>\n\n\n\n<p><strong>ASSERT:<\/strong><\/p>\n\n\n\n<ul><li>An <code>EHandlerExceptions<\/code> exception is raised by the multicast event.  (This is an aggregate exception type that captures any exceptions raised by any handlers).<\/li><li>The <code>EHandlerExceptions<\/code> exception has captured a single exception of class <code>Exception<\/code>.<\/li><li>All handlers are called, despite the exception raised by the first handler (tested by ensuring that <code>fCallCountA<\/code> has been incremented).<\/li><\/ul>\n\n\n\n<p>For the first of these test outcomes, the test uses a <a href=\"https:\/\/github.com\/deltics\/deltics.smoketest\">new feature in Smoketest 2.1<\/a> that describes an expected exception in the initial arrange phase.<\/p>\n\n\n\n<p>The test must then <em>catch<\/em> that exception in order to test it for the expected properties.  The exception is then <em>re-raised<\/em> to allow the stated, expected exception to be satisfied.  The <code>Test.RaisesException()<\/code> could have been replaced by tests in the exception handler, avoiding the re-raise, but at the expense of (a little) greater complexity (imho).<\/p>\n\n\n\n<p>Finally, the call count is tested to ensure that even though the first handler raised an exception, the second handler was also successfully called.<\/p>\n\n\n\n<p>And this test executes and passes successfully in all versions of Delphi from 7 thru 10.3.  It also executes and passes in an x64 build with 10.4.1.<\/p>\n\n\n\n<p>But in an x86 build with 10.4.1, the test fails because the <code>raise<\/code> statement in the <code>e: EHandlerExceptions<\/code> handler crashes with an <em>Access Violation<\/em>.  <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"269\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image.png?resize=640%2C269&#038;ssl=1\" alt=\"\" class=\"wp-image-3038\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image.png?resize=1024%2C431&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image.png?resize=300%2C126&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image.png?resize=768%2C323&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image.png?resize=380%2C160&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image.png?w=1446&amp;ssl=1 1446w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>We can see in this log output from the Azure DevOps build pipeline, that two tests exhibit this behaviour.  The second test case is similar and suffers the same problem as a result.<\/p>\n\n\n\n<p class=\"has-background has-luminous-vivid-amber-background-color\">Ordinarily this would be an <code>ERROR<\/code> result, rather than <code>FAILED<\/code>, but because the test has the expectation of <em>some<\/em> exception being raised, the test is considered to have failed because although an exception was indeed raised, it was not of the expected class.<\/p>\n\n\n\n<p>I have not yet gotten to the bottom of why this happens.  However, I came upon <a href=\"https:\/\/en.delphipraxis.net\/topic\/3515-help-needed-re-raising-exception-gives-av\/\">this discussion thread<\/a> in which someone experiencing the same problem mentioned that passing the captured exception as a parameter to a procedure &#8216;fixed&#8217; the problem.<\/p>\n\n\n\n<p>So I applied that to my case as follows:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-pascal\">  procedure TMulticastNotifyTests.AllHandlersAreCalledIfAnExceptionIsRaisedByOne;\n\n  {$ifdef DELPHI10_4}\n    \/\/ In 10.4.1, re-raising the exception crashes immediately with an AV exception.\n    \/\/  This is a compiler bug that can be avoided by passing the capture exception (e)\n    \/\/  to a procedure (for some reason).\n    procedure DoTestsInProcToAvoidCompilerBugWhenReraisingTheException(e: EHandlerExceptions);\n    begin\n      Test(&#039;e.Count&#039;).Assert(e.Count).Equals(1);\n      Test(&#039;e.Exceptions[0] is Exception&#039;).Assert(e[0] is Exception);\n    end;\n  {$endif}\n\n  begin\n    Test.RaisesException(EHandlerExceptions);\n\n    sut.Add(NotifyException);\n    sut.Add(NotifyA);\n\n    try\n      try\n        sut.DoEvent;\n      except\n        on e: EHandlerExceptions do\n        begin\n        {$ifdef DELPHI10_4}\n          DoTestsInProcToAvoidCompilerBugWhenReraisingTheException(e);\n        {$else}\n          Test(&#039;e.Count&#039;).Assert(e.Count).Equals(1);\n          Test(&#039;e.Exceptions[0] is Exception&#039;).Assert(e[0] is Exception);\n        {$endif}\n\n          raise;\n        end;\n      end;\n\n    finally\n      Test(&#039;fCallCountA&#039;).Assert(fCallCountA).Equals(1);\n    end;\n  end;<\/code><\/pre>\n\n\n\n<p class=\"has-medium-font-size\">And it worked!  \\o\/<\/p>\n\n\n\n<p>Of course, this is far from ideal, resulting in duplication of the test conditions not to mention behaviour specific to a particular compiler version.  I could have adopted a consistent approach for <em>all<\/em> compiler versions of course, but decided not to as providing the best opportunity to identify whether the problem persists in Delphi 10.5 (or a compiler update to 10.4 with a different version identifier).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reproducability<\/h2>\n\n\n\n<p>Frustratingly, an MCVE console app that simply raises an exception, catches it and re-raises it does not exhibit the behaviour.  So is it something about my Smoketest framework?  The multicast event implementation?<\/p>\n\n\n\n<p>Well, the person experiencing this problem in that praxis thread isn&#8217;t using either of those things.  But perhaps there is something in common between our two scenarios.<\/p>\n\n\n\n<p>The multicast event implementation has some behaviour to capture the exceptions from the registered handlers, calling <code>AcquireExceptionObject<\/code> to ensure that the exceptions &#8220;live&#8221; beyond the lifetime of the encapsulating handler (and are then freed when the containing <code>EHandlerExceptions<\/code> exception is itself destroyed).<\/p>\n\n\n\n<p>But this is not especially exotic behaviour, nor does it cause any problems in any other version of Delphi or the 64-bit compiler of 10.4.  For completeness, here&#8217;s the test run output for that from the same build pipeline:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"128\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-1.png?resize=640%2C128&#038;ssl=1\" alt=\"\" class=\"wp-image-3039\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-1.png?resize=1024%2C205&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-1.png?resize=300%2C60&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-1.png?resize=768%2C154&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-1.png?resize=380%2C76&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-1.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>And a summary of the results across the entire pipeline:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"alignleft is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-2.png?resize=289%2C682&#038;ssl=1\" alt=\"\" class=\"wp-image-3042\" width=\"289\" height=\"682\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-2.png?resize=434%2C1024&amp;ssl=1 434w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-2.png?resize=127%2C300&amp;ssl=1 127w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-2.png?resize=110%2C260&amp;ssl=1 110w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image-2.png?w=708&amp;ssl=1 708w\" sizes=\"(max-width: 289px) 100vw, 289px\" data-recalc-dims=\"1\" \/><\/figure><\/div>\n\n\n\n<p>The warnings in the PreXE2 job come from Delphi 7, caused by the mere existence of &#8220;deprecated&#8221; methods in the Smoketest framework (not triggered in any other compiler version), which I still need to resolve to remove this &#8216;noise&#8217; from my pipelines.<\/p>\n\n\n\n<p>The multiple jobs for PreXE2, XEx86, XEx64 etc are due to the way I have &#8220;batched&#8221; my builds to take advantage of multiple build agents to get parallel builds and speed things up.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>I would love to hear from anyone who has an explanation for why this problem only manifests in some circumstances and why simply passing the captured exception as a param to local procedure does enough to keep the compiler on the straight and narrow.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> I just ran into a very frustrating issue in Delphi 10.4.1 with long-standing, basic functionality that is now broken in certain circumstances. Fortunately there is a work-around in those certain circumstances, but it&#8217;s not pretty.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"A very puzzling re-raise Exception bug in the Delphi 10.4.1 x86 Compiler (doesn't affect x64).","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4,348,48],"tags":[292],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-MZ","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":3045,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3045\/","url_meta":{"origin":3037,"position":0},"title":"Big Updates to Smoketest 2.x","date":"19 Dec 2020","format":false,"excerpt":"A little over a year ago, I released Smoketest 2.0, a complete re-write of the Smoketest unit testing framework first implemented some years ago. Other things then consumed my time, but in the past couple of months, Smoketest has rapidly progressed through no less than 4 (four!) fairly significant feature\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/vape_2.jpg?fit=1200%2C307&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":2337,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2337\/","url_meta":{"origin":3037,"position":1},"title":"A Silent Danger&#8230;","date":"07 Jun 2015","format":false,"excerpt":"A brief post on a long standing omission in type checking in Pascal and the limitations of Range Checking as applied to the problem. Consider this contrived example of a simple function: This very simple function accepts an explicitly 32-bit Integer parameter and simply returns TRUE if the value passed\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1817,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1817\/","url_meta":{"origin":3037,"position":2},"title":"Getting the Battery Level on Android With Delphi","date":"01 Oct 2013","format":false,"excerpt":"Over the past few days I posted a two part series showing how to obtain the current battery level as part of the implementation of an Android AppWidget using Oxygene. As far as I can tell AppWidgets simply aren't possible using Delphi but reading the battery is quite straightforward Android\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":194,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/194\/","url_meta":{"origin":3037,"position":3},"title":"Another Week, Another Poll","date":"17 Aug 2008","format":false,"excerpt":"Last weeks poll asked which was your first Delphi version.\u00a0 It may or may not come as a surprise that the results essentially provided a list of Delphi versions largely sorted by age. The one exception was that (of visitors to this blog) more gained their first Delphi experience from\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1540,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1540\/","url_meta":{"origin":3037,"position":4},"title":"Delphi and Oxygene &#8211; A Cost of Ownership Comparison","date":"31 Aug 2013","format":false,"excerpt":"In a comment on my previous post, David Michael made some observations about the cost of Oxygene being comparable to Delphi. This surprised me, so I did some checking. The results surprised even me. DISCLAIMERS I should mention that I took advantage of an upgrade pricing offer when I purchased\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":421,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/421\/","url_meta":{"origin":3037,"position":5},"title":"Raising The Dead","date":"25 Apr 2009","format":false,"excerpt":"What can possibly go wrong in the simple act of modifying the message of an exception to add some additional diagnostic information and then re-raising it? Quite a lot actually, and all from one simple mistake. Here's a simplified version of a fairly common construct that you will encounter in\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3037"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=3037"}],"version-history":[{"count":2,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3037\/revisions"}],"predecessor-version":[{"id":3043,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3037\/revisions\/3043"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=3037"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=3037"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=3037"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}