Skip to content

Commit ac6af6a

Browse files
archiecobbsVicente Romero
authored andcommitted
7176515: ExceptionInInitializerError for an enum with multiple switch statements
8299760: ExceptionInInitializerError for an enum with multiple switch statements, follow-up Reviewed-by: vromero
1 parent dd23ee9 commit ac6af6a

File tree

4 files changed

+193
-17
lines changed

4 files changed

+193
-17
lines changed

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,34 @@ JCClassDecl classDef(ClassSymbol c) {
227227
return def;
228228
}
229229

230+
/**
231+
* Get the enum constants for the given enum class symbol, if known.
232+
* They will only be found if they are defined within the same top-level
233+
* class as the class being compiled, so it's safe to assume that they
234+
* can't change at runtime due to a recompilation.
235+
*/
236+
List<Name> enumNamesFor(ClassSymbol c) {
237+
238+
// Find the class definition and verify it is an enum class
239+
final JCClassDecl classDef = classDef(c);
240+
if (classDef == null ||
241+
(classDef.mods.flags & ENUM) == 0 ||
242+
(types.supertype(currentClass.type).tsym.flags() & ENUM) != 0) {
243+
return null;
244+
}
245+
246+
// Gather the enum identifiers
247+
ListBuffer<Name> idents = new ListBuffer<>();
248+
for (List<JCTree> defs = classDef.defs; defs.nonEmpty(); defs=defs.tail) {
249+
if (defs.head.hasTag(VARDEF) &&
250+
(((JCVariableDecl) defs.head).mods.flags & ENUM) != 0) {
251+
JCVariableDecl var = (JCVariableDecl)defs.head;
252+
idents.append(var.name);
253+
}
254+
}
255+
return idents.toList();
256+
}
257+
230258
/** A hash table mapping class symbols to lists of free variables.
231259
* accessed by them. Only free variables of the method immediately containing
232260
* a class are associated with that class.
@@ -427,14 +455,62 @@ List<VarSymbol> freevars(ClassSymbol c) {
427455
Map<TypeSymbol,EnumMapping> enumSwitchMap = new LinkedHashMap<>();
428456

429457
EnumMapping mapForEnum(DiagnosticPosition pos, TypeSymbol enumClass) {
430-
EnumMapping map = enumSwitchMap.get(enumClass);
431-
if (map == null)
432-
enumSwitchMap.put(enumClass, map = new EnumMapping(pos, enumClass));
433-
return map;
458+
459+
// If enum class is part of this compilation, just switch on ordinal value
460+
if (enumClass.kind == TYP) {
461+
final List<Name> idents = enumNamesFor((ClassSymbol)enumClass);
462+
if (idents != null)
463+
return new CompileTimeEnumMapping(idents);
464+
}
465+
466+
// Map identifiers to ordinal values at runtime, and then switch on that
467+
return enumSwitchMap.computeIfAbsent(enumClass, ec -> new RuntimeEnumMapping(pos, ec));
434468
}
435469

436-
/** This map gives a translation table to be used for enum
437-
* switches.
470+
/** Generates a test value and corresponding cases for a switch on an enum type.
471+
*/
472+
interface EnumMapping {
473+
474+
/** Given an expression for the enum value's ordinal, generate an expression for the switch statement.
475+
*/
476+
JCExpression switchValue(JCExpression ordinalExpr);
477+
478+
/** Generate the switch statement case value corresponding to the given enum value.
479+
*/
480+
JCLiteral caseValue(VarSymbol v);
481+
482+
default void translate() {
483+
}
484+
}
485+
486+
/** EnumMapping using compile-time constants. Only valid when compiling the enum class itself,
487+
* because otherwise the ordinals we use could become obsolete if/when the enum class is recompiled.
488+
*/
489+
class CompileTimeEnumMapping implements EnumMapping {
490+
491+
final List<Name> enumNames;
492+
493+
CompileTimeEnumMapping(List<Name> enumNames) {
494+
Assert.check(enumNames != null);
495+
this.enumNames = enumNames;
496+
}
497+
498+
@Override
499+
public JCExpression switchValue(JCExpression ordinalExpr) {
500+
return ordinalExpr;
501+
}
502+
503+
@Override
504+
public JCLiteral caseValue(VarSymbol v) {
505+
final int ordinal = enumNames.indexOf(v.name);
506+
Assert.check(ordinal != -1);
507+
return make.Literal(ordinal);
508+
}
509+
}
510+
511+
/** EnumMapping using run-time ordinal lookup.
512+
*
513+
* This builds a translation table to be used for enum switches.
438514
*
439515
* <p>For each enum that appears as the type of a switch
440516
* expression, we maintain an EnumMapping to assist in the
@@ -466,8 +542,8 @@ EnumMapping mapForEnum(DiagnosticPosition pos, TypeSymbol enumClass) {
466542
* </pre>
467543
* class EnumMapping provides mapping data and support methods for this translation.
468544
*/
469-
class EnumMapping {
470-
EnumMapping(DiagnosticPosition pos, TypeSymbol forEnum) {
545+
class RuntimeEnumMapping implements EnumMapping {
546+
RuntimeEnumMapping(DiagnosticPosition pos, TypeSymbol forEnum) {
471547
this.forEnum = forEnum;
472548
this.values = new LinkedHashMap<>();
473549
this.pos = pos;
@@ -500,15 +576,22 @@ class EnumMapping {
500576
// the mapped values
501577
final Map<VarSymbol,Integer> values;
502578

503-
JCLiteral forConstant(VarSymbol v) {
579+
@Override
580+
public JCExpression switchValue(JCExpression ordinalExpr) {
581+
return make.Indexed(mapVar, ordinalExpr);
582+
}
583+
584+
@Override
585+
public JCLiteral caseValue(VarSymbol v) {
504586
Integer result = values.get(v);
505587
if (result == null)
506588
values.put(v, result = next++);
507589
return make.Literal(result);
508590
}
509591

510592
// generate the field initializer for the map
511-
void translate() {
593+
@Override
594+
public void translate() {
512595
boolean prevAllowProtectedAccess = attrEnv.info.allowProtectedAccess;
513596
try {
514597
make.at(pos.getStartPosition());
@@ -3760,7 +3843,7 @@ public JCTree visitEnumSwitch(JCTree tree, JCExpression selector, List<JCCase> c
37603843
selector.type,
37613844
currentMethodSym);
37623845
JCStatement var = make.at(tree.pos()).VarDef(dollar_s, selector).setType(dollar_s.type);
3763-
newSelector = make.Indexed(map.mapVar,
3846+
newSelector = map.switchValue(
37643847
make.App(make.Select(make.Ident(dollar_s),
37653848
ordinalMethod)));
37663849
newSelector =
@@ -3771,7 +3854,7 @@ public JCTree visitEnumSwitch(JCTree tree, JCExpression selector, List<JCCase> c
37713854
.setType(newSelector.type))
37723855
.setType(newSelector.type);
37733856
} else {
3774-
newSelector = make.Indexed(map.mapVar,
3857+
newSelector = map.switchValue(
37753858
make.App(make.Select(selector,
37763859
ordinalMethod)));
37773860
}
@@ -3783,7 +3866,7 @@ public JCTree visitEnumSwitch(JCTree tree, JCExpression selector, List<JCCase> c
37833866
pat = makeLit(syms.intType, -1);
37843867
} else {
37853868
VarSymbol label = (VarSymbol)TreeInfo.symbol(((JCConstantCaseLabel) c.labels.head).expr);
3786-
pat = map.forConstant(label);
3869+
pat = map.caseValue(label);
37873870
}
37883871
newCases.append(make.Case(JCCase.STATEMENT, List.of(make.ConstantCaseLabel(pat)), c.stats, null));
37893872
} else {

test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ public class ClassFileLoadHookTest {
4242
public static String sharedClasses[] = {
4343
"ClassFileLoadHook",
4444
"ClassFileLoadHook$TestCaseId",
45-
"ClassFileLoadHook$1",
4645
"LoadMe",
4746
"java/sql/SQLException"
4847
};

test/langtools/tools/javac/T8011181/EmptyUTF8ForInnerClassNameTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,10 @@ void checkClassFile(final Path path) throws Exception {
6767
}
6868

6969
static class EnumPlusSwitch {
70-
enum E {E1}
7170

72-
public int m (E e) {
71+
public int m (Thread.State e) {
7372
switch (e) {
74-
case E1:
73+
case NEW:
7574
return 0;
7675
}
7776
return -1;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 7176515 8299760
27+
* @summary ExceptionInInitializerError for an enum with multiple switch statements
28+
*/
29+
30+
import java.math.RoundingMode;
31+
32+
public class EnumLookupTableExceptionInInitializer {
33+
34+
public enum MyEnum {
35+
FIRST(RoundingMode.CEILING),
36+
SECOND(RoundingMode.HALF_DOWN),
37+
THIRD(RoundingMode.UNNECESSARY),
38+
FOURTH(RoundingMode.HALF_EVEN),
39+
FIFTH(RoundingMode.HALF_DOWN),
40+
SIXTH(RoundingMode.CEILING),
41+
SEVENTH(RoundingMode.UNNECESSARY);
42+
43+
private final RoundingMode mode;
44+
45+
private MyEnum(RoundingMode mode) {
46+
switch (mode) {
47+
case CEILING:
48+
case HALF_DOWN:
49+
case UNNECESSARY:
50+
case HALF_EVEN:
51+
break;
52+
default:
53+
throw new IllegalArgumentException();
54+
}
55+
this.mode = mode;
56+
}
57+
58+
public boolean isOdd() {
59+
switch (this) {
60+
case FIRST:
61+
case THIRD:
62+
case FIFTH:
63+
case SEVENTH:
64+
return true;
65+
default:
66+
return false;
67+
}
68+
}
69+
}
70+
71+
public enum Nested {
72+
AAA(MyEnum.FIRST),
73+
BBB(MyEnum.THIRD),
74+
CCC(MyEnum.FIFTH),
75+
DDD(MyEnum.SEVENTH),
76+
EEE(MyEnum.SECOND);
77+
78+
private Nested(MyEnum x) {
79+
switch (x) {
80+
default:
81+
break;
82+
}
83+
}
84+
}
85+
86+
public static void main(String[] args) {
87+
boolean shouldBeOdd = true;
88+
for (MyEnum x : MyEnum.values()) {
89+
if (x.isOdd() != shouldBeOdd)
90+
throw new RuntimeException("failed");
91+
shouldBeOdd = !shouldBeOdd;
92+
}
93+
Nested.class.hashCode();
94+
}
95+
}

0 commit comments

Comments
 (0)