A basic question regarding Iocompletion

No exception handling was invented in the late 1960’s and the paper
showed the problems. It is interesting that when C++ was being
standardized that the restartable versus non-restartable exception wars
were still going on. I passed on to the committee a copy of the POPL
paper and that ended the push for restartable exceptions in C++.

Don Burn
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

“Maxim S. Shatskih” wrote in message
news:xxxxx@ntdev:

> > which points out a lot of problem. This was presented in the 70’s at
> > one of the first Principal of Programming Languages conferences.
>
> Stroustrup/Ellis wrote that C++ SEH was designed based on some work of 1980ies.
>
> So, SEH is as old as 70ies?
>
> –
> Maxim S. Shatskih
> Windows DDK MVP
> xxxxx@storagecraft.com
> http://www.storagecraft.com

See below…

> library writers; for example, should I do RaiseException or throw in my
> library?

No libraries in unmanaged languages (except those like STL or ATL which
are a set of sources rebuilt each time) should ever raise C++ exceptions.

It’s a more difficult call than this. I can use the same trick STL/ATL
use of putting too much in the .h file so things are rebuilt each time,
but it still isn’t pleasant.

Reason: C++ exception implementation depends not only on the compiler’s
brand, but on its version number and command-line parameters.

Actually, the good taste of programming in Windows is to only use a) C
exports b) COM as the library APIs. Both are well-defined and
language-independent.

COM comes with far too much baggage. Also, an MFC programmer gets a C++
interface to COM which does throws, and a plain interface to COM is just
far too ugly to use, whether in C or C++.

> do a throw, but as a general library writer, trying to support both
> languages, I’d be in trouble.

No troubles at all. Support C only, it will be automatically
C+±compatible.

> exceptions right is hard; Java is one of the few languages that actually
> got it right. Or at least righter than C++.

Correct.

Some additions:

  1. C# is also such, not only Java

C# doesn’t complain if you call a function that does a throw and you have
not specified a handler for it, and not included its exceptions in what
your function can (in effect) return. Java is far more uptight about
this, and I think this is good. If it compiles, I got everything right.

  1. C++ exceptions are just plain inadequate without RAII. And, usually,
    wrapping everything to RAII (including stuff like tmp memory buffers for
    IOCTLs and such, which are major fun for RAII) is considered to be much
    larger effort then just fixing bugs in the code which does “return
    NtStatus;” or “return hr;”.

Yep.

  1. MS’s C exceptions are a toy. SEH frames in the language which has no
    destructors is just a punk-style fun. With C SEH, the amount of error
    recovery code to be typed is the same as with “goto HandleError1; goto
    HandleError2;” code.

Actually, it is a bit worse than this in terms of code quantity, but I
think that exceptions provide better documentation as to what is really
going on. I’ve worked in three systems that used exceptions that look
like C SEH (Microsoft C being the fourth). I was able to use exceptions
to advantage in each one of them.

Actually, looks like MS’s SEH was developed with the only real practical
purpose in mind: to allow touching ->UserBuffer in drivers without making
a copy of it.

Yes, you need SEH-style stuff for METHOD_BUFFERED too, but it can be
hidden in the kernel and not published to the outside world to avoid
confusion :slight_smile: this is what Linux does with __copy_to_user() (Linux does
not support METHOD_NEITHER so it is safe).

Also one another drawback of SEH of any kind: in usual style, if I see the
call without the if( !NT_SUCCESS(…) ) wrapper, then I know that this
call has no-fail guarantee.

In Java, the no-fail guarantee is enforced by the compiler, which I think
is one of its winning (albeit sometimes painful) features.

With SEH, it is not so. The “throws” spec helps only a bit: first, its
implementation on C++ is pathetic. Second, you need to look at the
function declaration to see whether is has no-fail guarantee.

Yep. This is high on my list of defects.

The presense of try/catch and try/except operators just plain does not
reduce your effort of proper error handing
.

No. Sometimes it even makes it messier. But what it does is force you to
do error handling, not just write
DoSomething(…);
and get away with not checking the return value. Sure, lint and other
checking programs will complain, so you end up writing
(void)DoSomething(…);
but all this is just a copout to doing correct error handling.

