Thread overview | |||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
August 21, 2004 Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
struct Foo { int x() { return _x; } void x(int i) { _x = i; } int _x; invariant { assert (x != 13); // guard against bad luck } } int main() { Foo foo; foo.x = 999; int x = foo.x; printf("%i\n", x); return x; } Setting foo.x causes a stack overflow because Foo.x checks the invariant, which calls Foo.x() which itself cheks the invariant again, ad infinitum. This isn't really a bug per se, but I somehow doubt that anybody would ever expect or desire this behaviour. It would be nice if method calls didn't trigger invariant tests from within the invariant itself. If that's unfeasable, a good error message would be sufficient. I think it's pretty safe to say that this one is /way/ too subtle to leave as-is. :) -- andy |
August 21, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andy Friesen | In article <cg830p$289n$1@digitaldaemon.com>, Andy Friesen says... > > > struct Foo { > int x() { > return _x; > } > void x(int i) { > _x = i; > } > int _x; > > invariant { > assert (x != 13); // guard against bad luck > } > } > > int main() { > Foo foo; > foo.x = 999; > int x = foo.x; > printf("%i\n", x); > return x; > } > >Setting foo.x causes a stack overflow because Foo.x checks the invariant, which calls Foo.x() which itself cheks the invariant again, ad infinitum. > >This isn't really a bug per se, but I somehow doubt that anybody would ever expect or desire this behaviour. > >It would be nice if method calls didn't trigger invariant tests from within the invariant itself. If that's unfeasable, a good error message would be sufficient. > >I think it's pretty safe to say that this one is /way/ too subtle to leave as-is. :) > > -- andy There was a thread a while back - though possibly in the other forum - called something like "a temporary relaxation of the class invariant", in which I mentioned a similar problem. I had code which did something like this: # /* temporarily break invariant */ # _x = x; // invoke a getter function # /* restore invariant */ Walter's basic response at the time was "well don't do that then - access the variable directly". However, there are sometimes very good reasons why you might /want/ to abstract things away to a getter/setter function, and it /might/ do something a lot more complicated than just reference a variable, so you'd have to cut-and-paste and duplicate code all over the place. This is the kind of place where a #define would be handy - but D's answer to #define (the inline function) checks the damn invariant. I've suggested before that the class invariant should be suspended (that is, not checked) when a function is called from /within/ that class, and only invoked when a member function is called from outside. Sadly, the only realistic workaround we coders can use is to /not/ supply a class invariant in these circumstances. D wants to encourange DbC, but by being /too/ overzealous about checking, it can actually discourage it. I'd like to see this relaxed. Arcane Jill |
August 21, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andy Friesen | "Andy Friesen" <andy@ikagames.com> wrote in message news:cg830p$289n$1@digitaldaemon.com... > > struct Foo { > int x() { > return _x; > } > void x(int i) { > _x = i; > } > int _x; > > invariant { > assert (x != 13); // guard against bad luck > } > } > > int main() { > Foo foo; > foo.x = 999; > int x = foo.x; > printf("%i\n", x); > return x; > } > > Setting foo.x causes a stack overflow because Foo.x checks the invariant, which calls Foo.x() which itself cheks the invariant again, ad infinitum. > > This isn't really a bug per se, but I somehow doubt that anybody would ever expect or desire this behaviour. > > It would be nice if method calls didn't trigger invariant tests from within the invariant itself. If that's unfeasable, a good error message would be sufficient. > > I think it's pretty safe to say that this one is /way/ too subtle to leave as-is. :) I ran into this myself with the first version of std.recls. Can't remember OTTOMH how I got around it, but it was basically an avoidance thing, rather than anything particularly clever. |
August 21, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Arcane Jill | "Arcane Jill" <Arcane_member@pathlink.com> wrote in message news:cg89on$2bva$1@digitaldaemon.com... > In article <cg830p$289n$1@digitaldaemon.com>, Andy Friesen says... > > > > > > struct Foo { > > int x() { > > return _x; > > } > > void x(int i) { > > _x = i; > > } > > int _x; > > > > invariant { > > assert (x != 13); // guard against bad luck > > } > > } > > > > int main() { > > Foo foo; > > foo.x = 999; > > int x = foo.x; > > printf("%i\n", x); > > return x; > > } > > > >Setting foo.x causes a stack overflow because Foo.x checks the invariant, which calls Foo.x() which itself cheks the invariant again, ad infinitum. > > > >This isn't really a bug per se, but I somehow doubt that anybody would ever expect or desire this behaviour. > > > >It would be nice if method calls didn't trigger invariant tests from within the invariant itself. If that's unfeasable, a good error message would be sufficient. > > > >I think it's pretty safe to say that this one is /way/ too subtle to leave as-is. :) > > > > -- andy > > There was a thread a while back - though possibly in the other forum - called something like "a temporary relaxation of the class invariant", in which I mentioned a similar problem. I had code which did something like this: > > # /* temporarily break invariant */ > # _x = x; // invoke a getter function > # /* restore invariant */ > > Walter's basic response at the time was "well don't do that then - access the variable directly". However, there are sometimes very good reasons why you might /want/ to abstract things away to a getter/setter function, and it /might/ do something a lot more complicated than just reference a variable, so you'd have to cut-and-paste and duplicate code all over the place. This is the kind of place where a #define would be handy - but D's answer to #define (the inline function) checks the damn invariant. > > I've suggested before that the class invariant should be suspended (that is, not checked) when a function is called from /within/ that class, and only invoked when a member function is called from outside. > > Sadly, the only realistic workaround we coders can use is to /not/ supply a class invariant in these circumstances. D wants to encourange DbC, but by being /too/ overzealous about checking, it can actually discourage it. > > I'd like to see this relaxed. I'd like to see a reasoned treatment of the consequences of suspending invariant calls for all but the outermost instance method call. It sounds nice, and I have no immediate counter arguments, but I think it needs some thinking about. |
August 22, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Matthew | > I'd like to see a reasoned treatment of the consequences of suspending
> invariant calls for all but the outermost
> instance method call. It sounds nice, and I have no immediate counter
> arguments, but I think it needs some thinking
> about.
OOSC page 364 and onwards.
"In spite of its name, the invariant does not need to be satisfied at all times..."
"it is perfectly acceptable for a procedure g to begin by trying to work towards its goal -- its postcondition -- and in the process to destroy the invariant (as in human affairs, trying to do something useful may disrupt the established order of things); then it spends the second part of its execution trying to restore the invariant without loosing too much of whatever ground has been gained"
"Qualified calls of the form a.f ...), executed on behalf of a client, are the only ones that must allways start from a state satisfying the invariant and leave a state satisfying the invariant; there is no such rule for unqualified calls of the form f(...), which are not directly executed by clients but only serve as auxiliary tools for carrying out the needs of qualified calls."
Better quotes may be somewhwere, these are just some i dug out. The reasoning is as you can see quite simple. I think the relaxation is justified since its relatively easy for a class to maintain its invariant compared to the same class trying to maintain the invariant of other classes; at least the scope is easier to grasp. But that's just one way of looking at it....
|
August 23, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andy Friesen | "Andy Friesen" <andy@ikagames.com> wrote in message news:cg830p$289n$1@digitaldaemon.com... > Setting foo.x causes a stack overflow because Foo.x checks the invariant, which calls Foo.x() which itself cheks the invariant again, ad infinitum. Yes. Invariant checks are placed at the beginning and end of all public member functions. > This isn't really a bug per se, but I somehow doubt that anybody would ever expect or desire this behaviour. > > It would be nice if method calls didn't trigger invariant tests from within the invariant itself. If that's unfeasable, a good error message would be sufficient. Unfortunately, this is infeasible to implement. There's no way that the invariant compilation can check all possible paths that might result in a call to another public member function, which will cause the infinite recursion. The other way is for the invariant itself to see if it is nested inside a call to itself. This will require either walking the stack, or adding a flag to the object instance data. The former is expensive, and the latter is a problem since the instance layout is already fixed. > I think it's pretty safe to say that this one is /way/ too subtle to leave as-is. :) I'll have to document this better. |
August 23, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | On Sun, 22 Aug 2004 18:13:30 -0700, Walter <newshound@digitalmars.com> wrote: > "Andy Friesen" <andy@ikagames.com> wrote in message > news:cg830p$289n$1@digitaldaemon.com... >> Setting foo.x causes a stack overflow because Foo.x checks the >> invariant, which calls Foo.x() which itself cheks the invariant again, >> ad infinitum. > > Yes. Invariant checks are placed at the beginning and end of all public > member functions. > >> This isn't really a bug per se, but I somehow doubt that anybody would >> ever expect or desire this behaviour. >> >> It would be nice if method calls didn't trigger invariant tests from >> within the invariant itself. If that's unfeasable, a good error message >> would be sufficient. > > Unfortunately, this is infeasible to implement. There's no way that the > invariant compilation can check all possible paths that might result in a > call to another public member function, which will cause the infinite > recursion. The other way is for the invariant itself to see if it is nested > inside a call to itself. This will require either walking the stack, or > adding a flag to the object instance data. The former is expensive, and the > latter is a problem since the instance layout is already fixed. Can't you simply use a flag, i.e. (lines marked with // are compiler generated) class A { # bool insideInvariant; int _i; int i() { # if (!insideInvariant) invariant(); return _i; # if (!insideInvariant) invariant(); } invariant { # insideInvariant = true; assert(i != 13); # insideInvariant = false; } } (lines with # are compiler generated) or something? >> I think it's pretty safe to say that this one is /way/ too subtle to >> leave as-is. :) > > I'll have to document this better. Regan -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/ |
August 23, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | Walter wrote:
>>It would be nice if method calls didn't trigger invariant tests from
>>within the invariant itself. If that's unfeasable, a good error message
>>would be sufficient.
>
>
> Unfortunately, this is infeasible to implement. There's no way that the
> invariant compilation can check all possible paths that might result in a
> call to another public member function, which will cause the infinite
> recursion. The other way is for the invariant itself to see if it is nested
> inside a call to itself. This will require either walking the stack, or
> adding a flag to the object instance data. The former is expensive, and the
> latter is a problem since the instance layout is already fixed.
One thing I noticed is that invariant calls are handled by the caller, not the callee.
Would it be feasable for internal method calls to skip the invariant test? This would be reasonable behaviour in the majority of cases, I would think. In those cases when it isn't, an explicit "assert(this);" is easy enough to write.
-- andy
|
August 23, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andy Friesen | Andy Friesen wrote:
> One thing I noticed is that invariant calls are handled by the caller, not the callee.
>
> Would it be feasable for internal method calls to skip the invariant test? This would be reasonable behaviour in the majority of cases, I would think. In those cases when it isn't, an explicit "assert(this);" is easy enough to write.
errr... I noticed wrong. The invariant is called within the method, not by the caller.
Is it feasable to turn this around?
-- andy
|
August 23, 2004 Re: Nasty invariant bug | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andy Friesen | In article <cgbl12$1mf1$1@digitaldaemon.com>, Andy Friesen says... >errr... I noticed wrong. The invariant is called within the method, not by the caller. Is it feasable to turn this around? Complicated. Consider this: Suppose I release a library L, and someone else develops an application A which statically links with L. Let us suppose that L has been through its developmental phase, so now there's only a release build of it. A, on the other hand, is still being developed, and so is being compiled in debug build. So when A calls a function in L, I would /hope/ that the "in" preconditions of functions within L will still be tested. (Preconditions are there, after all, to help callers find bugs in /their/ code). This can only happen if preconditions are handled by the caller, not the callee. For class invariants, it should be allowed for the invariant to be temporarily broken when inside member functions, and therefore it should only be checked when called from outside. One way to achieve this is by placing responsibility on the callee. /However/, once the callee (the library L) goes into release build, the invariant should not be checked at all, regardless of the debug/release state of the caller (the application A). Think about it. One solution is to give two entry-points to each member function (but only in debug build) - one for use when the function is called from inside the class (i.e. for calls of the form f(...)) which does not call the invariant test, and one for functions called from outside the class (i.e. for calls on the form a.f(...)) which does. In a release build, those two entry points collapse to one. I think this could be done by giving each class a separate "vtbl for internal calls" in a debug build. Only for postconditions does it makes sense for the callee to perform the test regardless of whence the call came. Arcane Jill |
Copyright © 1999-2021 by the D Language Foundation