Skip to content

Wrong super constructor is called #582

@Vampire

Description

@Vampire
  • Version of JMockit that was used: 1.44 & 1.45

We had very strange behavior:

  • In a test we have a field @Mocked javax.faces.context.PartialResponseWriter writer;
  • In the constructor of another class used in that test we initialize a private field like responseWriter = new TestResponseWriter();
  • javax.faces.context.ResponseWriter which both classes extend from (one directly, one indirectly) has no explicit constructor, just the default implied no-arg constuctor
  • java.io.Writer on the other hand has two protected constructors, one no-arg and one with an Object parameter that must not be null

grafik

  • Now when we run our tests, we get
    java.lang.NullPointerException
    	at java.base/java.io.Writer.<init>(Writer.java:174)
    	at javax.faces.context.ResponseWriter.<init>(ResponseWriter.java)
    	at de.empic.web.testutils.TestResponseWriter.<init>(TestResponseWriter.java:14)
    
  • This on first look makes absolutely no sense, as there is no constructor in ResponseWriter that calls the one-arg constructor of Writer.
  • So I naturally kicked off the debugger to look what is going on, but suddenly it didn't happen anymore then. And this constantly stays like hat, if you run without debugger it fails with NPE, if you run with debug setting (even if you don't even attach a debugger), it runs fine

So after a couple of hours of adding stdout-debugging to JMockit and digging my way through subtle differences in the output, I got to the root cause.
In mockit.internal.expectations.mocking.MockedClassModifier#visitMethod you call generateCallToSuperConstructor().
I'm not sure why this is done, I guess so that you are able to then add the call to the MockingBridge, as the super-constructor always has to be called first.

The problem is, that your code calls a random super constructor with default values according to types. I'm not sure this is the best decision, as different constructors can ensure different invariants and so calling a random one might not be the best solution. Maybe it would better to look into the actual implementation of the constructor and calling the super-constructor the real method also is calling? But even then using default values for the arguments might not be the best solution.

In my case here, after adding

System.out.println("vvvvvvvvvvvvvvvvvvvvvvvv");
for (MethodInfo methodOrConstructor : cmr.getMethods()) {
   if (methodOrConstructor.isConstructor()) {
      System.out.println("methodOrConstructor.desc = " + methodOrConstructor.desc);
      System.out.println("methodOrConstructor.accessFlags = " + methodOrConstructor.accessFlags);
   }
}
System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^");

to mockit.internal.SuperConstructorCollector#findConstructor I have seen that without debug I get

vvvvvvvvvvvvvvvvvvvvvvvv
methodOrConstructor.desc = (Ljava/lang/Object;)V
methodOrConstructor.accessFlags = 4
methodOrConstructor.desc = ()V
methodOrConstructor.accessFlags = 4
^^^^^^^^^^^^^^^^^^^^^^^^

whereas with debug I get

vvvvvvvvvvvvvvvvvvvvvvvv
methodOrConstructor.desc = ()V
methodOrConstructor.accessFlags = 4
methodOrConstructor.desc = (Ljava/lang/Object;)V
methodOrConstructor.accessFlags = 4
^^^^^^^^^^^^^^^^^^^^^^^^

and the code just takes the first one it finds.
In my case without debug this was the one-arg constructor that was given null as parameter and thus produced that NPE.

So one problem is, that it sometimes uses one constructor, sometimes the other, another problem probably is that actually it uses some random constructor that might have requirements or produces certain invariants.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions