// todo: need header import org.aspectj.testing.Tester; /** * "Coverage" tests for * PR#476: * Member initializations are run after explicit * constructor calls ("this()" or "super()") when they should be run beforehand. * <p>Status: * 10 additional variants are defined, 5 of which fail, emitting 30 errors. * <p>background: * <br>The effective order of operation during initialization should be: * <ol> * <ol>superclass static initialization</ol> * <ol>selfclass static initialization</ol> * <ol>superclass member initialization</ol> * <ol>superclass constructor</ol> * <ol>selfclass member initialization</ol> * <ol>selfclass constructor</ol> * </ol> * Other relevant rules: * <li>this() or super() if present must be the first statement in a constructor</li> * <li>Cannot use this (and hence this.member) in either this() or super() * (checked by javac, not ajc) </li> * <li>Cannot refer to parent instance members in either this() or super() * (checked by javac, not ajc) </li> * <li>an enclosing instance is accessible only in the body of an instance * method, constructor (after the explicit constructor invocation, if any), * initializer block, or in the initializer expression of an instance variable.</li> * <p>fault model: * the compiler is inserting member initialization after the explicit * constructor call in the intermediate code. I.e., it produces: * <pre>ThisCall() { * this("correctValue"); * { * this.initString = "INIT"; * this.initNull = null; * }</pre> * when it should produce: * <pre>ThisCall() { * this("correctValue");</pre> * * <p>fix model: * Since member initialization must occur before this() call, * and this() must be first in the constructor, * I see no way to implement before advice on member initializers * using preprocessing to produce source code except to put them only * (and always) in the constructors without this() calls. * * <p>Variants tested in this coverage extension of the original test case: * <li>{type}[Object, String, Primitive]: Different member types</li> * <li>location[top, bottom, mixed]: location of the member initializer in the class declaration - * before constructor, after constructor</li> * <li>initializer[simpleExpression, blockExpression, none]: * type of member initialization * (<code>Member m = x;<code> or <code>Member m; { m = x; }<code>) * with location variants. </li> * <li>initializerLocus[this (default), super, enclosing ]: * fields being initialized - this instance, superclass, enclosing class * <li>{enclosingClass}[none, Outer, ]: Different member types</li> * * <p>Variants not (yet?) tested: * <li>static variants</li> * <li>{super}[{default},Child]: Calling <code>super()</code> rather than <code>this()</code> </li> * * <p>Untestable variants: * <li>Illegal to use this or super member values in explicit constructor call parameter * evaluation: <code>super("result: " + member)</code> * or <code>this("result: " + member)</code>. * or <code>this("result: " + super.member)</code>. * * $Id: MemberInitializationsAfterExplicitConstructorCallsCoverage.java,v 1.2 2001/08/03 22:38:49 isberg Exp $ */ public class MemberInitializationsAfterExplicitConstructorCallsCoverage { public static final String INPUT = "input"; public static final String INIT = "INIT"; public static void main(String[] args) { test(); } public static void test() { boolean doPassingTests = true; boolean doFailingTests = true; //--------- proof that test code is correct { ThisCallTopSimple thisCall = new ThisCallTopSimple(1); thisCall.go(); } //--------- passing test cases //--- this duplicates original test case // ThisCall thisCall; // no constructor call to this // thisCall = new ThisCall(INPUT); // thisCall.go(); //--- new coverage tests - 5 tests, 6 errors each, 30 errors if (doPassingTests) { { ThisCallTopSimple thisCall = new ThisCallTopSimple(INPUT); thisCall.go(); } { ThisCallTopBlock thisCall = new ThisCallTopBlock(INPUT); thisCall.go(); } { ThisCallBottomSimple thisCall = new ThisCallBottomSimple(INPUT); thisCall.go(); } { ThisCallBottomBlock thisCall = new ThisCallBottomBlock(INPUT); thisCall.go(); } { ThisCallMixed thisCall = new ThisCallMixed(INPUT); thisCall.go(); } // all super cases pass { ThisCallChild thisCall = new ThisCallChild(); thisCall.go(); } { ThisCallChild thisCall = new ThisCallChild(2); thisCall.go(); } { ThisCallChild thisCall = new ThisCallChild(INPUT); thisCall.go(); } // enclosed inner class initializer can access enclosing members { ThisCallEnclosing.ThisCallEnclosed thisCall = (new ThisCallEnclosing("ignored")).new ThisCallEnclosed(); } } // { ThisCallChild thisCall = new ThisCallChild(); thisCall.go(); } //--------- failing test cases //--- duplicate original test case // fails - constructor call to this //thisCall = new ThisCall(); //thisCall.go(); //--- new coverage tests if (doFailingTests) { { ThisCallTopSimple thisCall = new ThisCallTopSimple(); thisCall.go(); } { ThisCallTopBlock thisCall = new ThisCallTopBlock(); thisCall.go(); } { ThisCallBottomSimple thisCall = new ThisCallBottomSimple(); thisCall.go(); } { ThisCallBottomBlock thisCall = new ThisCallBottomBlock(); thisCall.go(); } { ThisCallMixed thisCall = new ThisCallMixed(); thisCall.go(); } } //--------- impossible test cases //---- unable to test superclass initialization before instance // { ThisCallChild thisCall = new ThisCallChild((long)1l); thisCall.go(); } } /** variant: location top, initializer simpleExpression */ static class ThisCallTopSimple { /** type primitive, location top, initializer simpleExpression */ int initOne = 1; /** type String, location top, initializer simpleExpression */ String initString = "INIT"; /** type Object, location top, initializer simpleExpression */ Object initNull = null; /** type String, location top, initializer none */ String initNone; /** no bug when calling this directly */ ThisCallTopSimple (String input) { checkMembersHaveInitializedValues("constructor ThisCallTopSimple(\" + input + \")"); setValues(input); checkMembersHaveSetValues("constructor ThisCallTopSimple.ThisCallTopSimple(\" + input + \")"); } void setValues(String input) { this.initString = input; this.initNull = input; this.initNone = input; this.initOne = 2; } /** proof that test code is correct */ ThisCallTopSimple (int ignored) { checkMembersHaveInitializedValues("constructor ThisCallTopSimple.ThisCallTopSimple(int)"); setValues(INPUT); checkMembersHaveSetValues("constructor ThisCallTopSimple.ThisCallTopSimple(int)"); } /** bug when calling this which calls ThisCall(String) */ ThisCallTopSimple () { this(INPUT); checkMembersHaveSetValues("constructor ThisCallTopSimple.ThisCallTopSimple()"); } /** redundant check - same check at end of constructors */ void go() { checkMembersHaveSetValues("method ThisCallTopSimple.go()"); } /** the same method for all variants */ protected void checkMembersHaveInitializedValues(String label) { Tester.checkEqual("INIT", initString, label + " initialized "); Tester.checkEqual((Object) null, initNull, label + " initialized "); Tester.checkEqual((Object) null, initNone, label + " initialized "); Tester.checkEqual(1, initOne, label + " initialized "); } /** the same method for all variants */ protected void checkMembersHaveSetValues(String label) { Tester.checkEqual(2, initOne, label + " set "); Tester.checkEqual("input", initString, label + " set "); Tester.checkEqual("input", initNone, label + " set "); // Object uses strict/reference identity - input dependency Tester.checkEqual(INPUT, initNull, label + " set "); } } // ThisCallTopSimple /** variant: location top, initializer blockExpression */ static class ThisCallTopBlock { /** top declarations */ /** type primitive, location top, initializer blockExpression */ int initOne; /** type String, location top, initializer blockExpression */ String initString; /** type Object, location top, initializer blockExpression */ Object initNull; /** type String, location top, initializer none */ String initNone; /** top initializer block */ { initOne = 1; initString = "INIT"; initNull = null; } /** no bug when calling this directly */ ThisCallTopBlock (String input) { checkMembersHaveInitializedValues("constructor ThisCallTopBlock(\" + input + \")"); this.initString = input; this.initNull = input; this.initNone = input; this.initOne = 2; checkMembersHaveSetValues("constructor ThisCallTopSimple.ThisCall(\" + input + \")"); } /** bug when calling this which calls ThisCallTopBlock(String) */ ThisCallTopBlock () { this(INPUT); checkMembersHaveSetValues("constructor ThisCallTopSimple.ThisCallTopBlock()"); } /** redundant check - same check at end of constructors */ void go() { checkMembersHaveSetValues("method ThisCallTopBlock.go()"); } /** the same method for all variants */ protected void checkMembersHaveInitializedValues(String label) { Tester.checkEqual("INIT", initString, label + " initialized "); Tester.checkEqual((Object) null, initNull, label + " initialized "); Tester.checkEqual((Object) null, initNone, label + " initialized "); Tester.checkEqual(1, initOne, label + " initialized "); } /** the same method for all variants */ protected void checkMembersHaveSetValues(String label) { Tester.checkEqual(2, initOne, label + " set "); Tester.checkEqual("input", initString, label + " set "); Tester.checkEqual("input", initNone, label + " set "); // Object uses strict/reference identity - input dependency Tester.checkEqual(INPUT, initNull, label + " set "); } } // ThisCallTopBlock /** variant: location bottom, initializer simpleExpression */ static class ThisCallBottomSimple { /** no bug when calling this directly */ ThisCallBottomSimple (String input) { checkMembersHaveInitializedValues("constructor ThisCallBottomSimple(\" + input + \")"); this.initString = input; this.initNull = input; this.initNone = input; this.initOne = 2; checkMembersHaveSetValues("constructor ThisCallBottomSimple.ThisCallBottomSimple(\" + input + \")"); } /** bug when calling this which calls ThisCallBottomSimple(String) */ ThisCallBottomSimple () { this(INPUT); checkMembersHaveSetValues("constructor ThisCallBottomSimple.ThisCallBottomSimple()"); } /** redundant check - same check at end of constructors */ void go() { checkMembersHaveSetValues("method ThisCallBottomSimple.go()"); } /** the same method for all variants */ protected void checkMembersHaveInitializedValues(String label) { Tester.checkEqual("INIT", initString, label + " initialized "); Tester.checkEqual((Object) null, initNull, label + " initialized "); Tester.checkEqual((Object) null, initNone, label + " initialized "); Tester.checkEqual(1, initOne, label + " initialized "); } /** the same method for all variants */ protected void checkMembersHaveSetValues(String label) { Tester.checkEqual(2, initOne, label + " set "); Tester.checkEqual("input", initString, label + " set "); Tester.checkEqual("input", initNone, label + " set "); // Object uses strict/reference identity - input dependency Tester.checkEqual(INPUT, initNull, label + " set "); } /** type primitive, location bottom, initializer simpleExpression */ int initOne = 1; /** type String, location bottom, initializer simpleExpression */ String initString = "INIT"; /** type Object, location bottom, initializer simpleExpression */ Object initNull = null; /** type String, location bottom, initializer none */ String initNone; } // ThisCallBottomSimple /** variant: location bottom, initializer blockExpression */ static class ThisCallBottomBlock { /** no bug when calling this directly */ ThisCallBottomBlock (String input) { checkMembersHaveInitializedValues("constructor ThisCallBottomBlock(\" + input + \")"); this.initString = input; this.initNull = input; this.initNone = input; this.initOne = 2; checkMembersHaveSetValues("constructor ThisCallBottomBlock.ThisCallBottomBlock(\" + input + \")"); } /** bug when calling this which calls ThisCallBottomBlock(String) */ ThisCallBottomBlock () { this(INPUT); checkMembersHaveSetValues("constructor ThisCallBottemBlock.ThisCallBottomBlock()"); } /** redundant check - same check at end of constructors */ void go() { checkMembersHaveSetValues("method ThisCallBottomBlock.go()"); } /** the same method for all variants */ protected void checkMembersHaveInitializedValues(String label) { Tester.checkEqual("INIT", initString, label + " initialized "); Tester.checkEqual((Object) null, initNull, label + " initialized "); Tester.checkEqual((Object) null, initNone, label + " initialized "); Tester.checkEqual(1, initOne, label + " initialized "); } /** the same method for all variants */ protected void checkMembersHaveSetValues(String label) { Tester.checkEqual(2, initOne, label + " set "); Tester.checkEqual("input", initString, label + " set "); Tester.checkEqual("input", initNone, label + " set "); // Object uses strict/reference identity - input dependency Tester.checkEqual(INPUT, initNull, label + " set "); } /** bottom declarations */ /** type primitive, location bottom, initializer blockExpression */ int initOne; /** type String, location bottom, initializer blockExpression */ String initString; /** type Object, location bottom, initializer blockExpression */ Object initNull; /** type String, location bottom, initializer none */ String initNone; /** bottom initializer block */ { initOne = 1; initString = "INIT"; initNull = null; } } // ThisCallBottomBlock /** variant: location mixed, initializer mixed */ static class ThisCallMixed { /** type primitive, location top, initializer simpleExpression */ int initOne = 1; /** type String, location top, initializer simpleExpression */ String initString; /** no bug when calling this directly */ ThisCallMixed (String input) { checkMembersHaveInitializedValues("constructor ThisCallMixed(\" + input + \")"); this.initString = input; this.initNull = input; this.initNone = input; this.initOne = 2; checkMembersHaveSetValues("constructor ThisCallMixed.ThisCallMixed(\" + input + \")"); } /** bug when calling this which calls ThisCallMixed(String) */ ThisCallMixed () { this(INPUT); checkMembersHaveSetValues("constructor ThisCallMixed.ThisCallMixed()"); } /** redundant check - same check at end of constructors */ void go() { checkMembersHaveSetValues("method ThisCallMixed.go()"); } /** the same method for all variants */ protected void checkMembersHaveInitializedValues(String label) { Tester.checkEqual("INIT", initString, label + " initialized "); Tester.checkEqual((Object) null, initNull, label + " initialized "); Tester.checkEqual((Object) null, initNone, label + " initialized "); Tester.checkEqual(1, initOne, label + " initialized "); } /** the same method for all variants */ protected void checkMembersHaveSetValues(String label) { Tester.checkEqual(2, initOne, label + " set "); Tester.checkEqual("input", initString, label + " set "); Tester.checkEqual("input", initNone, label + " set "); // Object uses strict/reference identity - input dependency Tester.checkEqual(INPUT, initNull, label + " set "); } /** bottom declarations */ /** type String, location bottom, initializer none */ String initNone; /** type Object, location bottom, initializer blockExpression */ Object initNull; /** bottom (partial) initializer block */ { initString = "INIT"; initNull = null; } } // ThisCallMixed static class ThisCallChild extends ThisCallParent { /** type primitive, location top, initializer simpleExpression */ int initOne = 1; /** type String, location top, initializer simpleExpression */ String initString = "INIT"; /** type Object, location top, initializer simpleExpression */ Object initNull = null; /** type String, location top, initializer none */ String initNone; /** no bug when calling this directly */ ThisCallChild (String input) { checkMembersHaveInitializedValues("constructor ThisCallChild(\" + input + \")"); setValues(input); checkMembersHaveSetValues("constructor ThisCallChild.ThisCallChild((\" + input + \")");; Tester.checkEqual(parentObject, INPUT, "ThisCallChild.ThisCallChild(int ignored)"); } void setValues(String input) { this.initString = input; this.initNull = input; this.initNone = input; this.initOne = 2; } /** * @param correctResult * @param actual * @param expected * @param failedResult * @param testerMessage the String to use for Tester on failure - * Tester unused if null * @return correctResult if expected.equals(actual), failedResult otherwise */ static private String checkObject(String correctResult , Object expected , Object actual , String failedResult , String testerMessage) { if (null == expected) { if (null == actual) { return correctResult; } // else failures fall through } else if ((null != actual) && (expected.equals(actual))) { return correctResult; } // failures if (null != testerMessage) { Tester.checkEqual(actual, expected, testerMessage); } return failedResult; } /** proof that test code is correct */ ThisCallChild (int ignored) { checkMembersHaveInitializedValues("constructor ThisCallChild.ThisCallChild(int)"); setValues(INPUT); checkMembersHaveSetValues("constructor ThisCallChild.ThisCallChild(int)"); Tester.checkEqual(parentObject, INPUT, "ThisCallChild.ThisCallChild(int ignored)"); } /** no bug when calling this which calls ThisCall(String) */ ThisCallChild () { super(INPUT); checkMembersHaveInitializedValues("constructor ThisCallChild.ThisCallChild()"); setValues(INPUT); checkMembersHaveSetValues("constructor ThisCallChild.ThisCallChild()"); Tester.checkEqual(parentObject, INPUT, "ThisCallChild.ThisCallChild()"); } private static final String tccsuperlabel = "ThisCallChild.ThisCallChild(long)/* parent initialization complete before child */"; /** unable to access superclass member state before explicitly invoking constructor */ ThisCallChild (long ignored) { // this would do the check, but it is illegal // this(checkObject(INPUT, INPUT, parentObject, tccsuperLabel + "_FAILED", tccsuperlabel)); // this(checkObject(INPUT, INPUT, this$.getParentObject(), tccsuperlabel + "_FAILED", tccsuperlabel)); checkMembersHaveInitializedValues("constructor ThisCallChild.ThisCallChild()"); setValues(INPUT); checkMembersHaveSetValues("constructor ThisCallChild.ThisCallChild()"); Tester.checkEqual(parentObject, INPUT, "ThisCallChild.ThisCallChild()"); } /** redundant check - same check at end of constructors */ void go() { checkMembersHaveSetValues("method ThisCallChild.go()"); Tester.checkEqual(parentObject, INPUT, "ThisCallChild.go()"); } /** the same method for all variants */ protected void checkMembersHaveInitializedValues(String label) { Tester.checkEqual("INIT", initString, label + " initialized "); Tester.checkEqual((Object) null, initNull, label + " initialized "); Tester.checkEqual((Object) null, initNone, label + " initialized "); Tester.checkEqual(1, initOne, label + " initialized "); } /** the same method for all variants */ protected void checkMembersHaveSetValues(String label) { Tester.checkEqual(2, initOne, label + " set "); Tester.checkEqual("input", initString, label + " set "); Tester.checkEqual("input", initNone, label + " set "); // Object uses strict/reference identity - input dependency Tester.checkEqual(INPUT, initNull, label + " set "); } } static class ThisCallParent { /** not available to in child explicit constructor parameter expression */ protected Object parentObject = INIT; /** not available to in child explicit constructor parameter expression */ protected Object getParentObject() { return parentObject; } /** no bug here */ ThisCallParent() { Tester.checkEqual(parentObject, INIT, "ThisCallParent.ThisCallParent()"); parentObject = INPUT; } /** no bug here */ ThisCallParent(String input) { Tester.checkEqual(parentObject, INIT, "ThisCallParent.ThisCallParent(\"" + input + "\")"); parentObject = input; } } } // MemberInitializationsAfterExplicitConstructorCallsCoverage /** variant: location enclosing */ class ThisCallEnclosing { public static final String INPUT = "input"; public static final String INIT = "INIT"; String initString = INIT; String constructedString; public ThisCallEnclosed getEnclosed() { return new ThisCallEnclosed(); } /** no bug when calling this directly */ ThisCallEnclosing (String ignored) { constructedString = INPUT; initString = INPUT; } public class ThisCallEnclosed { boolean didCheck; { // check enclosing instance in initializer Tester.checkEqual(INPUT, initString, "ThisCallEnclosed.<initializer> initString"); Tester.checkEqual(INPUT, constructedString, "ThisCallEnclosed.<initializer> constructedString"); didCheck = true; } public ThisCallEnclosed() { this("init: " + initString + " constructed: " + constructedString); Tester.check(didCheck, "initializer ran before ThisCallEnclosed() body"); } public ThisCallEnclosed(String s) { Tester.checkEqual(INPUT, initString, "ThisCallEnclosed(String) initString"); Tester.checkEqual(INPUT, constructedString, "ThisCallEnclosed(String) constructedString"); Tester.check(didCheck, "initializer ran before ThisCallEnclosed(String) body"); } } } // ThisCallEnclosing