Visual C++ Compiler Fatal Error C1904

Message Text

bad provider interaction: file

Circumstances

To get an attribute handled, the compiler called an external attribute provider, i.e., not one of the two that are implemented within the compiler. An exception occurred during the call and the provider left the exception unhandled. The compiler has caught the exception and reported it as fatal error C1904. The file placeholder either names the provider as a ProgID or shows its CLSID.

As with other fatal errors that may originate as exceptions (such as C1001 and C1903), compiling with the /Bd option will typically let the exception pass from the compiler as unhandled. This applies especially to exceptions of the sort that are raised by the operating system in response to a CPU fault, as when a coding error in the attribute provider causes it to try dereferencing a bad pointer.

The product documentation’s sole comment on this fatal error is to advise reinstalling Visual C++ and possibly contacting Microsoft Product Support Services. In some sense, this is perfectly reasonable. Since Microsoft does not document how anyone external to Microsoft might write an attribute provider, occurrence of an exception in an attribute provider can only be an internal error of Microsoft’s.

This aside however, for a coding error in the provider to be described as a “bad provider interaction” seems the height of euphemism. Perhaps the author of this message text sincerely had in mind that an exception would occur only because the compiler and provider disagree on some detail of their interface. Perhaps the division of responsibility in the interface is supposed to include that the provider should see to its own exception handling for its own coding errors. Whatever the thinking, what Microsoft has ended up showing its customers is yet another instance of Microsoft’s readiness to disguise bugs in Microsoft’s software.

Example

As it happens, an internal error in an external attribute provider is known, and so an example can be given. The only external attribute provider that Microsoft supplies with Visual C++ is ATLPROV. There is a bug in the coding of this provider’s perf_object attribute, such that the provider can be induced to dereference a bad pointer. All that’s required is to contrive an ATL2102 error for a source file’s first use of perf_object. Compile

#define     _ATL_ATTRIBUTES
#include    <atlbase.h>
#include    <atlperf.h>

[
    perf_object (0)
]
class Test
{               // ATL2102 (C2338)
};              // C1904

To some extent, this example is unfair. The source code is incorrect for not presenting the expected pair of name_res and help_res arguments or of namestring and helpstring arguments. Fix the source code, and not only does the ATL2102 error go away, but so too does the exception that shows as fatal error C1904.

On the other hand, the example demonstrates well the inevitability of defects where a product is under-documented. The product documentation describes ATL2102 only by reciting the text of the error message. Had Microsoft bothered with even a simple example, which surely is a minimal requirement for a multi-billion dollar company to claim reasonable effort (whether at testing or documentation), then the coding error would have been exposed and, presumably, corrected.

Compiler Bug

A special case of attribute-provider exception exists such that the compiler attempts to raise fatal error C1904 but is thwarted by yet another exception. It gets only as far as asking for the ProgID to use as the file placeholder in the message text. When this goes wrong, the hapless programmer isn’t even told of a “bad provider interaction”, but sees instead

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

To appreciate this case, it helps to know that the ordinary progress of a fatal error is that the compiler (C1XX) displays an error message, cleans up its use of system resources such as files and memory, and then unwinds its stack by raising a custom exception (0xE0005031). This last operation enables the compiler to make an orderly return to the CL.EXE program, which called C1XX and which now exits cleanly. Note however that this technique for returning to CL requires that all the compiler’s exception handlers recognise the custom exception code and know not to depend on anything that the compiler has already cleaned up.

Now consider what happens when code injected by an attribute provider is defective, such that compiling it causes a fatal error. The compiler has called the attribute provider, which has called back into the compiler to deliver the injected code and get it compiled. On finding the fatal error in the injected code, the compiler raises the custom exception. Among the exception handlers that now get to execute (unless the /Bd option is active) is the one that the compiler set up to guard against exceptions occurring inside the call to the provider. Unfortunately, this exception handler knows nothing of the custom exception code and is unaware that the compiler has already released all but the most critical of its resources. In particular, the memory that held the provider’s CLSID, which the exception handler wants for the message text of fatal error C1904, is long gone. Giving its address to the OLE32 function ProgIDFromCLSID causes a CPU fault. As usual for CPU faults, the compiler seeks to handle the fault as a fatal error. Of course, a fatal error while still handling a fatal error is hardly something the compiler can proceed with as usual. The compiler instead exits by calling the CRT function abort, whence the message shown above.

It is ironic that this case does not succeed in getting described as a “bad provider interaction” even though it actually does require coding errors on both sides of the interface between compiler and provider. On the one side, the provider must inject code that is sufficiently defective to cause a fatal error when compiled. On the other side, the compiler has a bug in not anticipating that the exception from the call to the provider may be the compiler’s own fatal error.

Example

For an example of all this, compile the following (without /Bd):

#define     _WIN32_WINNT    0x0400
#define     _ATL_ATTRIBUTES
#include    <atlbase.h>
#include    <atlisapi.h>

[
    module (type = "service", name = "Test")    // errors plus fatal error C1004
];

ATLPROV assumes that when service is given as the type argument for the module attribute, then a non-empty resource_name argument is also given. When this assumption is not met, as in the example, ATLPROV raises no error or warning: it just injects bad code.