> Note: COM interfaces in C++ throw exceptions

No COM interface can throw C++ exceptions. To begin with, the COM’s
remoting/marshalling layer cannot support them.

COM interfaces in MFC go through a layer that throws exceptions.

If the COM component throws across the interface boundary, then it is
broken.

There is ISupportErrorInfo/DISP_E_EXCEPTION OA exceptions, but they are
not C++ ones and require translation layer (there are macros in ATL for
this).

> Actually, free() CAN fail, but it does so catastrophically, making the
> handling of such an error next to impossible in C.

free() cannot fail. It just has no failure modes (C function, returns
“void”, has no callbacks) except crashing everything with impossible
recovery from it.

Well, I tend to call that “failure”. And if I’m trying to make something
bulletproof (I could even recover from parity errors, if they were
detected) I am not willing to accept anything that doesn’t return a value
but can take my app down.

Also note: recovering from the heap corruption is useless. The proper
thing is to fix the bugs which lead to heap corruption.

I agree. But in our case, the heap corruption occurred about once a day,
and nothing in the dumps allowed us to determine the cause. The heap was
definitely containing trash, but short of having our program fix the
hardware, I had no choice but to recover from it. Also, I want to make my
component independent of the other components that might be running, and I
want to be isolated from *their* bugs (ISAPI is one of the historical
interfaces where the confusion of heap corruption was not handled well;
ISAPI components should never have been allowed to share the general heap,
but there was no way to isolate the heap usage. Managed code, the current
approach, is far better, although it has its own problems).

> It is not clear why destruction calls should not be allowed to fail.

How will you recover from destructor failure? For instance, in C++ SEH, if
the RAII destructor raises itself, then abnormal_terminate() occurs. The
RAII/SEH machinery just cannot continue from such a failure.

I had to deal with destructor failure, when the heap was found to be
damaged. And yes, I was able to recover from it. So I don’t want some
other programmer telling me I can’t, because I already know that I can.

And this is correct. Destructors must not fail, this is the obvious law.

It is not at all obvious to me. A failing destructor may be indicative of
additional damage, and the recovery might be complex, but that decision
should not be made for me by someone else, who simply says “destructor
failure is fatal”.

> Any robust allocator is going to be checking for heap integrity on
> release

…if special debug mode is turned on (like what Verifier does) - then
yes.

There are many variants of this. Some, like debug mode, provide tons of
information about what might have gone wrong, where it went wrong, who
last allocated that block, etc. But I’ve written product (retail build)
allocators that did some fairly minimal error checking; for example, that
the “magic cookie” signature was in the block header, AND was at the end
of the block. Otherwise, we had an overwrite. And it is far more useful
to issue an error message in terms of the input instead of just failing
hard. Which makes more sense:

Starting compilation of module whatever.ext

000321 typedef enum {
…hundreds of symbols generated by a tool
004418 xyzzy,
***ERROR Internal error detected, heap corruption
***FATAL ERROR Compilation terminated

or

Your application has stopped. Do you want to send debug information to
Microsoft?

which would have been, in those days, done as

Starting compilation of module whatever.ext
FATAL ERROR A fatal internal error occurred

Actually, we discovered that somebody in our compiler group had
(erroneously) decided there could be no more than 4096 names in an enum
(equivalent; the language the compiler was processing wasn’t C, but the
idea is the same), but if you used 4097, instead of doing a bounds check,
it overwrote a heap block. Yes, it was a bug. Yes, we fixed it. The
fact that xyzzy appeared on line N, and the typedef appeared on line M,
and (N - M) > 4096, led us to the bug. The source code had been generated
by a YACC-like tool, so the symbols had silly names like
S_EXPR_PLUS
which were states (S_) derived from the grammar spec. Each non-identifier
terminal symbol had a transliteration, e.g., ‘+’ => _PLUS so you could
have operators like ‘+=’ which would generate a symbol S_PLUS_EQUAL, and
no, I not only had nothing to do with this, it was some third-party
product used by one of our customers.

The way I found the problem was to run in the “full debug” mode where a
heap integrity check was run on every allocation and deallocation request.
Performance hit? Well, I started it running, an hour later, I left for a
meeting, two hours later I had returned, it was still running, so I went
out to lunch, and when I came back it was still running, and about half an
hour after I returned, it hit. But we had useful data to start with, that
it only failed in long typedef-equivalents, so the source code was only
that typedef-equivalent, and none of us knew the (now former) programmer
had used a fixed-size table for enum names.

Production allocators should never do this due to very major perf hit.

No, it isn’t a “major” peformance hit; it involved comparing two integers.
It was cheap on 1980s mainframes, and it is essentially free on a
pipelined superscalar machine with speculative read. It was certainly not
our performance bottleneck, and we worked like crazy to get the compiler
to process 200 lines/min of source. Which made us faster [just barely]
than our competitors in that product domain, for the same language on the
same host. In fact, the allocator was not even visible in the performance
profiling; it was for all intents and purposes, “free”. We were working,
in those days, in a single-threaded environment, and we were considering
ways to make the compiler generate inline code to allocate, but when we
couldn’t even find the allocation cost, we dropped that optimization
effort. I labored long and hard to make that allocator have incredible
performance.

> integrity of the heap are important. How can you free something that is
> not a valid address?)

Crash the whole app and let the developer fix this bug.

See previous example. Identifying what was going on in the input is a BIG
help. Field support is a major cost center, and having the ability to
report bugs in terms of the customer problem space instead of the machine
implementation space is a major help!
joe


Maxim S. Shatskih
Windows DDK MVP
xxxxx@storagecraft.com
http://www.storagecraft.com


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

