Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4353401

Cyclic static initializers allow method invocation before class initialization

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Won't Fix
    • Icon: P4 P4
    • None
    • 1.2.2
    • hotspot
    • generic
    • generic



      Name: jk109818 Date: 07/14/2000


      java version "1.2.2"
      Classic VM (build 1.2.2-L, green threads, nojit)


      With these two java files, it is possible to access methods in a class before
      the class has finished initializing.

      According to the JLS 2nd Ed. Draft, Sec. 12.4: "Before a class is initialized,
      its superclass must be initialized..." and Sec. 12.4.1:
      "A class or interface type T will be initialized immediately before the first
      occurence of any one of the following:
      - T is a class and an instance of T is created.
      - T is a class and a static method declared by T is invoked....

      The intent here is that a class or interface type has a set of initializers that
      put it in a consistent state, and that this state is the first state that is
      observed by other classes."

      Then in 12.4.2, it describes how a Class may have one of four states:
      "- This Class object is verified and prepared but not initialized.
      - This Class object is being initialized by some particular thread T.
      - This Class object is fully initialized and ready for use.
      - This Class object is in an erroneous state, perhaps because the verification
      or preparation step failed, or because initialization was attempted and failed."
      The algorithm lists steps 3 and 7:
      "3. If initialization is in progress for the class or interface by the current
      thread, then this must be a recursive request for initialization. Release the
      lock on the Class object and complete normally."
      "7. Next, if the Class object represents a class rather than an interface, and
      the superclass of this class has not yet been initialized, then recursively
      perform this entire procedure for the superclass. If necessary, verify and
      prepare the superclass first. If the initialization of the superclass completes
      abruptly because of a thrown exception, then lock this Class object, label it
      erroneous, notify all waiting threads , release the lock, and complete abruptly,
      throwing the same exception that resulted from initializing the superclass."

      In the sample code, executing the Initialize.main method forces initialization
      of Initialize. Now the Class for Initialize is in the second state (in
      progress), when it creates an instance of Sub, which forces initialization of
      Sub. When the VM gets to step 7 for initializing Sub, it sees that it must
      initialize Initialize first; but this recursive attempt is ended at step 3, and
      the thread returns to initializing Sub. This sets the Class for Sub to the
      third state (fully initialized), so the constructor is now executed. Finally,
      the original initialization of Initialize can finish.

      On the other hand, executing the Sub.main method forces initialization of Sub,
      leaving it marked in progress when the initialization for Initialize starts per
      step 7. This attempts to construct an instance of Sub, starting a recursive
      attempt to initialize Sub. This attempt ends in step 3, so the constructor is
      called even though both initializations are in progress, and in this case, the
      uninitialized state of the variable i can be seen.

      In both cases, it appears that the JLS was violated. In the first case, Sub was
      initialized before its superclass; ie. in the fully initialized state while its
      superclass was still only in the in progress state. In the second case,
      Initialize was able to access a method in a class that was not initialized (both
      classes were in the in progress state), and was therefore able to access
      inconsistent state of the variable i.

      I see a few possible solutions. One is modifying JLS 8.7 and 12.4 to prohibit a
      static initializer in a superclass to have an active reference to a subclass
      during static initialization. This would prevent this instance of recursive
      initialization, but will not catch all cases of recursion. Another would be to
      modify the language of JLS 12.4, making it clear that before a class is
      initialized, its superclasses must have begun the process of initialization, but
      may not be fully initialized; and 12.4.1 to state that the first active access
      of a class will begin the initialization process, but that during initialization
      another access of the class may occur and see incomplete state. A third option
      would be to flag any attempts of a static initializer to access another class,
      where initialization is currently in progress by the same thread but not yet
      complete, as an error such as InitializerCircularityError. This could be
      detected at step 3 of the initialization algorithm of JLS 12.4.2. However, this
      option invalidates any initializer recursion, which is more restrictive than the
      current version of Java.

      I note that Bug 4248094 brings up a related issue, but it only deals with access
      to static fields and not methods. It was marked as not a bug, but without
      quoting JLS how it was to spec. Further, JLS 12.4.2 does state that "a variable
      initializer in class A might invoke a method of an unrelated class B, which
      might in turn invoke a method of class A," but does not mention if this is legal
      in related classes or for static initializer blocks in general, and still has
      the problem that the method in class A will be executed before A's
      initialization has completed, which seems to contradict earlier statements in
      12.4.

      Here are the test files and their outputs:
      public class Initialize {
        static {
          new Sub();
          System.out.println("Initializing class Initialize");
        }
        public static void main(String[] argv) {
          System.out.println("In Initialize.main");
        }
      }
      /* Output:
      Initializing class Sub
      In constructor of Sub, i = 1
      Initializing class Initialize
      In Initialize.main
      */

      public class Sub extends Initialize {
        static int i = 1;
        static {
          System.out.println("Initializing class Sub");
        }
        public static void main(String[] argv) {
          System.out.println("In Sub.main");
        }
        Sub() {
          System.out.println("In constructor of Sub, i = " + i);
        }
      }
      /* Output:
      In constructor of Sub, i = 0
      Initializing class Initialize
      Initializing class Sub
      In Sub.main
      */
      (Review ID: 105860)
      ======================================================================

            collins Gary Collins (Inactive)
            jkimsunw Jeffrey Kim (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: