August 08, 2007
Sean Kelly wrote:
> I assume the compiler would not throw an exception for the following case?
> 
> class D : B
> {
>     long square;
>     alias B.set set;
>     void set(long i) { B.set(i); square = x * x; }
>     long squareIt() { return square; }
> }

Nope, no exception thrown.
August 08, 2007
Manfred Nowak wrote:
> Walter Bright wrote
>> closes all the known hijacking issues.
> 
> If accessing data declared private in another not imported module can be called hijacking there is at least one more issue:
> 
> 
> Defining a Base class:
> 
> module def;
> class Base{int i;}
> 
> 
> and defining a Derived class with some operations and some private data `hidden':
> 
> module def2;
> import std.stdio;
> import def;
> class Derived:Base{
>   private:
>   int hidden=41;  // foreign access possible
>   public:
>   void process( inout Base p){
>     scope d= new Derived;
>     d.hidden= 666;
>     d.i+=1;
>     p= d; // no upcast needed
>   }
>   void read( Base p){
>     scope d= cast(Derived)p;  //downcast needed
>     assert( d !is null);
>     writefln( "def2:", d.hidden);
>   }
> }
> 
> 
> and having a module that does some work:
> 
> module mod;
> private import std.stdio, def, def2;
> public:
> void process( inout Base p){
>   scope d= new Derived;
>   d.process= p; // stores Base, drops Derived
>   // do something with d?
> }
> void read( Base p){
>   scope d= new Derived;
>   d.read= p; // drops Derived
>   // do something with d?
> }
> 
> 
> 
> the following claim is currently (dmd.1.016,win32) true::
> 
> ! One can access the `private' data `hidden' defined in `module def2'
> ! by having access only to sources of `module def' and `module mod'.  
> 
> 
> This are about 25 LOC and if D is indeed designed to support auditing well, it should be easy to spot that leak.

mod does not access hidden, neither does def. I don't understand the issue here.
August 08, 2007
Steven Schveighoffer wrote:
> I still would like a real example of how this is desirable.

When author A wants to extend the API of A without silently breaking derived B of which author A has no knowledge of.

>>> If the author really didn't want to expose "foo(char[])" then why were they deriving their class from A?  It goes against the whole idea of inheritance, doesn't it?
>> The problem is when the base class implementor wants to add some functionality (or specialization) with a new overload. A's implementor may be a third party, and has no idea about or control over B. His hands shouldn't be tied.
> 
> This argument is absurd.  How is the author of A's hands tied?  If author A changes his API, author B had better take notice.

The problem is, author A adds to (not changes) his API, and B starts silently failing. No compile error, no runtime error.

> What if author A decides to change the way he stores protected data?  How can you prevent author B from having to understand that?

Author A should be able to *add* methods and fields to his API without possibly causing silent breakage of derived classes. It is ok to cause them to break with a compile or runtime error, but not ok to silently break them.

If author A *changes* his API, that's a different thing entirely.

> Even with your exception solution, A can break code which uses instances of B by adding an overload.

It won't be silent breakage, though. The evil is in the *silent* breakage. The author A shouldn't have his hands tied preventing him from adding to his API out of fear of silently breaking his customers' code.
August 08, 2007
"Walter Bright" <newshound1@digitalmars.com> wrote in message news:f9bej0$21vn$1@digitalmars.com...
> Steven Schveighoffer wrote:
>> I still would like a real example of how this is desirable.
>
> When author A wants to extend the API of A without silently breaking derived B of which author A has no knowledge of.

I meant a piece of code that will not cause compiler error.  I believe in the case that A adds an overload that B does not override, the compiler will error when author B tries to compile his code, will it not?  Is this not a loud error?  I'm assuming the author of B must recompile his code when A is updated.

I realize now that we may be arguing the same point.  I believe my original solution may not be feasible to imlpement, so I have fallen back on my alternate solution.  I'll formally state it here:

If there is a class A, which is a base class of class B, where A defines a method foo(args),  and B does not define foo(args), then the compiler should fail to compile B indicating that the user must define B.foo(args) by override or by alias.

>> This argument is absurd.  How is the author of A's hands tied?  If author A changes his API, author B had better take notice.
>
> The problem is, author A adds to (not changes) his API, and B starts silently failing. No compile error, no runtime error.
>

I belive that my new solution would produce a compile error upon compiling B.

Even in my original solution, you should see no error, but if the arguments of the new method are not implicitly convertable to any of B's methods, then code which uses B would not accidentally call the new method anyways.  If they were implicitly convertable, the compiler would error.

>> What if author A decides to change the way he stores protected data?  How can you prevent author B from having to understand that?
>
> Author A should be able to *add* methods and fields to his API without possibly causing silent breakage of derived classes. It is ok to cause them to break with a compile or runtime error, but not ok to silently break them.
>

Your definition of silent error is not the same as mine.  If author B does not care about new APIs, he could release his code without testing the new method, and without finding out it throws an exception.  This means B can compile without the author knowing that this bomb is sitting in the new method.  He then can release his code unknowing that it will fail when someone tries to call the new method on his class.  To me, it is a silent error to the author of B, maybe not as "evil" as code hijacking, but still silent.

> If author A *changes* his API, that's a different thing entirely.
>

If author A adds any methods to his class, it changes the API.  Imagine if A added a method with a new name that B did not have an overload for.  This is augmenting the class, but will not force an exception because B does not define an override of that method.  What if this method "breaks" the class as you have alluded to (but still have not given an example)?  B is now forced to take into account this new method.  By your argument A's hands are still tied even with your solution.

-Steve


August 08, 2007
Walter Bright wrote in reply to Regan Heath:

[ reciting omitted quote!
>> you're already making an assumption, you're assuming the author of B does not want to expose foo(char[]) and it's the fact that this assumption is wrong that has caused this entire debate.
]
>> As others have mentioned, this assumption destroys the "is-a" relationship of inheritance because "foo(char[])" is a method of A but not a method of B.
> 
> We should not take rules as absolutes when they don't give us desirable behavior.
[...]
>> If the author really didn't want to expose "foo(char[])" then why were they deriving their class from A?  It goes against the whole idea of inheritance, doesn't it?
>
> The problem is when the base class implementor wants to add some functionality (or specialization) with a new overload. A's implementor may be a third party, and has no idea about or control over B. His hands shouldn't be tied.


and in reply to Steven Schveighoffer:

> The problem is, author A adds to (not changes) his API, and B
> starts silently failing.
[...]
> If author A *changes* his API, that's a different thing entirely.


Please observe that your arguments are showing kind of ambivalence.

You want
1) that inheritance gives authors "desirable behaviour".
2) that authors needn't have "ideas about or control over" derived
:  classes
3) that authors are allowed to add to their API
4) that changing an API is entirely different, where changing is
:  anything but adding


Now assume, that I am author of class C deriving from class B.

According to wish 2) I need not have "ideas about or control over" classes D, that derive from my class C.

According to wishes 1) and 3) the author of B as well as me can carelessly "add" to the respective APIs, but according to wish 4) are not allowed to "change" those APIs.


But there is no other way than to change the API of B, if there is any
kind of conflict between the APIs of A and B. This forces me to change
the API of my class C. This forces the author of class D ... resulting
in a virtual infinite recurrence.


Conclusions:
1) I do not believe that in real scenarios virtual infinite recurrences
:  are considered as "desirable behaviour" according to wish 1).
2) For escaping from this vicious circle use _composition_.
3) This reestablishes the "is-a" relationship of inheritance.

-manfred
August 09, 2007
Steven Schveighoffer wrote:
> If there is a class A, which is a base class of class B, where A defines a method foo(args),  and B does not define foo(args), then the compiler should fail to compile B indicating that the user must define B.foo(args) by override or by alias.

And if you don't want to provide access to those overloads? If, for instance, the new overload computes something that was previously passed in as an argument, but due to your changes, you cannot compute that argument. With your solution, I'd have to override that method and have it throw an exception. And put it in the docs that the method shouldn't be used.

How about having a strong and weak override? A strong override hides all overloads of the function it overrides, while a weak one hides only the overload that matches it. Then it's unambiguous, and only a matter of defining syntax. After all, Walter doesn't implicitly know whether each individual programmer wants to allow inherited overloads to work, and neither does dmd, unless they're explicitly told.
August 09, 2007
Walter Bright wrote:
> Regan Heath wrote:
> 
> There is no runtime check or cost for it. The compiler just inserts a call to a library support routine in D's vtbl[] entry for B.set(int).
> 
>> Is this done at runtime instead of compile time because the parameters cannot always be determined at compile time?
> 
> Yes.
> 

