Home




Stack overflow calling a superclass method.

Copyright © 2008 Matthew H. Fields, DMA

Here's a pitfall of object-oriented programming that I have stumbled across twice in ten years. Both times it stumped me for a bit longer than it should have, so I finally wrote this white paper as something to refer to next time it happens.

Consider a situation where you're writing any instance method, and ask yourself these questions:

If you said yes to all three of those, the call to the superclass method might create a bottomless recursion, which will end with a bang when your machine runs out of stack space.

This can be especially puzzling when you don't have access to the source code for the superclass.

Here's a classic instance in Java.

public class MyComponent extends Component {
     private Dimension dMySize;
     public void setSize(int w, int h){
          setSize(new Dimension(w, h));
     }
     public void setSize(Dimension D){
          synchronize(dMySize){
               dMySize=new Dimension(D);
          }
          super.setSize(D)
     }
     .
     .
     .

}

This is a reasonable thing to do. In addition to getting the functionality of the superclass, you've saved a local instance copy of the size of the component in the variable dMySize, so other methods in your MyComponent class can do interesting things with it.

By the official syntax and semantics of Java, it seems that this ought to work, and it would be really quite reasonable for one to suppose that it does. In fact, when one calls MyComponent.setSize(Dimension), the machine bombs with a stack overflow, with MyComponent.setSize(Dimension) and MyComponent.setSize(int,int) mysteriously calling each other back and forth.

The key to this mystery is that the superclass--in this case Component--also has methods with the same two signatures, Component.setSize(Dimension), and Component.setSize(int,int). One of these is implemented in terms of the other--- the opposite of the way it's done in the MyComponent class.

public class java.awt.Component extends Object {
     .
     .
     .
     public void setSize(Dimension D){
          setSize(D.getWidth(),d.getHeight());
     }
     public void setSize(int w, int h){
          .
          .
          .
     }
     .
     . 
     .
}

So what happens when you take an instance I of class MyComponent and call I.setSize(D)? First, MyComponent.setSize(D) starts, and saves a copy of D in dMySize; then it calls Component.setSize(D).

Component.setSize(D) in turn calls setSize(w, h). And that's where the gotcha strikes. Component.setSize(D) does not explicitly call Component.setSize(w, h), but merely setSize(w, h). This is an instance method, so the rules of polymorphism say to look at this.getClass() and see whether setSize(w, h) is overridden by that class. Indeed it is, so Component.setSize(D) calls MyComponent.setSize(w, h), which calls MyComponent.SetSize(D), and now we're back where we started--but with three function calls on the stack.

Gotcha.

There's nothing in this example that is particular to Java. This pitfall can occur in any typical object-oriented environment, or any other environment with inheritance and polymorphism. Having multiple function signatures isn't required. But since it's fairly normal and usually good coding practice to implement one function signature in terms of the other so almost all the code is in exactly one place, that practice increases the likelihood of stumbling across this calling sequence. The exact same thing can happen in C#.Net, VisualBasic.Net, SmallTalk, and so forth.

If you're the developer of the subclass, the path of least resistance is to reverse the dependency order of your member functions.

If I simply make MyComponent.setSize(D) call setSize(w,h), and make myComponent.setSize(w, h) call super.setSize(w,h), then neither of my member functions will fail in this way. Both will eventually call Component.setSize(w, h), which never calls any other setSize method.

If you are overriding three or more signatures that are all implemented in the superclass, and you suspect something like this is going on, you may have to try more permutations before you find a dependency order that doesn't bomb like this. Getting access to the source code of the superclass could save you a great deal of costly and time-consuming experimentation.