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

Second assignment to final variable is possible inside an assert()

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Fixed
    • Icon: P3 P3
    • 1.4.0
    • 1.4.0
    • tools
    • beta2
    • generic
    • generic
    • Verified



      Name: bsC130419 Date: 06/11/2001


      java version "1.4.0-beta"
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-beta-b65)
      Java HotSpot(TM) Client VM (build 1.4.0-beta-b65, mixed mode)

      /*
      Javac 1.4 beta has a SERIOUS bug in allowing a final variable to be assigned
      twice. This particular example shows that class Foo can read three different
      values in the final variable Bar.s: the value before initialization, after the
      first assignment, and after the second assignment.

      $ cat Foo.java
      // */
      class Bar {
        static final String s;
        static {
          try {
            Foo.show();
            assert false : s="One";
          } catch (AssertionError ae) {
          }
          Foo.show();
          s="Two";
        }
      }
      class Foo extends Bar {
        static void show() {
          System.out.println(s);
        }
        public static void main(String[] args) {
          show();
        }
      }
      /*
      $ javac -source 1.4 Foo.java
      $ java -ea Foo
      null
      One
      Two

      This is due to a vague specification in the assert whitepaper, and a bug in JLS
      16.2.14. Regarding catch and finally blocks, the assert whitepaper states only
      that definite unassignment treats contained assert statements as if they were
      throw statements. However, the JLS incorrectly describes the behavior for
      definite unassignment in catch and finally blocks that can be reached by throw
        statements, by stating that a variable only need be unassigned _before_ the
      throw statement, rather than _after_ the expression of the throw statement.

      Javac has always implemented definite unassignment of catch blocks due to throw
        statements correctly, in spite of the incorrect wording in the JLS, but does
      not do the same with assert. The correct behavior must check that a variable
      is definitely unassigned AFTER expression1 if false, and AFTER expression2 if
      present, before considering the variable to be definitely unassigned in a catch
      or finally block.
      */
      (Review ID: 126149)
      ======================================================================

      Also:


      The final release candidate specification for assert has a serious bug with
      regards to definite assignment. JDK 1.4 beta compiles this class with no
      second thoughts, and the resulting behavior assigns a final variable twice!

      $ cat Foo.java
      class Foo {
        public static void main(String[] args) {
          final boolean b;
          try {
            assert false : b = true;
          } catch (Error e) {
            System.out.println(e);
            b = false;
            System.out.println(b);
          }
        }
      }
      $ javac -source 1.4 Foo.java
      $ java -ea Foo
      java.lang.AssertionError: true
      false
      $ javap -c Foo
      [...]
      Method void main(java.lang.String[])
         0 getstatic #7 <Field boolean $assertionsDisabled>
         3 ifne 17
         6 new #8 <Class java.lang.AssertionError>
         9 dup
        10 iconst_1
        11 dup
      // store true to b
        12 istore_1
        13 invokespecial #9 <Method java.lang.AssertionError(boolean)>
        16 athrow
        17 goto 37
        20 astore_2
        21 getstatic #11 <Field java.io.PrintStream out>
        24 aload_2
        25 invokevirtual #12 <Method void println(java.lang.Object)>
        28 iconst_0
      // store false to b
        29 istore_1
        30 getstatic #11 <Field java.io.PrintStream out>
        33 iload_1
        34 invokevirtual #13 <Method void println(boolean)>
        37 return
      Exception table:
         from to target type
           0 17 20 <Class java.lang.Error>
      [...]


      According to section IV, this is perfectly legal, too (I'll use DU as an
      abbreviation for definitely unassigned):
      Before the try block, b is DU.
      Conclusion 1: By JLS 16.2.2.f, b is DU before the assert statement.
      Thus, by IV.a, b is DU before Expression1.
      By JLS 16.1.1.b, b is DU after Expression1 when true, since it is constant
      false.
      So, by IV.c, b is DU after the assert statement.
      Conclusion 2: By JLS 16.2.2.e, b is DU after the try block.

      In addition, by JLS 16.1.1.d, b is DU after Expression1 when false.
      Then, by IV.d, b is DU before Expression2.
      Thus, the assignment in Expression2 is legal.

      Now, by 16.2.14.c (as modified by the 3rd paragraph of IV), b is DU before
      the catch block iff:
        it is DU after the try block (conclusion 2)
        it is DU before every break, continue, or return statement in the try
      block (there are none)
        it is DU before every explicit throw statement in the try block (there are
      none)
        it is DU before every assert statement in the try block, treated like an
      implicit throw statement (conclusion 1)
      Therefore, b is DU before the catch block, and the assignment in the catch
      block is legal.

      Yet, in the course of execution, both assignments take place, and with
      conflicting values.


      Actually, after further thought, I think the bug lies in 16.2.14. At least
      in the way javac behaves, it should read that V is DU before a catch block
      iff it is DU after the contained expression of all contained throw
      statements (as opposed to the current wording of DU before the throw
      statement).

      For example, javac rejects this method, although the JLS permits it:
      void foo() {
        final boolean b;
        class MyExc extends Exception { MyExc(boolean b) { super(""+b); }}
        try {
          throw new MyExc(b = true);
        } catch (Error e) {
          System.out.println(e);
          b = false;
          System.out.println(b);
        }
      }

      If the bug is in the JLS, at least the specification for assert should
      clarify that a variable is not DU before a catch block unless it is DU after
      Expression1 when false, as well as after Expression2 if present.

      The same bug also exists with the final bullet in 16.2.14, regarding DU
      status before a finally block. That bullet is additionally lacking the
      requirement that a variable be DU before any of these occurances within a
      catch block of the try statement: any break or continue whose target exits
      the try statement; after the expression of any throw statement; and after
      the expression of any return statement in non-void methods, or before the
      return statement in void methods. For example, javac rejects this, although
      the JLS permits it:

      boolean foo() {
        final boolean b;
        try {
          throw new Exception();
        } catch (Exception e) {
          return b = true;
        } finally {
          return b = false;
        }
      }

      --
      Eric Blake, Elixent, University Gate, Park Row, Bristol, BS1 5UB, UK
      ###@###.### tel:+44(0)117 9008252

      *********
      Also:


      > $ javac -source 1.4 Bar.java
      > class Bar {
      > static final int i;
      > static {
      > show();
      > i = 1;
      > show();
      > assert false;
      > i = 2;
      > show();
      > }
      > static void show() {
      > System.out.println(i);
      > }
      > public static void main(String[] args) {}
      > }
      > $ java Bar
      > 0
      > 1
      > 2
      >
      > This time, the bug is definitely (pardon the pun) in the assert
      > specification, not the JLS.
      > Since asserts need not be enabled, rule IV.c needs to read as follows:
      >
      > V is definitely unassigned after the assert statement iff V is definitely
      > unassigned after Expression1 when true _and V is definitely unassigned
      > before the assert statement_.

      That won't quite cut it, in the case where a variable is assigned as part of
      expression 1. I ended up implementing it in my compiler as the following:

      V is definitely unassigned after the assert statement iff V is definitely
      unassigned after Expression1 when true and V is definitely unassigned after
      Expresssion1 when false (in other words, V is definitely unassigned after
      the assert statement iff V is definitely assigned after Expression1).

      --
      Eric Blake, Elixent, University Gate, Park Row, Bristol, BS1 5UB, UK
      ###@###.### tel:+44(0)117 9008252


            gafter Neal Gafter (Inactive)
            bstrathesunw Bill Strathearn (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: