Preventing third-party derivation, part one

Preventing third-party derivation, part one

Rate This
  • Comments 27

In this episode of FAIC, a conversation I had with a C# user recently. Next time, some further thoughts on how to use the CLR security system in this sort of scenario.

Him: I have this abstract class defined in one assembly:

// Assembly FooBar.DLL
public abstract class Foo
{
    internal abstract void DoSomething();
// ...
}

I want to create a concrete class derived directly from Foo in another assembly, Blah.DLL.

Me: You are going to have to learn to live with disappointment.

Him: But I should be able to because I have public access to the class Foo!

Me: That statement is false. Having public access to a class absolutely does not mean that it is always legal to create a subclass in another assembly. There are any number of reasons why it might not be legal to create a subclass in another assembly; you happen to have run across just one of them. (There are plenty more -- for example, you could make a public class and mark all the constructors as internal. It's not possible to subclass that from another assembly either, since the derived class constructor does not have an accessible base class constructor that it can call.)

Him: You're right. In this case, I can’t provide an overriding implementation for the internal abstract method DoSomething() because it is not accessible. Therefore the compiler will give an error when I try to subclass Foo. How do I get around this?

Me: You don’t get around it. The type safety system is working as designed; don’t try to defeat it.

If you own the FooBar.DLL assembly then of course there are several ways you can do what you want. Two that immediately come to mind are (1) mark Blah.DLL as a friend assembly of FooBar.DLL using the InternalsVisibleTo attribute (2) change the accessibility of DoSomething to public, protected or protected internal.

Him: Why is it even allowed to have a nonextensible class like this in the first place?

Me: It is sometimes a good thing to make a class that cannot be extended arbitrarily. I have myself occasionally used a similar technique to ensure that no third party can subclass one of my public base classes, though, as we’ll see later, there are other, perhaps better ways.

Look at it this way: if the author of the class wanted you to be able to subclass it, they probably would have made it possible. Clearly they do not want you to subclass this class.

Him: My previous question has been thoroughly begged. Why would someone want to prevent a third party from subclassing?

Me: I can think of a few reasons. Here are two:

1) Designing a class to be extended effectively by third parties is work, and work requires effort. If the class is not intended to be extended by third parties, but must be unsealed (for internal extension, for example) then the implementer is faced with a choice: spend the time/money/effort unnecessarily, provide an extensible but brittle class, or prevent extension by some other method.

This trick is a cheap, easy and straightforward way to prevent extension by arbitrary third parties without preventing extension by internal classes. As we'll see next time, there are other ways you can do this too.

2) A class may need to be extensible internally and used externally, but must not be extended by third parties for security reasons. Imagine you have a method Transfer which takes two instances of your class BankAccount. Suppose BankAccount is an abstract class with two derived classes, CaymanIslandsAccount and SwissAccount. Do you really want arbitrary third parties able to make new objects which fulfill the BankAccount contract but were not implemented by you?

Again, you end up with a tradeoff – either implement Transfer so that it does type checks on the BankAccount passed to it (possibly expensive both in initial implementation and maintenance), implement Transfer so that it accepts any old thing (dangerous!), or prevent anyone from making an unknown kind of BankAccount (cheap, safe).

In general, good security design is “make them do something impossible in order to break the system”. (Or even better “make them do seven impossible things”.) By making it impossible for third parties to fulfill the contract, you add additional security to the system. (By no means enough security, but a little bit more.)

Him: Thanks, I see what's going on here. Clearly I got into this situation because this trick of using access modifiers to prevent extension is insufficient to convey the author of FooBar.DLL's intentions.

Me: Next time we'll talk about more obvious ways to state the intention of "please don't subclass this thing".

  • I don't think you can deduce that Foo's author doesn't want you to subclass Foo based only on the existence of an internal abstract method.

    It's perfectly reasonable to have a public class with a few public methods and a few internal ones. It might then occasionally happen that it is convenient to make a class abstract (for the usual reasons) and so you might end up with a public abstract class with some public methods and some internal methods, some of which just happen to be abstract.

    Of course in that case the class can no longer _be_ subclassed by a third party, which should be something that is explicitly designed for, but this isn't an unthinkable oversight.

    If I wanted a class not to be subclassed, I'd make it sealed. If I wanted it to be subclassed, but not externally, I'd make all constructors private or internal (and not protected). If I for some reason would need a protected constructor, I'd look for any attributes that enforce the inability to externally subclass my class. If I couldn't find any, then as a last resort I might use an abstract internal method to enforce it. But then I'd at least document that I don't want external subclasses and that this is how I enforce it.

  • >My previous question has been thoroughly begged

    ??

    http://en.wikipedia.org/wiki/Begging_the_question

  • Right -- begging the question is answering a question in a manner which provides no new information, but rather, depends logically upon the answer to an equally difficult or more difficult question.

    Answering "Why is this allowed?" with "because it is desirable" doesn't tell you anything. Rather, it logically relies upon the answer to the harder question of "why is it desirable?"

    A similar example is attributed to Newton, in his criticism of the atomic theory. Why are some substances hard and some soft?  Because all substances are made of these things called "atoms", and some atoms are hard and some are soft.

    Newton quite rightly pointed out that this was begging the question; it's answering the question in a manner that provides no new information and raises the same question at a different level.

  • Joren: yes, you have re-stated my whole point here -- that if that is what the author of the class INTENDED, then they did a lousy job of communicating it.

  • Today I have already learned of the InternalsVisibleTo attribute, and it's only 8am. Thanks Eric!

  • FYI, InternalsVisibleTo is especially useful when you want to write unit tests for internal methods/classes as you usually do it from a separate dll.

  • Maybe the whole thing points to a missing feature in most popular type systems. What did the author try to prevent: reusing a base implementation or adding new players to the game by implementing the type interface?

    If I remember well, C# requires same visibility to a type and its implemented interfaces. However, I think there are public types in mscorlib implementing internal interfaces (maybe I'm wrong in this). Should C# allow this situation? On the other hand, there's no easy way in C# to reuse an implementation without inheriting. I'm thinking in some alternative as interface delegation in good old Delphi.

  • You do not remember well. C# does not require that an implemented interface be as accessible as the type. C# requires that a base class be at least as accessible as a derived class, but does not make the same restriction on interfaces. This is perfectly legal:

    public class C : C.I { private interface I {} }

    That is, C is a public class which implements private interface I.

    Similarly, this is perfectly legal:

    public interface I<X>{}

    public class C : I<C.D> { private class D {} }

  • This does seem a very interesting way for the author to support his intention to stop external subclassing. I can think of one class in particular where I came across this issue. The biggest problem from my point of view is that from the publically visible members there is no way of knowing that you could not subclass - after all, the thing that is stopping you is internal - there is little chance that this is going to be in the documentation (imagine the MSDN docs for the Foo class - it is public so in the docs, but nothing will indicate that you can't sub-class).

    Surely there are much more obvious ways to do such things. What I would consider the standard way of indicating that someone can't externally create an instance of something without some internal help is through having only private or internal constructors as you suggested as an alternative. One look at the docs in MSDN and you can see there are no public constructors - so you can see that it is not subclassable.

    As far as I can see though, all implementations from the same DLL (e.g. class Bar : Foo) will either have to have a similar trick to stop subclassing, or be external (as in the case I have in mind!). Otherwise you can subclass Bar instead, override all other overridable methods, and leave the Bar implementation of the internal abstact method. Can anybody think of any issues here I haven't thought about?

  • <i>public class C : C.I { private interface I {} }</i>

    Heh, heh... that produces a "Circular base class dependency involving C and C.I" (Visual Studio 2008 SP1). I'm working in a compiler, and I have found the problem when importing type declarations from mscorlib, via reflection. Of course, I just ignore those "internal interfaces", since they don't have any effect on client code.

    In any case, it would be nice if C# allowed it, with internal nested types. A private interface would be of little use in these cases, imho.

  • Whoops. As you point out, that is NOT legal. The reason I said it was legal is that I am actually fixing that bug as we speak, and on MY machine, it is legal. :-)

  • what about using friend assemblies?

  • What about them? Could you ask a more specific question?

  • I get the gist of what you're saying, but I hope your real conversation wasn't so harsh. Ouch.

    I also didn't get much from this, and had to reread 'the conversation' a few times to glean 1 fact - that there's some compiler hack that if I have an abstract class and put an abstract internal method it can't be subclassed outside of non-friend assemblies.

    +1 for trivia - I'm sure it took the newbies weeks to figure out how they broke the build when they altered a base-class.

  • It's not a "hack". This is the logical consequence of two perfectly sensible rules:

    1) A concrete derived class must implement all abstract methods of an abstract base class.

    2) It is illegal to override a method that you cannot access.

    Therefore, if you have an abstract class with internal abstract methods, it is impossible to make a concrete derived class in another assembly because there will always be a method that cannot be overridden.

Page 1 of 2 (27 items) 12