More fun with call vs callvirt
A while back I posted a little bit about the one implication of call vs. callvirt and the other day I ran into another one that I thought I would share. This one is part of class of issues where C# or VB developers assume that if their language can’t do it, it is not doable. It turns out that is not always true and you can get yourself in trouble for thinking that way.
Let’s say I have a little collection that I want to ensure only stores even numbers in it. As Whidbey is not quite out yet, I can’t use List<int>, so I subclass for ArrayList and customize the add method to ensure that only even numbers get in.
public class EvenCollection : ArrayList
{
public EvenCollection()
{
}
public override int Add(object value)
{
if (value is Int32 && IsEven ((int) value))
return base.Add (value);
else throw new Exception ("Value must be a prime int");
}
internal bool IsEven (int value)
{
return value % 2 == 0;
}
}
I repeat for all the other methods on ArrayList which allow new elements to be added to the list.
Then I do a couple of tests in C# to be sure everything is working as expected, and indeed it does.
EvenCollection ec = new EvenCollection();
ec.Add (2); // works
ec.Add (3); //throws as expected
Just to be sure I really do understand OOP 101, I make sure that calls through the base class (ArrayList) still execute my Add() method, and indeed they do:
ArrayList l = new EvenCollection();
l.Add (2); // works
l.Add (3); //throws as expected
But here is the trick, try this is C++. Calling the method the normal way works as expected, but C++ supports the ability to call any implementation of a virtual method, not just the most derived.
EvenCollection* ec = new EvenCollection();
ec->Add(
__box(2)
); //works as expected
ec->ArrayList::Add(
__box(3)
); //Utz! Does not throw an exception
The syntax “ArrayList::Add” tells the compiler to call the implementation of Add() on the ArrayList method rather than the most derived one. This is not supported in C#\VB.NET but IS supported in the CLR.
This is the basic difference between the call and the callvirt instructions that was the subject of a recent contest
. Let’s look at the IL:
IL_001b: callvirt instance int32 [OverrideDemoLibrary]OverrideSecIssue.EvenCollection::Add(object)
IL_002a: call instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
You should be aware of this issue anytime you are using virtual members to guarantee semantics for correctness or security issues. Imagine if this was a list of only authenticated users or strings that have been validated. The lesson here to not inherit from ArrayList… It is meant to be delegated to rather than inherited from.
Note: this is a repost