Huh? I don't understand this, how come it's not possible to detect it at compile time? Doesn't the full signature of all methods from both parent and child class have to be know at compile time?

-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
August 09, 2007
Bruno Medeiros wrote:
> Walter Bright wrote:
>> Regan Heath wrote:
>>
>> There is no runtime check or cost for it. The compiler just inserts a call to a library support routine in D's vtbl[] entry for B.set(int).
>>
>>> Is this done at runtime instead of compile time because the parameters cannot always be determined at compile time?
>>
>> Yes.
>>
> 
> Huh? I don't understand this, how come it's not possible to detect it at compile time? Doesn't the full signature of all methods from both parent and child class have to be know at compile time?

The feature(1) would be implemented at the call site.  In this example:

class B
{    long x;
     void set(long i) { x = i; }
    void set(int i) { x = i; }
    long squareIt() { return x * x; }
}
class D : B
{
    long square;
    void set(long i) { B.set(i); square = x * x; }
    long squareIt() { return square; }
}
long foo(B b)
{
    b.set(3);
    return b.squareIt();
}

When compiling b.set(3) the compiler determines the type of '3' and performs it's checking based on that type.

IANACW (I am not a compiler writer) so I can't think of a case but Walters reply seems to indicate that there are cases where the type of the parameter cannot be determined at compile time.

Regan

(1) The feature being the one described by Steven where the compiler searches all base classes of D for any overload of 'set' not implemented in D accepting 'int' or 'int' by implicit conversion and gives an error.
August 09, 2007
"Christopher Wright" <dhasenan@gmail.com> wrote in message news:f9evna$284k$1@digitalmars.com...
> Steven Schveighoffer wrote:
>> If there is a class A, which is a base class of class B, where A defines a method foo(args),  and B does not define foo(args), then the compiler should fail to compile B indicating that the user must define B.foo(args) by override or by alias.
>
> And if you don't want to provide access to those overloads? If, for instance, the new overload computes something that was previously passed in as an argument, but due to your changes, you cannot compute that argument. With your solution, I'd have to override that method and have it throw an exception. And put it in the docs that the method shouldn't be used.

You can't "hide" it, it can be called by casting to the base class.  I see no issue with you having to override the method and throw an exception, why is this solution not good enough?  It is the default solution that Walter is trying to promote.  However, in my opinion, I find it unlikely that an author would want this behavior, so it should not be the default.  I would have to see an example to understand it more, but I think I'd probably suggest an alternative to overriding and throwing an exception (such as not deriving but defining a new class instead).  Can you give me an actual example?  I still have never seen one.

>
> How about having a strong and weak override? A strong override hides all overloads of the function it overrides, while a weak one hides only the overload that matches it. Then it's unambiguous, and only a matter of defining syntax. After all, Walter doesn't implicitly know whether each individual programmer wants to allow inherited overloads to work, and neither does dmd, unless they're explicitly told.

As I said, you can't hide the override, but as a compromise, what if there were a way you could alias any undefined overrides to throw exceptions instead of call the base class?  Something like:

alias not_implemented foo;

This saves 1) having to code a silly override for each one you wish to "hide", and 2) having multiple copies of identical code which simply throws an exception compiled into your class.

I tried doing this with the current compiler by defining a function:

void not_implemented(...)

But the problem is that it allows one to compile code which calls overrides that didn't exist in a base class :)  I think the compiler would have to handle this case in a special way.  You would probably want the exception to say something like "class.foo(arg type) not defined," not sure how one would do this without adding a feature to the compiler.

-Steve


August 10, 2007
Walter Bright wrote

> mod does not access hidden, neither does def. I don't understand the issue here.

Not seeing the issue is the issue: D defies code inspection.

Although even you are not seeing the leak, one can at least read the value of `hidden', by adding 5 LOC into some `module hijack;'---which _is_ a leak according to my definition of black hat acting.

If it is okay for your intentions with D that black hats are able to read private data, then of course there is no issue zhat needs to be understood.

-manfred