Fix class reading of deficient constant types
char, byte, and short are encoded as ints in bytecode, so stringifying
bytecode constant chars didn't work.
Also fix a latent bug with unary operator evaluation.
MOE_MIGRATED_REVID=138215540
diff --git a/java/com/google/turbine/binder/ConstEvaluator.java b/java/com/google/turbine/binder/ConstEvaluator.java
index df8ae02..720586e 100644
--- a/java/com/google/turbine/binder/ConstEvaluator.java
+++ b/java/com/google/turbine/binder/ConstEvaluator.java
@@ -37,6 +37,7 @@
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.model.Const;
+import com.google.turbine.model.Const.Value;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.tree.Tree;
@@ -329,70 +330,70 @@
}
switch (t.op()) {
case NOT:
- switch (expr.constantTypeKind()) {
- case BOOLEAN:
- return new Const.BooleanValue(!expr.asBoolean().value());
- default:
- throw new AssertionError(expr.constantTypeKind());
- }
+ return unaryNegate(expr);
case BITWISE_COMP:
- switch (expr.constantTypeKind()) {
- case INT:
- return new Const.IntValue(~expr.asInteger().value());
- case LONG:
- return new Const.LongValue(~expr.asLong().value());
- case BYTE:
- return new Const.ByteValue((byte) ~expr.asByte().value());
- case SHORT:
- return new Const.ShortValue((short) ~expr.asShort().value());
- case CHAR:
- return new Const.CharValue((char) ~expr.asChar().value());
- default:
- throw new AssertionError(expr.constantTypeKind());
- }
+ return bitwiseComp(expr);
case UNARY_PLUS:
- switch (expr.constantTypeKind()) {
- case INT:
- return new Const.IntValue(+expr.asInteger().value());
- case LONG:
- return new Const.LongValue(+expr.asLong().value());
- case BYTE:
- return new Const.ByteValue((byte) +expr.asByte().value());
- case SHORT:
- return new Const.ShortValue((short) +expr.asShort().value());
- case CHAR:
- return new Const.CharValue((char) +expr.asChar().value());
- case FLOAT:
- return new Const.FloatValue(+expr.asFloat().value());
- case DOUBLE:
- return new Const.DoubleValue(+expr.asDouble().value());
- default:
- throw new AssertionError(expr.constantTypeKind());
- }
+ return unaryPlus(expr);
case NEG:
- switch (expr.constantTypeKind()) {
- case INT:
- return new Const.IntValue(-expr.asInteger().value());
- case BYTE:
- return new Const.ByteValue((byte) -expr.asByte().value());
- case SHORT:
- return new Const.ShortValue((short) -expr.asShort().value());
- case CHAR:
- return new Const.CharValue((char) -expr.asChar().value());
- case LONG:
- return new Const.LongValue(-expr.asLong().value());
- case FLOAT:
- return new Const.FloatValue(-expr.asFloat().value());
- case DOUBLE:
- return new Const.DoubleValue(-expr.asDouble().value());
- default:
- throw new AssertionError(expr.constantTypeKind());
- }
+ return unaryMinus(expr);
default:
throw new AssertionError(t.op());
}
}
+ private Value unaryNegate(Value expr) {
+ switch (expr.constantTypeKind()) {
+ case BOOLEAN:
+ return new Const.BooleanValue(!expr.asBoolean().value());
+ default:
+ throw new AssertionError(expr.constantTypeKind());
+ }
+ }
+
+ private Value bitwiseComp(Value expr) {
+ expr = promoteUnary(expr);
+ switch (expr.constantTypeKind()) {
+ case INT:
+ return new Const.IntValue(~expr.asInteger().value());
+ case LONG:
+ return new Const.LongValue(~expr.asLong().value());
+ default:
+ throw new AssertionError(expr.constantTypeKind());
+ }
+ }
+
+ private Value unaryPlus(Value expr) {
+ expr = promoteUnary(expr);
+ switch (expr.constantTypeKind()) {
+ case INT:
+ return new Const.IntValue(+expr.asInteger().value());
+ case LONG:
+ return new Const.LongValue(+expr.asLong().value());
+ case FLOAT:
+ return new Const.FloatValue(+expr.asFloat().value());
+ case DOUBLE:
+ return new Const.DoubleValue(+expr.asDouble().value());
+ default:
+ throw new AssertionError(expr.constantTypeKind());
+ }
+ }
+
+ private Value unaryMinus(Value expr) {
+ expr = promoteUnary(expr);
+ switch (expr.constantTypeKind()) {
+ case INT:
+ return new Const.IntValue(-expr.asInteger().value());
+ case LONG:
+ return new Const.LongValue(-expr.asLong().value());
+ case FLOAT:
+ return new Const.FloatValue(-expr.asFloat().value());
+ case DOUBLE:
+ return new Const.DoubleValue(-expr.asDouble().value());
+ default:
+ throw new AssertionError(expr.constantTypeKind());
+ }
+ }
private Const.Value evalCast(TypeCast t) {
Const.Value expr = evalValue(t.expr());
if (expr == null) {
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
index 16aabc5..240c6c1 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
@@ -320,9 +320,25 @@
Type type = null;
int access = cfi.access();
Const.Value value = cfi.value();
- if (value != null && cfi.descriptor().equals("Z")) {
- // boolean constants are encoded as integers
- value = new Const.BooleanValue(value.asInteger().value() == 1);
+ if (value != null) {
+ // TODO(b/32626659): this is not bug-compatible with javac
+ switch (cfi.descriptor()) {
+ case "Z":
+ // boolean constants are encoded as integers
+ value = new Const.BooleanValue(value.asInteger().value() != 0);
+ break;
+ case "C":
+ value = new Const.CharValue(value.asChar().value());
+ break;
+ case "S":
+ value = new Const.ShortValue(value.asShort().value());
+ break;
+ case "B":
+ value = new Const.ByteValue(value.asByte().value());
+ break;
+ default:
+ break;
+ }
}
fields.add(new FieldInfo(fieldSym, type, access, ImmutableList.of(), null, value));
}
diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
index 99d4123..a7f1e26 100644
--- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java
+++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
@@ -263,6 +263,7 @@
"long_expression.test",
"const_nonfinal.test",
"enum_abstract.test",
+ "deficient_types_classfile.test",
};
List<Object[]> tests =
ImmutableList.copyOf(testCases).stream().map(x -> new Object[] {x}).collect(toList());
diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java
index 42f395d..9bfe29f 100644
--- a/javatests/com/google/turbine/lower/LowerTest.java
+++ b/javatests/com/google/turbine/lower/LowerTest.java
@@ -43,17 +43,24 @@
import com.google.turbine.parse.Parser;
import com.google.turbine.type.Type;
import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.TypePath;
@@ -61,6 +68,8 @@
@RunWith(JUnit4.class)
public class LowerTest {
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
private static final ImmutableList<Path> BOOTCLASSPATH =
ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
@@ -329,4 +338,35 @@
assertThat(path[0].getStep(1)).isEqualTo(TypePath.ARRAY_ELEMENT);
assertThat(path[0].getStepArgument(1)).isEqualTo(0);
}
+
+ @Test
+ public void invalidBooleanConstant() throws Exception {
+ Path lib = temporaryFolder.newFile("lib.jar").toPath();
+ try (OutputStream os = Files.newOutputStream(lib);
+ JarOutputStream jos = new JarOutputStream(os)) {
+ jos.putNextEntry(new JarEntry("Lib.class"));
+
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(52, Opcodes.ACC_SUPER, "Lib", null, "java/lang/Object", null);
+ FieldVisitor fv =
+ cw.visitField(
+ Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, "CONST", "Z", null, Integer.MAX_VALUE);
+ fv.visitEnd();
+ cw.visitEnd();
+ jos.write(cw.toByteArray());
+ }
+
+ ImmutableMap<String, String> input =
+ ImmutableMap.of(
+ "Test.java", "class Test { static final boolean CONST = Lib.CONST || false; }");
+
+ Map<String, byte[]> actual =
+ IntegrationTestSupport.runTurbine(input, ImmutableList.of(lib), BOOTCLASSPATH);
+
+ Map<String, byte[]> expected =
+ IntegrationTestSupport.runJavac(input, ImmutableList.of(lib), BOOTCLASSPATH);
+
+ assertThat(IntegrationTestSupport.dump(actual))
+ .isEqualTo(IntegrationTestSupport.dump(expected));
+ }
}
diff --git a/javatests/com/google/turbine/lower/testdata/deficient_types_classfile.test b/javatests/com/google/turbine/lower/testdata/deficient_types_classfile.test
new file mode 100644
index 0000000..2ed33dd
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/deficient_types_classfile.test
@@ -0,0 +1,13 @@
+%%% Lib.java %%%
+class Lib {
+ static final char C = '/';
+ static final char S = (short) 1;
+ static final char B = (byte) 2;
+}
+
+=== Test.java ===
+class Test {
+ static final String A = "" + Lib.C;
+ static final String B = "" + Lib.S;
+ static final String C = "" + Lib.B;
+}
\ No newline at end of file