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