Practice thinking like a compiler tester, part two
Reader RichM found the same solution to the puzzle I posed yesterday that I did:
public class V : S.T {}
public class S {
public class T{}
}
The control flow of the emitter from the start to the point of the bug goes like this:
Emit(V)
Emit (S.T) // V base class
Emit(null) // S.T base class
Emit(S) // S.T outer class
Emit(null) // S base class
Emit(null) // S outer class
ReallyEmit(S)
S.emitted = true
Emit(S.T) // S's first inner class
Emit(S) // S.T's outer class.
// Take the early out because S.emitted is true
ReallyEmit(S.T)
S.T.emitted = true
// T has no inner classes, so return
// S has no more inner classes, so return
ReallyEmit(S.T) // oops, we already did this. Bug!
Of course this was not what the emitting code actually looked like. What it actually looked like was something like:
void Emit(Class c)
{
if (c == null || c.Emitted) return; // don’t emit twice!
Emit(c.BaseClass);
Emit(c.OuterClass);
// If somehow Emit was called on this class before our outer class, then
// we just caused ourselves to be emitted when we emitted the outer class.
// (The outer class emits its inner classes, ie, the current
// class, before it returns.)
// If we are already emitted then we have no more work to do.
if (c.Emitted) {
Debug.Assert(c.OuterClass != null && c.OuterClass.Emitted);
return;
}
ReallyEmit(c);
c.Emitted = true;
foreach(Class i in c.InnerClasses)
Emit(i);
}
This algorithm is correct. However, the comment and the assertion are not correct. (This is why I was looking at this code in the first place, because the assertion was firing. I fixed it by updating the comment and removing the assertion.)
Can you find a legal set of C# classes which are correctly emitted but cause the assertion to fire?
Comments
Anonymous
April 11, 2007
class A : B {} class B { class C : A {} } Emit(A) Emit(B) // base Emit(null) // base Emit(null) // outer ReallyEmit(B) B.Emitted = true Emit(B.C) // inner Emit(A) // base Emit(B) // base, returns immediately Emit(null) // outer ReallyEmit(A) // !!! uh-oh A.Emitted = true Emit(B) // outer, returns immediately ReallyEmit(B.C) B.C.Emitted = true Emit(null) // outer Debug.Assert(A.OuterClass != null && ...) // assertion failureAnonymous
April 11, 2007
I wrote a small vb.net console app to answer your puzzle and test your problem.Anonymous
April 11, 2007
Steve's example seems to be correct to me. Presumably you could put in an assertion: Debug.Assert( (c.OuterClass != null && c.OuterClass.Emitted) || (c.BaseClass != null && c.BaseClass.Emitted)); In other words "Either our base class, or our outer class, caused us to be emitted just now." I'm not sure how useful it would be as an assertion though. JonAnonymous
April 11, 2007
Steve is correct, yes. And yes, the assertion is useless. I simply removed it entirely rather than trying to replace it with something else.Anonymous
September 06, 2007
Yesterday I posed a slightly harder version of the puzzle I posted the day before . Reader Steve found