Is __finally supposed to run after EXCEPTION_CONTINUE_SEARCH?
Asked Answered
Q

1

8

In the following code, the function foo calls itself recursively once. The inner call causes an access violation to be raised. The outer call catches the exception.

#include <windows.h>
#include <stdio.h>

void foo(int cont)
{
    __try
    {
        __try
        {
            __try
            {
                if (!cont)
                    *(int *)0 = 0;
                foo(cont - 1);
            }
            __finally
            {
                printf("inner finally %d\n", cont);
            }
        }
        __except (!cont? EXCEPTION_CONTINUE_SEARCH: EXCEPTION_EXECUTE_HANDLER)
        {
            printf("except %d\n", cont);
        }
    }
    __finally
    {
        printf("outer finally %d\n", cont);
    }
}

int main()
{
    __try
    {
        foo(1);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        printf("main\n");
    }
    return 0;
}

The expected output here should be

inner finally 0
outer finally 0
inner finally 1
except 1
outer finally 1

However, outer finally 0 is conspicuously missing from the real output. Is this a bug or is there some detail I'm overlooking?

For completeness, happens with VS2015, compiling for x64. Surprisingly it doesn't happen on x86, leading me to believe that it is really a bug.

Q answered 27/9, 2015 at 8:11 Comment(16)
This might technically fall under the purview of undefined behavior, as you're assigning to a null pointer. Have you tried throwing a regular exception using RaiseException?Overspend
Well, not good. It is not a new issue, VS2013 behaves the same way. Looks like a structural limitation of /SAFESEH to me, specific to recursion, it works fine in a non-recursive case. Pretty doubtful anybody here can fix this problem, best to ping connect.microsoft.com about it.Appendectomy
@OmnipotentEntity: Assigning to a null pointer is undefined behavior, as far as the C++ Language Standard is concerned. On the Windows platform, however, this is well defined: The instruction raises an access violation, that is communicated to user code through an SEH exception.Ichthyosis
@Ichthyosis It's the compiler rather than the platform that makes this well defined.Amoebocyte
@DavidHeffernan: SEH is a system service on Windows, built into the OS. It's SEH, that makes the outcome of writing to a null pointer well defined. The compiler makes SEH accessible through special keywords, and implements stack unwinding semantics, if requested, but it's not the compiler, that turns undefined behavior into well defined behavior.Ichthyosis
@Ichthyosis No. It's the compiler that decides to emit the code. It could emit code to do something different on a null dereference. No compiler does.Amoebocyte
@DavidHeffernan: The compiler doesn't emit anything special. *(int*)0=0; produces the instruction mov dword ptr [0],0. SEH is a system service, and the OS makes the outcome well defined.Ichthyosis
@Ichthyosis The compiler vendor made a choice to emit that code. A compiler could exist that did something different.Amoebocyte
@DavidHeffernan: There's nothing compiler-specific under Exception Dispatching, because SEH is a system service. The document describes, what happens in case of a hardware or software exception. None of this requires compiler support. It's SEH, implemented in the system, that makes the outcome of writing to address 0 well defined. You need compiler (and CRT) support, if you decide to handle the exception.Ichthyosis
@Ichthyosis unless the compiler decides to emit code to do something different when dereferencing nullptrAmoebocyte
@DavidHeffernan: Stop turning around the question, until your wrong sounds right. SEH is implemented in the system. That's what I pointed out in my initial comment. SEH is, what turns the undefined behavior into well defined behavior. No compiler is involved. Please accept, that you can be wrong. There's nothing to be gained from this discussion anymore. I'm out.Ichthyosis
Guys, it doesn't matter, the behavior is exactly the same if I raise the exception with RaiseException.Q
@HansPassant, it turns out that there are sources for _C_specific_handler in the installation dir of vs2015. There's a piece of code that explicitely stops the unwinding once the "target handler" is found. However, this happens for all frames, not just the target frame. I'll go through connect, thanks.Q
I've submitted connect.microsoft.com/VisualStudio/feedback/details/1842143, let's see how long it will take MS to delete the report...Q
@Ichthyosis I accept that I can be wrong.Amoebocyte
Although not explicitly mentioned in the Visual Studio 2015 Update 3 release notes, the bug seems to have been fixed (possibly even with Update 2, but I couldn't check). You might want to write up an answer.Ichthyosis
P
1

exist and more simply example (we can remove inner try/finally block:

void foo(int cont)
{
    __try
    {
        __try
        {
            if (!cont) *(int *)0 = 0;
            foo(cont - 1);
        }
        __except (cont? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
        {
            printf("except %d\n", cont);
        }
    }
    __finally
    {
        printf("finally %d\n", cont);
    }
}

with output

except 1
finally 1

so finally 0 block not executed. but in not recursive case - no bug:

__try
{
    foo(0);
} 
__except(EXCEPTION_EXECUTE_HANDLER)
{
    printf("except\n");
}

output:

finally 0
except

this is bug in next function

EXCEPTION_DISPOSITION
__C_specific_handler (
    _In_ PEXCEPTION_RECORD ExceptionRecord,
    _In_ PVOID EstablisherFrame,
    _Inout_ PCONTEXT ContextRecord,
    _Inout_ PDISPATCHER_CONTEXT DispatcherContext
    );

old implementation of this function with bug here :

                    //
                    // try/except - exception filter (JumpTarget != 0).
                    // After the exception filter is called, the exception
                    // handler clause is executed by the call to unwind
                    // above. Having reached this point in the scan of the
                    // scope tables, any other termination handlers will
                    // be outside the scope of the try/except.
                    //

                    if (TargetPc == ScopeTable->ScopeRecord[Index].JumpTarget) { // bug
                        break;
                    }

if we have latest VC compiler/libraries installed, search for chandler.c (in my install in located at \VC\crt\src\amd64\chandler.c )

and in file can view now next code:

                if (TargetPc == ScopeTable->ScopeRecord[Index].JumpTarget
                    // Terminate only when we are at the Target frame;
                    // otherwise, continue search for outer finally:
                    && IS_TARGET_UNWIND(ExceptionRecord->ExceptionFlags)
                    ) {
                    break;
                }

so additional condition is added IS_TARGET_UNWIND(ExceptionRecord->ExceptionFlags) which fix this bug

__C_specific_handler implemented in different crt libraries (in some case with static link, in some case will be imported from vcruntime*.dll or msvcrt.dll (was forwarded to ntdll.dll)). also ntdll.dll export this function - however in latest win10 builds(14393) it still not fixed

Paget answered 17/12, 2016 at 21:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.