Reland "Enforce class access flags to be valid."
Disabling the 004-JniTest, as it contains a smali file with invalid flags.
Bug:
Change-Id: I87c16697d14333a676d9f1dd6a72330732147fe6
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index 12ae77f..7a3d631 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -14,6 +14,9 @@
/** vdex file version number for Android O (API level 26) */
public static final int ANDROID_O_VDEX_VERSION = 10;
+ // We apply Java 7 class file constraints on DEX files.
+ public static final int CORRESPONDING_CLASS_FILE_VERSION = 51;
+
public static final int DEX_MAGIC_SIZE = 8;
public static final int MAGIC_OFFSET = 0;
diff --git a/src/main/java/com/android/tools/r8/dex/DexFileReader.java b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
index e3559fe..812273b 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InstructionFactory;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.Descriptor;
@@ -637,10 +638,14 @@
DexType superclass = superclassIdx == NO_INDEX ? null : indexedItems.getType(superclassIdx);
int srcIdx = sourceFileIndices[i];
DexString source = srcIdx == NO_INDEX ? null : indexedItems.getString(srcIdx);
- // fix annotations.
DexType type = indexedItems.getType(classIndices[i]);
ClassAccessFlags flags = ClassAccessFlags.fromDexAccessFlags(accessFlags[i]);
- DexClass clazz;
+ // Check if constraints from
+ // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
+ if (!flags.areValid(Constants.CORRESPONDING_CLASS_FILE_VERSION)) {
+ throw new CompilationError("Class " + type.toSourceString()
+ + " has illegal access flags. Found: " + flags, origin);
+ }
DexEncodedField[] staticFields = DexEncodedField.EMPTY_ARRAY;
DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY;
DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
@@ -675,22 +680,21 @@
AttributesAndAnnotations attrs =
new AttributesAndAnnotations(type, annotationsDirectory.clazz, dexItemFactory);
- clazz =
- classKind.create(
- type,
- Kind.DEX,
- origin,
- flags,
- superclass,
- typeListAt(interfacesOffsets[i]),
- source,
- attrs.getEnclosingMethodAttribute(),
- attrs.getInnerClasses(),
- attrs.getAnnotations(),
- staticFields,
- instanceFields,
- directMethods,
- virtualMethods);
+ DexClass clazz = classKind.create(
+ type,
+ Kind.DEX,
+ origin,
+ flags,
+ superclass,
+ typeListAt(interfacesOffsets[i]),
+ source,
+ attrs.getEnclosingMethodAttribute(),
+ attrs.getInnerClasses(),
+ attrs.getAnnotations(),
+ staticFields,
+ instanceFields,
+ directMethods,
+ virtualMethods);
classCollection.accept(clazz); // Update the application object.
}
}
@@ -937,7 +941,7 @@
MethodHandleType type = MethodHandleType.getKind(file.getUshort());
file.getUshort(); // unused
int indexFieldOrMethod = file.getUshort();
- Descriptor<? extends DexItem, ? extends Descriptor<?,?>> fieldOrMethod;
+ Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod;
switch (type) {
case INSTANCE_GET:
case INSTANCE_PUT:
diff --git a/src/main/java/com/android/tools/r8/errors/CompilationError.java b/src/main/java/com/android/tools/r8/errors/CompilationError.java
index 060699e..4409816 100644
--- a/src/main/java/com/android/tools/r8/errors/CompilationError.java
+++ b/src/main/java/com/android/tools/r8/errors/CompilationError.java
@@ -3,11 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.errors;
+import com.android.tools.r8.origin.Origin;
+
/**
* Exception to signal an compilation error.
- *
- * This is always an expected error and considered a user input issue.
- * A user-understandable message must be provided.
+ * <p>
+ * This is always an expected error and considered a user input issue. A user-understandable message
+ * must be provided.
*/
public class CompilationError extends RuntimeException {
@@ -18,4 +20,8 @@
public CompilationError(String message, Throwable cause) {
super(message, cause);
}
+
+ public CompilationError(String message, Origin origin) {
+ super(origin + ": " + message);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 9f0471e..e2654aa 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -166,20 +166,26 @@
public String toSmaliString() {
- return toString();
+ return toStringInternal(true);
}
@Override
public String toString() {
+ return toStringInternal(false);
+ }
+
+ private String toStringInternal(boolean ignoreSuper) {
List<String> names = getNames();
List<BooleanSupplier> predicates = getPredicates();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < names.size(); i++) {
if (predicates.get(i).getAsBoolean()) {
- if (builder.length() > 0) {
- builder.append(' ');
+ if (!ignoreSuper || !names.get(i).equals("super")) {
+ if (builder.length() > 0) {
+ builder.append(' ');
+ }
+ builder.append(names.get(i));
}
- builder.append(names.get(i));
}
}
return builder.toString();
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 6430924..a72c1bc 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -33,6 +33,7 @@
.add("abstract")
.add("annotation")
.add("enum")
+ .add("super")
.build();
}
@@ -44,6 +45,7 @@
.add(this::isAbstract)
.add(this::isAnnotation)
.add(this::isEnum)
+ .add(this::isSuper)
.build();
}
@@ -80,6 +82,27 @@
return flags;
}
+ /**
+ * Checks whether the constraints from
+ * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
+ */
+ public boolean areValid(int majorVersion) {
+ if (isInterface()) {
+ // We ignore the super flags prior to JDK 9, as so did the VM.
+ if ((majorVersion >= 53) && isSuper()) {
+ return false;
+ }
+ // We require interfaces to be abstract from JDK 7 onwards. Old versions of javac seem to
+ // have produced package-info classes that are interfaces but not abstract.
+ if ((majorVersion >= 51) && (!isAbstract())) {
+ return false;
+ }
+ return !isFinal() && !isEnum();
+ } else {
+ return !isAnnotation() && (!isFinal() || !isAbstract());
+ }
+ }
+
public boolean isInterface() {
return isSet(Constants.ACC_INTERFACE);
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 868ac10..ba8a672 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -8,6 +8,7 @@
import static org.objectweb.asm.Opcodes.ASM6;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -31,6 +32,7 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
@@ -100,6 +102,7 @@
}
private static class CreateDexClassVisitor extends ClassVisitor {
+
private final Origin origin;
private final ClassKind classKind;
private final JarApplicationReader application;
@@ -163,6 +166,21 @@
this.version = version;
accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
type = application.getTypeFromName(name);
+ // Check if constraints from
+ // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
+ if (!accessFlags.areValid(getMajorVersion())) {
+ throw new CompilationError("Illegal class file: Class " + name
+ + " has invalid access flags. Found: " + accessFlags.toString(), origin);
+ }
+ if (superName == null && !name.equals(Constants.JAVA_LANG_OBJECT_NAME)) {
+ throw new CompilationError("Illegal class file: Class " + name
+ + " is missing a super type.", origin);
+ }
+ if (accessFlags.isInterface()
+ && !Objects.equals(superName, Constants.JAVA_LANG_OBJECT_NAME)) {
+ throw new CompilationError("Illegal class file: Interface " + name
+ + " must extend class java.lang.Object. Found: " + Objects.toString(superName), origin);
+ }
assert superName != null || name.equals(Constants.JAVA_LANG_OBJECT_NAME);
superType = superName == null ? null : application.getTypeFromName(superName);
this.interfaces = application.getTypeListFromNames(interfaces);
@@ -178,8 +196,8 @@
}
if (debug != null) {
getAnnotations().add(
- DexAnnotation.createSourceDebugExtensionAnnotation(
- new DexValueString(application.getString(debug)), application.getFactory()));
+ DexAnnotation.createSourceDebugExtensionAnnotation(
+ new DexValueString(application.getString(debug)), application.getFactory()));
}
}
@@ -260,6 +278,14 @@
}
return annotations;
}
+
+ private int getMajorVersion() {
+ return version & 0xFFFF;
+ }
+
+ private int getMinorVersion() {
+ return ((version >> 16) & 0xFFFF);
+ }
}
private static DexAnnotationSet createAnnotationSet(List<DexAnnotation> annotations) {
@@ -269,6 +295,7 @@
}
private static class CreateFieldVisitor extends FieldVisitor {
+
private final CreateDexClassVisitor parent;
private final int access;
private final String name;
@@ -366,6 +393,7 @@
}
private static class CreateMethodVisitor extends MethodVisitor {
+
private final int access;
private final String name;
private final String desc;
@@ -541,6 +569,7 @@
}
private static class CreateAnnotationVisitor extends AnnotationVisitor {
+
private final JarApplicationReader application;
private final BiConsumer<List<DexString>, List<DexValue>> onVisitEnd;
private List<DexString> names = null;
@@ -586,7 +615,7 @@
private void addElement(String name, DexValue value) {
if (name != null) {
- if (names == null){
+ if (names == null) {
names = new ArrayList<>();
}
names.add(application.getString(name));
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/DirectoryClassFileProvider.java
index eba7f8e..e8ee800 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryClassFileProvider.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.isClassFile;
import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.Resource;
@@ -41,10 +42,9 @@
if (child.isDirectory()) {
collectClassDescriptors(child.toPath(), result);
} else {
- String relative = root.relativize(child.toPath()).toString();
- if (relative.endsWith(CLASS_EXTENSION)) {
- result.add("L" + relative.substring(
- 0, relative.length() - CLASS_EXTENSION.length()) + ";");
+ Path relative = root.relativize(child.toPath());
+ if (isClassFile(relative)) {
+ result.add(DescriptorUtils.guessTypeDescriptor(relative));
}
}
}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 5eb7168..410f281 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -855,6 +855,9 @@
.put("960-default-smali", TestCondition.match(TestCondition.R8_COMPILER))
.put("966-default-conflict", TestCondition.match(TestCondition.R8_COMPILER))
.put("972-iface-super-multidex", TestCondition.match(TestCondition.R8_COMPILER))
+ // These tests have illegal class flag combinations, so we reject them.
+ .put("161-final-abstract-class", TestCondition.any())
+ .put("004-JniTest", TestCondition.any())
.build();
// Tests that does not have dex input for some toolchains.
diff --git a/src/test/smali/illegal-invokes/Iface.smali b/src/test/smali/illegal-invokes/Iface.smali
index d66777c..5f074dce 100644
--- a/src/test/smali/illegal-invokes/Iface.smali
+++ b/src/test/smali/illegal-invokes/Iface.smali
@@ -2,7 +2,7 @@
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
-.class interface public LIface;
+.class interface public abstract LIface;
.super Ljava/lang/Object;
.method public abstract bar()V