From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: fac41,a48e5b99425d742a X-Google-Attributes: gidfac41,public X-Google-Thread: f43e6,a48e5b99425d742a X-Google-Attributes: gidf43e6,public X-Google-Thread: ffc1e,a48e5b99425d742a X-Google-Attributes: gidffc1e,public X-Google-Thread: 103376,a48e5b99425d742a X-Google-Attributes: gid103376,public X-Google-Thread: 1108a1,5da92b52f6784b63 X-Google-Attributes: gid1108a1,public From: wolfgang@uran.informatik.uni-bonn.de (Wolfgang Reddig) Subject: Simulating Eiffel-style assertions (was: Papers on the Ariane-5 crash and Design by Contract) Date: 1997/03/24 Message-ID: <5h6jc0$jvu@news.rhrz.uni-bonn.de> X-Deja-AN: 228025047 References: Organization: Computer Science Bonn, Germany Newsgroups: comp.lang.eiffel,comp.object,comp.software-eng,comp.programming.threads,comp.lang.ada Date: 1997-03-24T00:00:00+00:00 List-Id: [request for Eiffel-stype assertion macros deleted] > > >Steve, > >Our techniques are not original, so don't blame me for inventing all of >the following. > >We actually used REQUIRE and ASSERT, not REQUIRE, ENSURE and INVARIANT. >REQUIRE usage was to detect fatal errors like NULL pointers or incorrect >run-time type. ASSERT usage was to detect non-fatal runtime errors. Our >reason for distinguishing is that we could optionally retire ASSERT and >switch REQUIRE from a program break to an exception or user error alert. >This distinction also added extra information for the code reader - a >trivial advantage over Eiffel. > >You can define the macros like this (slightly modified from our versions): > >#define ASSERT(identifier, expression) \ > if (!(expression)) \ > (AssertionFailed(__FILE__, __LINE__, false, \ > #identifier, #expression)); \ > >#define REQUIRE(identifier, expression) \ > if (!(expression)) \ > (AssertionFailed(__FILE__, __LINE__, true, \ > #identifier, #expression)); \ > >#define EXIT CheckInvariants(); \ > >And the function that does the work could be like this: > >void AssertionFailed(char *file, int line, Boolean fatal, > char *identifier, char *expression); > >Missing from the above illustration are compiler switches to switch >assertions on and off (and into intermediate states). In order to reduce >code size (methods often have more assertions than behavior!) you may want >alternative definitions that will not generate any code, for example: > >#define ASSERT(ignore) ((void) 0) > >AssertionFailed can write to a log file, or to the debugger (using >DebugStr), and/or if fatal can throw an exception or pose a user alert. >The alert would display the file name and line number of the require along >with instructions to report the error to the developer, and allowed the >user to quit (better than crashing the system). I leave it to you to draft >your own version. > >I think the idea of using separate macros ENSURE and INVARIANT is a good >idea - the debug message could express what kind of check is being >enforced along with the identifier and expression text. Modify the >assertion procedure to accept and express the type of assertion: > >enum EAssertionType {eRequire, eEnsure, eInvariant, eCheck}; > >#define REQUIRE(identifier, expression) \ > if (!(expression)) \ > (AssertionFailed(__FILE__, __LINE__, eRequire, \ > #identifier, #expression)); \ > >#define ENSURE(identifier, expression) \ > if (!(expression)) \ > (AssertionFailed(__FILE__, __LINE__, eEnsure, \ > #identifier, #expression)); \ > >void AssertionFailed(char *file, int line, > EAssertionType assertionType, > char *identifier, char *expression); > >You may not find it necessary to override assertions (other than adding >additional ones in subclass methods), but if you really want to follow >through on that part of my suggestion, you need to > >1) on startup of each process (normally just the application) create a >stack of global scope to the process >2) save a local "frame pointer" in ENTER (the macro would declare an index >and assign it the size of the stack) >3) check each assertion to see if the identifier is already in the stack >and, if so, ignore the local assertion >4) otherwise push the identifier name for the assertion onto the stack and >invoke the assertion >5) clear the stack down to the local frame pointer in EXIT (the macro >would set the size of the stack to the saved index). > >For example: > >#define ENTER \ > long assertionStackSize = globalAssertionStack->GetSize(); \ > >#define EXIT \ > CheckInvariants(); \ > globalAssertionStack->SetSize(assertionStackSize); \ > >#define REQUIRE(identifier, expression) \ > if (!globalAssertionStack->Contains(identifier) \ > { \ > globalAssertionStack->Push(identifier); \ > if (!(expression)) \ > (AssertionFailed(__FILE__, __LINE__, whatever, \ > #identifier, #expression)); \ > } \ > >You can either put the identifier's text on the stack, or else enumerate >all your assertion identifiers and place the enumerator on the stack (in >which case the AssertionFailed parameter lists I suggested would need to >be modified). > >In general, the process of writing code using these assertions is try to >state every assumption being made when you write methods and classes, in >such a way that the reader can immediately see what assumptions you are >making, and thereby get a good idea of how you expect the method or class >to be used, and ofcourse also enforce these assumptions when the >assertions are turned on. That is "design by contract" in fifty words, >more or less. > >NOTA BENE: in my example there is a danger of missing the ENSURE and >INVARIANT if the method has multiple returns. I can think of several >rather messy ways to solve this. Or you could just avoid multiple exits >when necessary, which may be a better programming style anyway. > >Please feel free to contact me at my email address if you have any more >questions. > >Anders. > >-- >Anders Pytte Milkweed Software >RR 1, Box 227 Voice: (802) 472-5142 >Cabot VT 05647 Internet: milkweed@plainfield.bypass.com But these macros dont cover some of the most important aspects of Eiffel-style assertions. In Eiffel, require, ensure and the class invariants are only triggered for "remote-calls" (if I remember well that's what B. Meyer calls it). Methods that directly or indirectly call methods with current (this, self, ...) as the receiver wont re-trigger any assertions. This is important because it 1) prevents endless loops caused by assertions and 2) allows objects to be inconsistent between entrance end exit of a "remote call". Consider a 'Car' class having an invariant which states that cars have either two or four doors. Lets assume there is a method 'make_limousine', which adds two doors (forgive the C++ notation, but I'm not very familiar with the syntax of Eiffel): void Car::make_limousine() { require(no_of_doors() == 2) ensure(no_of_doors() == 4) add_door(); // this would (incorrectly) cause an invariant failure! add_door(); } In short, C/C++ asserts do not only lack inheritance of assertions, they also dont support this kind of transaction-like concept. In fact, you can simulate much of Eiffel-style assertions in C++, but it requires a little more work in order to be (nearly) as powerful as Eiffel. For example, the 'require' macro may declare a local object whose constructor increments a counter in the object. If the counter is equal to 1, then check the expression passed to 'require' etc. The destructor should of course decrement the counter and can automatically trigger the invariant if the counter equals 1. Regards, Wolfgang +---------------------------------------------------------------------+ | Wolfgang Reddig | e-mail: | | Institut fuer Informatik III | wolfgang@informatik.uni-bonn.de | | Roemerstrasse 164 | | | 53117 Bonn | Tel.: (49) 228 / 73-4513 | | Germany | Fax: (49) 228 / 73-4382 | +---------------------------------------------------------------------+