Class writing support for module attributes
MOE_MIGRATED_REVID=182568594
diff --git a/java/com/google/turbine/bytecode/Attribute.java b/java/com/google/turbine/bytecode/Attribute.java
index 0700744..29efb60 100644
--- a/java/com/google/turbine/bytecode/Attribute.java
+++ b/java/com/google/turbine/bytecode/Attribute.java
@@ -19,6 +19,7 @@
import com.google.common.collect.ImmutableList;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo;
import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo;
import com.google.turbine.model.Const.Value;
import java.util.List;
@@ -39,7 +40,8 @@
DEPRECATED("Deprecated"),
RUNTIME_VISIBLE_TYPE_ANNOTATIONS("RuntimeVisibleTypeAnnotations"),
RUNTIME_INVISIBLE_TYPE_ANNOTATIONS("RuntimeInvisibleTypeAnnotations"),
- METHOD_PARAMETERS("MethodParameters");
+ METHOD_PARAMETERS("MethodParameters"),
+ MODULE("Module");
private final String signature;
@@ -288,4 +290,23 @@
return Kind.METHOD_PARAMETERS;
}
}
+
+ /** A JVMS §4.7.25 Module attribute. */
+ class Module implements Attribute {
+
+ private final ModuleInfo module;
+
+ public Module(ModuleInfo module) {
+ this.module = module;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.MODULE;
+ }
+
+ public ModuleInfo module() {
+ return module;
+ }
+ }
}
diff --git a/java/com/google/turbine/bytecode/AttributeWriter.java b/java/com/google/turbine/bytecode/AttributeWriter.java
index 4eece56..8179bc4 100644
--- a/java/com/google/turbine/bytecode/AttributeWriter.java
+++ b/java/com/google/turbine/bytecode/AttributeWriter.java
@@ -27,6 +27,12 @@
import com.google.turbine.bytecode.Attribute.TypeAnnotations;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo;
import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.ExportInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.OpenInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.ProvideInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.RequireInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.UseInfo;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo;
import com.google.turbine.model.Const;
import java.util.List;
@@ -78,6 +84,9 @@
case METHOD_PARAMETERS:
writeMethodParameters((Attribute.MethodParameters) attribute);
break;
+ case MODULE:
+ writeModule((Attribute.Module) attribute);
+ break;
default:
throw new AssertionError(attribute.kind());
}
@@ -203,4 +212,60 @@
output.writeShort(parameter.access());
}
}
+
+ private void writeModule(Attribute.Module attribute) {
+ ModuleInfo module = attribute.module();
+
+ ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
+
+ tmp.writeShort(pool.moduleInfo(module.name()));
+ tmp.writeShort(module.flags());
+ tmp.writeShort(pool.utf8(module.version()));
+
+ tmp.writeShort(module.requires().size());
+ for (RequireInfo require : module.requires()) {
+ tmp.writeShort(pool.moduleInfo(require.moduleName()));
+ tmp.writeShort(require.flags());
+ tmp.writeShort(pool.utf8(require.version()));
+ }
+
+ tmp.writeShort(module.exports().size());
+ for (ExportInfo export : module.exports()) {
+ tmp.writeShort(pool.packageInfo(export.moduleName()));
+ tmp.writeShort(export.flags());
+ tmp.writeShort(export.modules().size());
+ for (String exportedModule : export.modules()) {
+ tmp.writeShort(pool.moduleInfo(exportedModule));
+ }
+ }
+
+ tmp.writeShort(module.opens().size());
+ for (OpenInfo opens : module.opens()) {
+ tmp.writeShort(pool.packageInfo(opens.moduleName()));
+ tmp.writeShort(opens.flags());
+ tmp.writeShort(opens.modules().size());
+ for (String openModule : opens.modules()) {
+ tmp.writeShort(pool.moduleInfo(openModule));
+ }
+ }
+
+ tmp.writeShort(module.uses().size());
+ for (UseInfo use : module.uses()) {
+ tmp.writeShort(pool.classInfo(use.descriptor()));
+ }
+
+ tmp.writeShort(module.provides().size());
+ for (ProvideInfo provide : module.provides()) {
+ tmp.writeShort(pool.classInfo(provide.descriptor()));
+ tmp.writeShort(provide.implDescriptors().size());
+ for (String impl : provide.implDescriptors()) {
+ tmp.writeShort(pool.classInfo(impl));
+ }
+ }
+
+ byte[] data = tmp.toByteArray();
+ output.writeShort(pool.utf8(attribute.kind().signature()));
+ output.writeInt(data.length);
+ output.write(data);
+ }
}
diff --git a/java/com/google/turbine/bytecode/ClassWriter.java b/java/com/google/turbine/bytecode/ClassWriter.java
index 42aff6c..4a89ec8 100644
--- a/java/com/google/turbine/bytecode/ClassWriter.java
+++ b/java/com/google/turbine/bytecode/ClassWriter.java
@@ -31,8 +31,10 @@
private static final int MAGIC = 0xcafebabe;
private static final int MINOR_VERSION = 0;
- // TODO(cushon): configuration?
+ // use the lowest classfile version possible given the class file features
+ // TODO(cushon): is there a reason to support --release?
private static final int MAJOR_VERSION = 52;
+ private static final int MODULE_MAJOR_VERSION = 53;
/** Writes a {@link ClassFile} to bytecode. */
public static byte[] writeClass(ClassFile classfile) {
@@ -54,7 +56,7 @@
writeMethod(pool, output, m);
}
writeAttributes(pool, output, LowerAttributes.classAttributes(classfile));
- return finishClass(pool, output);
+ return finishClass(pool, output, classfile);
}
private static void writeMethod(
@@ -89,6 +91,8 @@
switch (e.kind()) {
case CLASS_INFO:
case STRING:
+ case MODULE:
+ case PACKAGE:
output.writeShort(((IntValue) value).value());
break;
case INTEGER:
@@ -112,11 +116,12 @@
}
}
- private static byte[] finishClass(ConstantPool pool, ByteArrayDataOutput body) {
+ private static byte[] finishClass(
+ ConstantPool pool, ByteArrayDataOutput body, ClassFile classfile) {
ByteArrayDataOutput result = ByteStreams.newDataOutput();
result.writeInt(MAGIC);
result.writeShort(MINOR_VERSION);
- result.writeShort(MAJOR_VERSION);
+ result.writeShort(classfile.module() != null ? MODULE_MAJOR_VERSION : MAJOR_VERSION);
writeConstantPool(pool, result);
result.write(body.toByteArray());
return result.toByteArray();
diff --git a/java/com/google/turbine/bytecode/ConstantPool.java b/java/com/google/turbine/bytecode/ConstantPool.java
index 2f3141a..b423cfc 100644
--- a/java/com/google/turbine/bytecode/ConstantPool.java
+++ b/java/com/google/turbine/bytecode/ConstantPool.java
@@ -40,6 +40,8 @@
private final Map<Double, Integer> doublePool = new HashMap<>();
private final Map<Float, Integer> floatPool = new HashMap<>();
private final Map<Long, Integer> longPool = new HashMap<>();
+ private final Map<Integer, Integer> modulePool = new HashMap<>();
+ private final Map<Integer, Integer> packagePool = new HashMap<>();
private final List<Entry> constants = new ArrayList<>();
@@ -56,6 +58,8 @@
case INTEGER:
case UTF8:
case FLOAT:
+ case MODULE:
+ case PACKAGE:
return 1;
case LONG:
case DOUBLE:
@@ -158,6 +162,30 @@
return index;
}
+ /** Adds a CONSTANT_Module_info entry to the pool. */
+ int moduleInfo(String value) {
+ Objects.requireNonNull(value);
+ int utf8 = utf8(value);
+ if (modulePool.containsKey(utf8)) {
+ return modulePool.get(utf8);
+ }
+ int index = insert(new Entry(Kind.MODULE, new IntValue(utf8)));
+ modulePool.put(utf8, index);
+ return index;
+ }
+
+ /** Adds a CONSTANT_Package_info entry to the pool. */
+ int packageInfo(String value) {
+ Objects.requireNonNull(value);
+ int utf8 = utf8(value);
+ if (packagePool.containsKey(utf8)) {
+ return packagePool.get(utf8);
+ }
+ int index = insert(new Entry(Kind.PACKAGE, new IntValue(utf8)));
+ packagePool.put(utf8, index);
+ return index;
+ }
+
private int insert(Entry key) {
int entry = nextEntry;
constants.add(key);
@@ -176,7 +204,9 @@
DOUBLE(6),
FLOAT(4),
LONG(5),
- UTF8(1);
+ UTF8(1),
+ MODULE(19),
+ PACKAGE(20);
private final short tag;
diff --git a/java/com/google/turbine/bytecode/LowerAttributes.java b/java/com/google/turbine/bytecode/LowerAttributes.java
index 1752456..67ef2b4 100644
--- a/java/com/google/turbine/bytecode/LowerAttributes.java
+++ b/java/com/google/turbine/bytecode/LowerAttributes.java
@@ -42,6 +42,9 @@
if (classfile.signature() != null) {
attributes.add(new Signature(classfile.signature()));
}
+ if (classfile.module() != null) {
+ attributes.add(new Attribute.Module(classfile.module()));
+ }
return attributes;
}
diff --git a/javatests/com/google/turbine/bytecode/ClassWriterTest.java b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
index 2247812..e544c15 100644
--- a/javatests/com/google/turbine/bytecode/ClassWriterTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
@@ -24,6 +24,7 @@
import com.google.common.io.ByteStreams;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
+import com.google.turbine.testing.AsmUtils;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.file.JavacFileManager;
@@ -42,6 +43,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.objectweb.asm.ModuleVisitor;
+import org.objectweb.asm.Opcodes;
@RunWith(JUnit4.class)
public class ClassWriterTest {
@@ -108,4 +111,43 @@
assertThat(reader.classInfo(entry.getKey())).isEqualTo(entry.getValue());
}
}
+
+ @Test
+ public void module() throws Exception {
+
+ org.objectweb.asm.ClassWriter cw = new org.objectweb.asm.ClassWriter(0);
+
+ cw.visit(53, /* access= */ 53, "module-info", null, null, null);
+
+ ModuleVisitor mv = cw.visitModule("mod", Opcodes.ACC_OPEN, "mod-ver");
+
+ mv.visitRequire("r1", Opcodes.ACC_TRANSITIVE, "r1-ver");
+ mv.visitRequire("r2", Opcodes.ACC_STATIC_PHASE, "r2-ver");
+ mv.visitRequire("r3", Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE, "r3-ver");
+
+ mv.visitExport("e1", Opcodes.ACC_SYNTHETIC, "e1m1", "e1m2", "e1m3");
+ mv.visitExport("e2", Opcodes.ACC_MANDATED, "e2m1", "e2m2");
+ mv.visitExport("e3", /* access= */ 0, "e3m1");
+
+ mv.visitOpen("o1", Opcodes.ACC_SYNTHETIC, "o1m1", "o1m2", "o1m3");
+ mv.visitOpen("o2", Opcodes.ACC_MANDATED, "o2m1", "o2m2");
+ mv.visitOpen("o3", /* access= */ 0, "o3m1");
+
+ mv.visitUse("u1");
+ mv.visitUse("u2");
+ mv.visitUse("u3");
+ mv.visitUse("u4");
+
+ mv.visitProvide("p1", "p1i1", "p1i2");
+ mv.visitProvide("p2", "p2i1", "p2i2", "p2i3");
+
+ byte[] inputBytes = cw.toByteArray();
+ byte[] outputBytes = ClassWriter.writeClass(ClassReader.read("module-info", inputBytes));
+
+ assertThat(AsmUtils.textify(inputBytes)).isEqualTo(AsmUtils.textify(outputBytes));
+
+ // test a round trip
+ outputBytes = ClassWriter.writeClass(ClassReader.read("module-info", outputBytes));
+ assertThat(AsmUtils.textify(inputBytes)).isEqualTo(AsmUtils.textify(outputBytes));
+ }
}
diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java
index 73a8f0e..0cc5e92 100644
--- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java
+++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java
@@ -29,9 +29,9 @@
import com.google.common.jimfs.Jimfs;
import com.google.turbine.binder.Binder;
import com.google.turbine.binder.ClassPathBinder;
-import com.google.turbine.bytecode.AsmUtils;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.parse.Parser;
+import com.google.turbine.testing.AsmUtils;
import com.google.turbine.tree.Tree;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.JavacTool;
diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java
index a418a4e..16ea1ce 100644
--- a/javatests/com/google/turbine/lower/LowerTest.java
+++ b/javatests/com/google/turbine/lower/LowerTest.java
@@ -33,13 +33,13 @@
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.MethodSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
-import com.google.turbine.bytecode.AsmUtils;
import com.google.turbine.bytecode.ByteReader;
import com.google.turbine.bytecode.ConstantPoolReader;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.parse.Parser;
+import com.google.turbine.testing.AsmUtils;
import com.google.turbine.type.Type;
import java.io.IOException;
import java.io.OutputStream;
diff --git a/javatests/com/google/turbine/bytecode/AsmUtils.java b/javatests/com/google/turbine/testing/AsmUtils.java
similarity index 97%
rename from javatests/com/google/turbine/bytecode/AsmUtils.java
rename to javatests/com/google/turbine/testing/AsmUtils.java
index 2591652..5b5e102 100644
--- a/javatests/com/google/turbine/bytecode/AsmUtils.java
+++ b/javatests/com/google/turbine/testing/AsmUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.turbine.bytecode;
+package com.google.turbine.testing;
import java.io.PrintWriter;
import java.io.StringWriter;