I started using an exception system designed by Jim Mitchell, in 1967. I
have no idea if his design was original or already well-understood. We
had the the notion of catch(), and I insisted on the equivalent of
catch(…). As far as I can recall, it was isomorphic to C++ in terms of
core structure (we didn’t have typed exceptions; it was implemented as a
set of macros in IBM 360 assembler, so it was more like C SEH in that
regard). When I saw some proposals for C++ SEH, I tried to get on the
committee to head off restartble exceptions, but then I saw Jim’s name on
the list and knew he would help kill that bad idea (by the time C++ SEH
was being designed, I’d already had experience with restartable
exceptions, in the DEC BLISS-10/16/32 compilers, and their “fix” for the
weaknesses of exceptions could have been a script for a horror movie. It
was unusable and resulted in incomprehensible and unmaintainable code. I
had also considerable experience using the Bliss-11 (which later became
Bliss-16) which used an SEH pretty isomorphic to the one in Microsoft C.

It is worth pointing out that Microsoft did not want this to be
“proprietary”, and submitted it to the C Standards Committee for
consideration. It was rejected simply because they had so many language
changes already submitted that they could not deal with one more
(according to a friend of mine, who spent many years on the committee
before he retired, there is always some nitwit who insists that “|a|”
replace “abs(a)”, and who cannot comprehend that such a fundamental syntax
change would break nearly every existing C program)
joe

No exception handling was invented in the late 1960’s and the paper
showed the problems. It is interesting that when C++ was being
standardized that the restartable versus non-restartable exception wars
were still going on. I passed on to the committee a copy of the POPL
paper and that ended the push for restartable exceptions in C++.

Don Burn
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

“Maxim S. Shatskih” wrote in message
> news:xxxxx@ntdev:
>
>> > which points out a lot of problem. This was presented in the 70’s at
>> > one of the first Principal of Programming Languages conferences.
>>
>> Stroustrup/Ellis wrote that C++ SEH was designed based on some work of
>> 1980ies.
>>
>> So, SEH is as old as 70ies?
>>
>> –
>> Maxim S. Shatskih
>> Windows DDK MVP
>> xxxxx@storagecraft.com
>> http://www.storagecraft.com
>
>
> —
> NTDEV is sponsored by OSR
>
> For our schedule of WDF, WDM, debugging and other seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at
> http://www.osronline.com/page.cfm?name=ListServer
>

One of the reasons we introduced exceptions as a syntactic entity in
Bliss-11 is that we had used the equivalent of setjmp/longjmp in Bliss-10,
and ended up with intermediate values caused by code motions and temporary
results in registers getting corrupted. The Bliss-11 SEH created
optimization fences against code motions, especially what where called
“hoisting” and “dropping”, which moved computations forward or backward in
the functions (we knew them as “Alpha” and “Omega” motions). Bliss-11 was
being used for product-level development by 1973. I don’t know when it
was designed, because I was in “thesis mode” at that time.
joe

SHE and code optimization has been known for a long time. Actually
there is a classic paper on code optimization in the face of exceptions
which points out a lot of problem. This was presented in the 70’s at
one of the first Principal of Programming Languages conferences. Of
course Microsoft C and C++ ignore the paper so if you ever use SEH do
not rely on values of variables that could be touched in the protected
code, in the exception handler or vice versa.

Don Burn
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

xxxxx@flounder.com” wrote in message
> news:xxxxx@ntdev:
>
>> There are all kinds of problems even with C SEH, such as responding to
>> hardware errors like access fault, divide by zero, etc. The major
>> problem
>> is that SEH will not do proper stack cleanup like C++ exceptions, so
>>
>>
>> try… C++ fn A —> C++ fn B –> C++ fn C; RaiseException;
>>
except(…)
>>
>>
>> will not call destructors for local variables declared in A, B and C,
>> but
>> will immediately transfer control to the matching __except. This means
>> that most common constructs in C++, such as std::vector (to name the
>> simplest) will not be cleaned up, and you will leak storage on every
>> RaiseException. In addition, if the destructors are involved in more
>> serious state maintenance, you how have problems with the integrity of
>> the
>> entire app.
>>
>> In the past, this was avoided in the kernel because there was no C++
>> code.
>> The fact that exceptions are (at least the last I heard) not supported
>> in
>> kernel C++ code is likely caused by the mix-and-match paradigm.
>>
>> Code optimization is another nightmare, and the notion of synchronous
>> vs.
>> asynchronous exception handling comes into play here. Doug Harrison
>> wrote
>> a detailed article on C/C++ exception handling, and I’d suggest googling
>> for it. Doug was for many years (and may still be, for all I know) a
>> C++
>> MVP.
>> joe
>>
>> > In my experience, the recommendation would be don’t mix exception
>> schemes
>> > in
>> > a single module. That way, each module looks after itself and the
>> > potential
>> > for bad interactions is minimized. MSDN has a fair description of the
>> > exception handling changes for x64, which implies the kind of
>> information
>> > that you want, but doesn’t spell it out. I don’t know of anywhere
>> else
>> > specifically
>> >
>> > “Pavel A” wrote in message news:xxxxx@ntdev…
>> >
>> > On 08-Aug-2012 10:07, Maxim S. Shatskih wrote:
>> >
>> >> MS’s C SEH is just another way of nesting “goto” operators. Nothing
>> >> else.
>> >> They do not improve anything, just replacing one ugly coding style
>> with
>> >> another :slight_smile:
>> >
>> > It is understandable that standard c++ libraries cannot rely on a
>> > proprietary MS technology on non-Windows platforms.
>> > Thus native c++ exceptions must use a technique independent from SEH
>> and
>> > Microsoft compilers that support it (even on Windows).
>> > Modern MSC++ compilers even forbid mixing SEH and C++ exceptions in
>> one
>> > function.
>> >
>> > So, does anyone know a good research, article, blog post that explains
>> > in sufficient depth how code written for C++ exceptions can be
>> combined
>> > with code that uses SEH, and what are recommended usage patterns?
>> >
>> > – pa
>> >
>> >
>> > —
>> > NTDEV is sponsored by OSR
>> >
>> > For our schedule of WDF, WDM, debugging and other seminars visit:
>> > http://www.osr.com/seminars
>> >
>> > To unsubscribe, visit the List Server section of OSR Online at
>> > http://www.osronline.com/page.cfm?name=ListServer
>> >
>
>
> —
> NTDEV is sponsored by OSR
>
> For our schedule of WDF, WDM, debugging and other seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at
> http://www.osronline.com/page.cfm?name=ListServer
>

> the functions (we knew them as “Alpha” and “Omega” motions). Bliss-11 was

being used for product-level development by 1973.

Is Bliss a DEC Algol-style or PL/1-style language, closely tied with VMS?


Maxim S. Shatskih
Windows DDK MVP
xxxxx@storagecraft.com
http://www.storagecraft.com

BLISS would be considered a C-like language, so both have roots back to
Algol, as does PL/1.
joe

> the functions (we knew them as “Alpha” and “Omega” motions). Bliss-11
> was
> being used for product-level development by 1973.

Is Bliss a DEC Algol-style or PL/1-style language, closely tied with VMS?


Maxim S. Shatskih
Windows DDK MVP
xxxxx@storagecraft.com
http://www.storagecraft.com


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

The reason to choose extreme cases is to make the point more clear - as has
been done with all logic since its inception; evaluate a precept by its
limit states or farthest consequences and one will know its merit.

With respect to you specific example where heap corruption was a recoverable
error, I submit that your control over the heap management routines was key
to this ‘soft restart’ being effective. In many systems, facilities exist
to do something similar, but in Windows the heap and pool are opaque to
applications and drivers and failures can’t be safely handled.

In my experience, the more important an activity was, the more it should be
isolated and the less it should rely on advanced services. When a CPU
executes an instruction, the result is a consequence of the physical
properties of the chip (or microcode) and barring errata or mechanical
failure, the effects of the execution are guaranteed by the same physical
properties. But when a program relies on services provided by a library,
API or DDI, then many additional failure modes are possible and more care is
needed to ensure that the critical component(s) are correctly coded

wrote in message news:xxxxx@ntdev…

It is far easier to adopt the absolute, and then discover where it doesn’t
apply, than to adopt the “It’s OK, sometimes” and leave it up to the
programmer to determine when the “sometimes” is valid, because under that
scenario, “sometimes” is “all the time”. Instead, if recovery is the only
acceptable solution, then you can find the rare exceptions more readily.

By the way, I was able to recover from heap corruption in my app. I did
the heap code, so the simple solution was to reset the heap to “fully
available” and then reconstruct the runtime structures from first
principles. This involved having a way to save the state so recovery was
possible. We never did find the source of the heap corruption, but the
belief was that we were having a double-bit-error in some early dynamic
memory which had only parity checking. So to me, I did not consider “heap
corruption” as a valid shutdown criterion.

It is essential to adopt the most robust possible solution, not the
easiest solution. And every case I’ve seen of exit(1) and all its guises
was always “it’s too hard to do recovery” when, in fact, it wasn’t all
that hard to get it right. I know this, because I fixed it in a small
number of hours’ effort.

So yes, I think in absolutes; I want the programmers to THINK before they
cause the program to exit, and realize that robustness is an important
parameter in many pieces of software, such as device drivers, realtime
data acquisition systems, editing tools, and the like.

The outcome was that I got really robust software from my team, because
after the first few times I proved to them that recovery was possible, and
wasn’t all that hard to get right (and we didn’t even have C++
exceptions!) then they really tried to make the software work. And in my
consulting, I regularly fixed robustness problems simply by removing all
exit() calls (and their various aliases) and putting proper recovery code
in.

So you have picked the extreme cases, where exiting just might be
justifiable, but the critical thing to remember is that termination is the
ABSOLUTE LAST RESORT, and everything possible must be done to avoid it.

I’m certainly not willing to accept heap damage in an app as being
sufficient criterion for termination; the first thing I’d do in an app
that had “mission critical” paths is to make sure that the heap would not
be a problem. And yes, this means a lot of std:: won’t work, and that’s
why it requires actual WORK to make the code robust. If you’ve ever lost
three hours’ work because the app or system crashes, you have a metric of
the pain that has to be dealt with (and yes, if you have termination, it
is critical that it not lose data, and techniques such as checkpointing
become important). And the users aren’t going to ask “how much would it
cost to improve the robustness of this app”; they are going to say “Fix
the damned thing or I look at the competition”. Or say “Forcing me to pay
for an upgrade so I get bug fixes for all the bugs you left in the first
version is extortion” and your product’s reputation suffers.
joe

I am not sure which post this is exactly in response to, but I continue to
object to your use of absolute terms here.

Did you read my example? It is not a situation where a specific function,
library, module or has failed because of a data inconsistency, but a
situation where operating environment of the whole program has been
corrupted. Depending on how bad the damage is, it may not be possible to
detect this at all, but if it is detected, what should a responsible
programmer do? Surely the answer isn’t “let’s ignore the problem and
continue execution so we can corrupt yet more stuff”. The environment may
be
so corrupted that even trying to terminate may fail and corrupt stuff
(i.e.
execute garbage instead of the expected terminate code), but there isn’t
much that can be done about that except hope that the next fault will be
detected by a lower level that can terminate.

I am not suggesting that this should be the normal method of handling all
errors, indeed far from it, but I am challenging your absolute statement
that there is never a situation where termination is correct. I have given
a
few examples of situations in Windows software where I believe that
termination is the correct response, and unless you can provide specific
alternatives for those situations, or explain how they are impossible in
correctly designed applications, I can’t see that an absolute
prohibition is
reasonable. Again, some examples from UM programming on Windows

  • Failure of RevertToSelf
  • Failure of HeapUnlock on the process heap
  • Failure of HeapFree *
  • Stack overflow exception in exception handler

The only causes for RevertToSelf failure are memory corruption or
mismatched
calls. In either case the thread may be executing as a security principle
other than the main one for the process, so any further execution, even
raising an exception or returning failure is a security risk

The only causes for HeapUnlock failure are memory corruption of mismatched
calls. The default process heap is used by many components in Windows
applications. Even when one’s code takes special care to avoid using the
default process heap, threads injected into the process by Windows or
other
libraries still use that heap. If the application performs some memory
analysis, for the purpose of updating a performance counter let’s say,
that
requires locking the process heaps and the call to HeapUnlock fails, then
the only courses for execution to take are deadlock or arbitrary memory
corruption because either the lock itself is hosed or the data it guards
is.

HeapFree has an asterisks because this one can fail for more reasons and
often it doesn’t fail but internally calls LogHeapFailure ->
RtlReportCriticalFailure before any failure code would be executed

Stack overflow exceptions are hard to handle under the best of
circumstances. It is not too bad if one can assume that the exception will
only be raised from a function prologue, but _alloca, assembly functions
and
optimized code can result in stack expansion at arbitrary points in the
instruction stream and writing an effective handler is only made possible
by
the API SetThreadStackGuarantee, but if the size is miscalculated and a
stack overflow exception is raised while handling an exception, then the
thread is in a non-continueable state and it is impossible to execute any
unwind handlers or other failure code. And because the thread was
executing
arbitrary code before the initial exception and within the exception
handler, it is impossible to determine if it is safe to simply spin or
kill
this thread because there is no information on what locks may be held or
what state any data structures may be in
Again, the purpose of pointing out these abstruse cases is to make you
modify your statement from an absolute prohibition into the qualified
‘don’t
do that because it is a really bad idea except in rare and obscure cases’

wrote in message news:xxxxx@ntdev…

The “blinders” approach is unacceptable. This is the belief that because
YOUR code is confused, ALL code is confused, and therefore it is
legitimate to exit the application. I have found this to be, without
exception, a colossal blunder. So the database index is screwed up. Stop
operations on that database. But the realtime data acquisition thread is
still happy, and if you exit the app, you lose live data. So it makes
ZERO sense to terminate the application. Even if attempts to use the
database could corrupt the actual database, then stop using the database.
But the realtime data collection thread, which is NOT using the database,
is supercritical, and if someone writing the database library thinks that
exiting is correct, they are so completely out of touch with reality that
they might as well be on drugs.

And talking about the difference between external APIs and internal
functions is meaningless. If my API calls a complex set of internal
functions, any failure, for whatever reason, must reflect out to the API.
Even if the error code is “fatal data consistency error”. But existing is
NOT an option. I’ve fought these amateur libraries for years, and I have
yet to find a place where simply reflecting the error condition back to
the caller would have been the wrong decision.

So, if I were working in C, I’d implement FailedAssertion to include
RaiseException, or in C++ I’d use a throw. Note that it becomes the
responsibility of intermediate layers to guarantee that in the presence of
exceptions that their invariants are preserved, making it hard to
mix-and-match C and C++ universes.

But having spent years fighting these poor decisions, and an entire year
implementing an exception-based completely bulletproof software component,
I simply cannot imagine any situation in which termination makes sense for
an app. OS inconsistencies which are indicative of OS data structure
corruption is a different situation, but I’d rather have “file system is
wonky” come back to my app as “unrecoverable internal error”, because the
file system in which I am logging realtime data might not be the same file
system, or device, on which the user is doing something else, such as data
mining of previuosly-existing data.
joe

> Scott Noon wrote:
>
>> For me, ASSERTs are great provided that they’re used as they are
>> intended to be used. They can make the assumed environment of a
>> particular routine explicit to future maintainers of the code
>> (including the original author), which makes things that much more
>> future proof.
>
> I completely agree and I will go a little further. An ASSERT does two
> things:
>
> 1) As Scott stated, an ASSERT helps document (in code) invariants
> for a function (or macro).
>
> 2) In addition, an ASSERT can prevent further execution (by exiting or
> bugchecking) when the invariant is violated. This is important
> because, if the invariant is violated, you may have no guarantees
> how any further code execution will behave (both inside and after
> you exit the function in question). Thus exiting/bugchecking in
> an ASSERT macro can be very important to prevent data
> loss/corruption.
>
> You can also have a couple of different ASSERT macros so that you can
> control whether to exit/bugcheck depending on how bad the invariant
> violation is. For example, you might have an ASSERT that only
> exits/bugchecks on a debug/checked build and a RETAIL_ASSERT that always
> exits/bugchecks because further execution could lead to data
> loss/corruption
> and you want to prevent that even on a retail build (in case some made a
> mistake).
>
> Now, this all being said, ASSERTs should never be used to do validation
> of
> inputs that are not invariant checks. For instance, if I have a
> top-level
> API that I expose externally, I should return STATUS_INVALID_PARAMETER
> (or
> equivalent in another error space) on bad input. However, if I have an
> internal function where I am in complete control of how it gets called,
> an
> ASSERT on bad internal input is appropriate as it would be a bug in *my*
> code.
>
> IIRC, this is generally how I code ASSERT/RETAIL_ASSERT macros:
>
> #if … // debug or checked build
> #define ASSERT(Expression) \
> ((Expression) ? 1 : \
> AssertFailed(#Expression, FUNCTION, FILE, LINE))
> #elif … // retail build w/logging for failed asserts
> #define ASSERT(Expression) \
> ((Expression) ? 1 : \
> LogAssertFailed(#Expression, FUNCTION, FILE, LINE))
> #else // this a retail build where I do not want any overhead
> #define ASSERT(Expression) 1
> #endif
>
> // RETAIL_ASSERT is always on.
> #define RETAIL_ASSERT(Expression) \
> ((Expression) ? 1 : \
> AssertFailed(#Expression, FUNCTION, FILE, LINE))
>
> where:
> AssertFailed logs the failure, stops execution, and returns 0
> LogAssertFailed logs the failure and returns 0
>
> Then you can do stuff like:
>
> if (!ASSERT(Expression))
> {
> // We use the ASSERT macro here because this invariant violation
> // is not too bad (will not corrupt data) and we can return
> // a reasonable error from this function (due to how this
> // function interface is specified). In a retail build w/o
> // ASSERT logging, the ASSERT macro is a no-op and this code
> // block is never executed. The code block can only be executed
> // in debug/checked builds or in retail builds with ASSERT
> // logging enabled.
> …
> }
>
> - Danilo
>
>
>
> —
> NTDEV is sponsored by OSR
>
> For our schedule of WDF, WDM, debugging and other seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at
> http://www.osronline.com/page.cfm?name=ListServer
>


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer