Add support for Class.getCanonicalName/getEnclosingClass

This commit is contained in:
Alexey Andreev 2019-10-18 15:10:05 +03:00
parent 28212f70ff
commit 8a91605c56
26 changed files with 478 additions and 151 deletions

View File

@ -23,14 +23,26 @@ import org.teavm.model.ClassReader;
public class DeclaringClassDependencyListener extends AbstractDependencyListener {
@Override
public void methodReached(DependencyAgent agent, MethodDependency method) {
if (method.getReference().getClassName().equals("java.lang.Class")
&& method.getReference().getName().equals("getDeclaringClass")) {
method.getVariable(0).getClassValueNode().addConsumer(t -> {
ClassReader cls = agent.getClassSource().get(t.getName());
if (cls != null && cls.getOwnerName() != null) {
agent.linkClass(cls.getOwnerName());
if (method.getReference().getClassName().equals("java.lang.Class")) {
switch (method.getReference().getName()) {
case "getEnclosingClass":
method.getVariable(0).getClassValueNode().addConsumer(t -> {
ClassReader cls = agent.getClassSource().get(t.getName());
if (cls != null && cls.getOwnerName() != null) {
agent.linkClass(cls.getOwnerName());
}
});
break;
case "getDeclaringClass": {
method.getVariable(0).getClassValueNode().addConsumer(t -> {
ClassReader cls = agent.getClassSource().get(t.getName());
if (cls != null && cls.getDeclaringClassName() != null) {
agent.linkClass(cls.getDeclaringClassName());
}
});
break;
}
});
}
}
}
}

View File

@ -1,56 +0,0 @@
/*
* Copyright 2019 konsoletyper.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.impl;
import java.io.IOException;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.dependency.MethodDependencyInfo;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodReference;
public class DeclaringClassGenerator implements Generator {
private static final MethodReference METHOD = new MethodReference(Class.class, "getDeclaringClass",
Class.class);
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
writer.append("var p").ws().append("=").ws().append("\"teavm_declaredClass\";").softNewLine();
writer.append("if").ws().append("(!").appendMethodBody(methodRef).append(".initialized").append(")")
.ws().append("{").indent().softNewLine();
MethodDependencyInfo methodDep = context.getDependency().getMethod(METHOD);
if (methodDep != null) {
for (String type : methodDep.getVariable(0).getClassValueNode().getTypes()) {
ClassReader cls = context.getClassSource().get(type);
if (cls != null) {
writer.appendClass(type).append("[p]").ws().append("=").ws();
if (cls.getOwnerName() != null) {
writer.appendClass(cls.getOwnerName());
} else {
writer.append("null");
}
writer.append(";").softNewLine();
}
}
}
writer.appendMethodBody(methodRef).append(".initialized").ws().append("=").ws().append("true;").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return ").append(context.getParameterName(1)).append("[p];").softNewLine();
}
}

View File

@ -144,12 +144,6 @@ public class JCLPlugin implements TeaVMPlugin {
installMetadata(host.getService(MetadataRegistration.class));
host.add(new DeclaringClassDependencyListener());
TeaVMJavaScriptHost jsExtension = host.getExtension(TeaVMJavaScriptHost.class);
if (jsExtension != null) {
jsExtension.add(new MethodReference(Class.class, "getDeclaringClassImpl", PlatformClass.class,
PlatformClass.class), new DeclaringClassGenerator());
}
}
private void installMetadata(MetadataRegistration reg) {

View File

@ -53,6 +53,7 @@ import org.teavm.runtime.RuntimeObject;
public class TClass<T> extends TObject implements TAnnotatedElement {
String name;
String simpleName;
String canonicalName;
private PlatformClass platformClass;
private TAnnotation[] annotationsCache;
private Map<TClass<?>, TAnnotation> annotationsByType;
@ -115,51 +116,110 @@ public class TClass<T> extends TObject implements TAnnotatedElement {
}
public String getSimpleName() {
String simpleName = getSimpleNameCache();
String simpleName = getSimpleNameCache(this);
if (simpleName == null) {
if (isArray()) {
simpleName = getComponentType().getSimpleName() + "[]";
setSimpleNameCache(simpleName);
return simpleName;
}
String name = Platform.getName(platformClass);
int lastDollar = name.lastIndexOf('$');
if (lastDollar != -1) {
name = name.substring(lastDollar + 1);
if (name.charAt(0) >= '0' && name.charAt(0) <= '9') {
name = "";
} else if (getEnclosingClass() != null) {
simpleName = Platform.getSimpleName(platformClass);
if (simpleName == null) {
simpleName = "";
}
} else {
int lastDot = name.lastIndexOf('.');
if (lastDot != -1) {
name = name.substring(lastDot + 1);
String name = Platform.getName(platformClass);
int lastDollar = name.lastIndexOf('$');
if (lastDollar != -1) {
name = name.substring(lastDollar + 1);
if (name.charAt(0) >= '0' && name.charAt(0) <= '9') {
name = "";
}
} else {
int lastDot = name.lastIndexOf('.');
if (lastDot != -1) {
name = name.substring(lastDot + 1);
}
}
simpleName = name;
}
simpleName = name;
setSimpleNameCache(simpleName);
setSimpleNameCache(this, simpleName);
}
return simpleName;
}
@DelegateTo("getSimpleNameCacheLowLevel")
private String getSimpleNameCache() {
return simpleName;
private static String getSimpleNameCache(TClass<?> self) {
return self.simpleName;
}
@Unmanaged
@PluggableDependency(ClassDependencyListener.class)
private RuntimeObject getSimpleNameCacheLowLevel() {
return Address.ofObject(this).<RuntimeClass>toStructure().simpleName;
private static RuntimeObject getSimpleNameCacheLowLevel(RuntimeClass self) {
return self.simpleNameCache;
}
@DelegateTo("setSimpleNameCacheLowLevel")
private void setSimpleNameCache(String value) {
simpleName = value;
private static void setSimpleNameCache(TClass<?> self, String value) {
self.simpleName = value;
}
@Unmanaged
private void setSimpleNameCacheLowLevel(RuntimeObject object) {
Address.ofObject(this).<RuntimeClass>toStructure().simpleName = object;
private static void setSimpleNameCacheLowLevel(RuntimeClass self, RuntimeObject object) {
self.simpleNameCache = object;
}
public String getCanonicalName() {
String result = getCanonicalNameCache();
if (result == null) {
if (isArray()) {
String componentName = getComponentType().getCanonicalName();
if (componentName == null) {
return null;
}
result = componentName + "[]";
} else if (getEnclosingClass() != null) {
if (getDeclaringClass() == null || isSynthetic()) {
return null;
}
String enclosingName = getDeclaringClass().getCanonicalName();
if (enclosingName == null) {
return null;
}
result = enclosingName + "." + getSimpleName();
} else {
result = getName();
}
setCanonicalNameCache(result);
}
return result;
}
private boolean isSynthetic() {
if (PlatformDetector.isJavaScript()) {
return (platformClass.getMetadata().getAccessLevel() & Flags.SYNTHETIC) != 0;
} else {
return (RuntimeClass.getClass(Address.ofObject(this).toStructure()).flags & RuntimeClass.SYNTHETIC) != 0;
}
}
@DelegateTo("getCanonicalNameCacheLowLevel")
private String getCanonicalNameCache() {
return canonicalName;
}
@Unmanaged
@PluggableDependency(ClassDependencyListener.class)
private RuntimeObject getCanonicalNameCacheLowLevel() {
return Address.ofObject(this).<RuntimeClass>toStructure().canonicalName;
}
@DelegateTo("setCanonicalNameCacheLowLevel")
private void setCanonicalNameCache(String value) {
canonicalName = value;
}
@Unmanaged
private void setCanonicalNameCacheLowLevel(RuntimeObject object) {
Address.ofObject(this).<RuntimeClass>toStructure().canonicalName = object;
}
public boolean isPrimitive() {
@ -178,6 +238,15 @@ public class TClass<T> extends TObject implements TAnnotatedElement {
return (platformClass.getMetadata().getFlags() & Flags.INTERFACE) != 0;
}
public boolean isLocalClass() {
return (platformClass.getMetadata().getFlags() & Flags.SYNTHETIC) != 0
&& getEnclosingClass() != null;
}
public boolean isMemberClass() {
return getDeclaringClass() != null;
}
@PluggableDependency(ClassGenerator.class)
public TClass<?> getComponentType() {
return getClass(Platform.getArrayItem(platformClass));
@ -597,11 +666,14 @@ public class TClass<T> extends TObject implements TAnnotatedElement {
}
public TClass<?> getDeclaringClass() {
PlatformClass result = getDeclaringClassImpl(getPlatformClass());
PlatformClass result = Platform.getDeclaringClass(getPlatformClass());
return result != null ? getClass(result) : null;
}
private static native PlatformClass getDeclaringClassImpl(PlatformClass cls);
public TClass<?> getEnclosingClass() {
PlatformClass result = Platform.getEnclosingClass(getPlatformClass());
return result != null ? getClass(result) : null;
}
@SuppressWarnings("unchecked")
public <U> TClass<? extends U> asSubclass(TClass<U> clazz) {

View File

@ -64,7 +64,7 @@ public class CNameProvider extends LowLevelNameProvider {
preserveFieldNames(RuntimeClass.class.getName(), "size", "flags", "tag", "canary", "name", "itemType",
"arrayType", "isSupertypeOf", "init", "enumValues", "layout", "simpleName", "superinterfaceCount",
"superinterfaces");
"superinterfaces", "simpleNameCache", "declaringClass", "enclosingClass", "canonicalName");
memberFieldNames.put(new FieldReference(RuntimeClass.class.getName(), "parent"), "superclass");
preserveFieldNames(RuntimeReference.class.getName(), "queue", "object", "next");
preserveFieldNames(RuntimeReferenceQueue.class.getName(), "first", "last");

View File

@ -667,6 +667,9 @@ public class ClassGenerator {
String initFunction = "NULL";
String superinterfaceCount = "0";
String superinterfaces = "NULL";
String simpleName = null;
String declaringClass = "NULL";
String enclosingClass = "NULL";
if (type instanceof ValueType.Object) {
String className = ((ValueType.Object) type).getClassName();
@ -682,8 +685,13 @@ public class ClassGenerator {
} else {
sizeExpr = "0";
}
if (cls != null && cls.hasModifier(ElementModifier.ENUM)) {
flags |= RuntimeClass.ENUM;
if (cls != null) {
if (cls.hasModifier(ElementModifier.ENUM)) {
flags |= RuntimeClass.ENUM;
}
if (cls.hasModifier(ElementModifier.SYNTHETIC)) {
flags |= RuntimeClass.SYNTHETIC;
}
}
List<TagRegistry.Range> ranges = tagRegistry != null ? tagRegistry.getRanges(className) : null;
tag = !context.isIncremental() && ranges != null && !ranges.isEmpty() ? ranges.get(0).lower : 0;
@ -730,6 +738,22 @@ public class ClassGenerator {
break;
}
simpleName = cls.getSimpleName();
if (cls.getDeclaringClassName() != null
&& context.getDependencies().getClass(cls.getDeclaringClassName()) != null) {
declaringClass = "(TeaVM_Class*) &" + context.getNames().forClassInstance(
ValueType.object(cls.getDeclaringClassName()));
includes.includeClass(cls.getDeclaringClassName());
}
if (cls.getOwnerName() != null
&& context.getDependencies().getClass(cls.getOwnerName()) != null) {
enclosingClass = "(TeaVM_Class*) &" + context.getNames().forClassInstance(
ValueType.object(cls.getOwnerName()));
includes.includeClass(cls.getOwnerName());
}
} else if (type instanceof ValueType.Array) {
includes.includeClass("java.lang.Object");
parent = "(TeaVM_Class*) &" + context.getNames().forClassInstance(ValueType.object("java.lang.Object"));
@ -766,13 +790,20 @@ public class ClassGenerator {
arrayTypeExpr = "NULL";
}
if (simpleName == null) {
simpleName = "NULL";
} else {
int simpleNameIndex = context.getStringPool().getStringIndex(simpleName);
simpleName = "(TeaVM_Object**) &TEAVM_GET_STRING(" + simpleNameIndex + ")";
}
includes.includePath("strings.h");
codeWriter.println(".size = " + sizeExpr + ",");
codeWriter.println(".flags = " + flags + ",");
codeWriter.println(".tag = " + tag + ",");
codeWriter.println(".canary = 0,");
codeWriter.println(".name = (TeaVM_Object**) &TEAVM_GET_STRING(" + nameRef + "),");
codeWriter.println(".simpleName = NULL,");
codeWriter.println(".simpleName = " + simpleName + ",");
codeWriter.println(".arrayType = " + arrayTypeExpr + ",");
codeWriter.println(".itemType = " + itemTypeExpr + ",");
codeWriter.println(".isSupertypeOf = &" + superTypeFunction + ",");
@ -781,6 +812,8 @@ public class ClassGenerator {
codeWriter.println(".superinterfaces = " + superinterfaces + ",");
codeWriter.println(".layout = " + layout + ",");
codeWriter.println(".enumValues = " + enumConstants + ",");
codeWriter.println(".declaringClass = " + declaringClass + ",");
codeWriter.println(".enclosingClass = " + enclosingClass + ",");
codeWriter.print(".init = " + initFunction);
if (context.isHeapDump() && type instanceof ValueType.Object) {
@ -1161,7 +1194,7 @@ public class ClassGenerator {
throw new AssertionError();
}
} else if (type instanceof ValueType.Array) {
return nameOfType(((ValueType.Array) type).getItemType()) + "[]";
return type.toString().replace('/', '.');
} else if (type == ValueType.VOID) {
return "void";
} else if (type instanceof ValueType.Object) {

View File

@ -802,7 +802,8 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor {
}
private boolean isWrappedNativeCall(MethodReader method) {
if (!method.hasModifier(ElementModifier.NATIVE)) {
if (!method.hasModifier(ElementModifier.NATIVE)
|| method.getAnnotations().get(DelegateTo.class.getName()) != null) {
return false;
}
if (method.getAnnotations().get(Variable.class.getName()) != null) {

View File

@ -28,6 +28,12 @@ public class PlatformClassMetadataIntrinsic implements Intrinsic {
RuntimeClass.class.getName(), "parent");
private static final FieldReference NAME_FIELD = new FieldReference(
RuntimeClass.class.getName(), "name");
private static final FieldReference SIMPLE_NAME_FIELD = new FieldReference(
RuntimeClass.class.getName(), "simpleName");
private static final FieldReference DECLARING_CLASS_FIELD = new FieldReference(
RuntimeClass.class.getName(), "declaringClass");
private static final FieldReference ENCLOSING_CLASS_FIELD = new FieldReference(
RuntimeClass.class.getName(), "enclosingClass");
@Override
public boolean canHandle(MethodReference method) {
@ -38,6 +44,9 @@ public class PlatformClassMetadataIntrinsic implements Intrinsic {
case "getArrayItem":
case "getSuperclass":
case "getName":
case "getSimpleName":
case "getDeclaringClass":
case "getEnclosingClass":
return true;
}
return false;
@ -56,6 +65,19 @@ public class PlatformClassMetadataIntrinsic implements Intrinsic {
context.writer().print("*");
printFieldAccess(context, invocation, NAME_FIELD);
break;
case "getSimpleName":
context.writer().print("(");
printFieldAccess(context, invocation, SIMPLE_NAME_FIELD);
context.writer().print(" ? *");
printFieldAccess(context, invocation, SIMPLE_NAME_FIELD);
context.writer().print(" : NULL)");
break;
case "getDeclaringClass":
printFieldAccess(context, invocation, DECLARING_CLASS_FIELD);
break;
case "getEnclosingClass":
printFieldAccess(context, invocation, ENCLOSING_CLASS_FIELD);
break;
}
}

View File

@ -475,7 +475,7 @@ public class Renderer implements RenderingManager {
return;
}
Set<String> classesRequiringName = findClassesRequiringName();
Map<String, RequiredClassMetadata> classesRequiringName = findRequiredClassMetadata();
int start = writer.getOffset();
try {
@ -496,7 +496,7 @@ public class Renderer implements RenderingManager {
}
private void renderClassMetadataPortion(List<PreparedClass> classes, ObjectIntMap<String> packageIndexes,
Set<String> classesRequiringName) throws IOException {
Map<String, RequiredClassMetadata> metadataRequirements) throws IOException {
writer.append("$rt_metadata([");
boolean first = true;
for (PreparedClass cls : classes) {
@ -507,7 +507,8 @@ public class Renderer implements RenderingManager {
debugEmitter.emitClass(cls.getName());
writer.appendClass(cls.getName()).append(",").ws();
if (classesRequiringName.contains(cls.getName())) {
RequiredClassMetadata requiredMetadata = metadataRequirements.get(cls.getName());
if (requiredMetadata != null && requiredMetadata.name) {
String className = cls.getName();
int dotIndex = className.lastIndexOf('.') + 1;
String packageName = className.substring(0, dotIndex);
@ -539,6 +540,33 @@ public class Renderer implements RenderingManager {
writer.append(ElementModifier.pack(cls.getClassHolder().getModifiers())).append(',').ws();
writer.append(cls.getClassHolder().getLevel().ordinal()).append(',').ws();
if (requiredMetadata == null || !(requiredMetadata.enclosingClass
|| requiredMetadata.declaringClass || requiredMetadata.simpleName)) {
writer.append("0");
} else {
writer.append('[');
if (requiredMetadata.enclosingClass && cls.getClassHolder().getOwnerName() != null) {
writer.appendClass(cls.getClassHolder().getOwnerName());
} else {
writer.append('0');
}
writer.append(',');
if (requiredMetadata.declaringClass && cls.getClassHolder().getDeclaringClassName() != null) {
writer.appendClass(cls.getClassHolder().getDeclaringClassName());
} else {
writer.append('0');
}
writer.append(',');
if (requiredMetadata.simpleName && cls.getClassHolder().getSimpleName() != null) {
writer.append("\"").append(RenderingUtil.escapeString(cls.getClassHolder().getSimpleName()))
.append("\"");
} else {
writer.append('0');
}
writer.append(']');
}
writer.append(",").ws();
MethodReader clinit = classSource.get(cls.getName()).getMethod(CLINIT_METHOD);
if (clinit != null && context.isDynamicInitializer(cls.getName())) {
writer.appendClassInit(cls.getName());
@ -561,13 +589,14 @@ public class Renderer implements RenderingManager {
writer.append("]);").newLine();
}
private ObjectIntMap<String> generatePackageMetadata(List<PreparedClass> classes, Set<String> classesRequiringName)
throws IOException {
private ObjectIntMap<String> generatePackageMetadata(List<PreparedClass> classes,
Map<String, RequiredClassMetadata> metadataRequirements) throws IOException {
PackageNode root = new PackageNode(null);
for (PreparedClass classNode : classes) {
String className = classNode.getName();
if (!classesRequiringName.contains(className)) {
RequiredClassMetadata requiredMetadata = metadataRequirements.get(className);
if (requiredMetadata == null || !requiredMetadata.name) {
continue;
}
@ -626,23 +655,50 @@ public class Renderer implements RenderingManager {
}
}
private Set<String> findClassesRequiringName() {
Set<String> classesRequiringName = new HashSet<>();
private Map<String, RequiredClassMetadata> findRequiredClassMetadata() {
Map<String, RequiredClassMetadata> requirements = new HashMap<>();
MethodDependencyInfo getNameMethod = context.getDependencyInfo().getMethod(
new MethodReference(Class.class, "getName", String.class));
if (getNameMethod != null) {
addClassesRequiringName(classesRequiringName, getNameMethod.getVariable(0).getClassValueNode().getTypes());
addClassesRequiringName(requirements, getNameMethod.getVariable(0).getClassValueNode().getTypes());
}
MethodDependencyInfo getSimpleNameMethod = context.getDependencyInfo().getMethod(
new MethodReference(Class.class, "getSimpleName", String.class));
if (getSimpleNameMethod != null) {
addClassesRequiringName(classesRequiringName,
getSimpleNameMethod.getVariable(0).getClassValueNode().getTypes());
String[] classNames = getSimpleNameMethod.getVariable(0).getClassValueNode().getTypes();
addClassesRequiringName(requirements, classNames);
for (String className : classNames) {
RequiredClassMetadata requiredMetadata = requirements.computeIfAbsent(className,
k -> new RequiredClassMetadata());
requiredMetadata.simpleName = true;
requiredMetadata.enclosingClass = true;
}
}
return classesRequiringName;
MethodDependencyInfo getDeclaringClassMethod = context.getDependencyInfo().getMethod(
new MethodReference(Class.class, "getDeclaringClass", Class.class));
if (getDeclaringClassMethod != null) {
String[] classNames = getDeclaringClassMethod.getVariable(0).getClassValueNode().getTypes();
for (String className : classNames) {
requirements.computeIfAbsent(className, k -> new RequiredClassMetadata()).declaringClass = true;
}
}
MethodDependencyInfo getEnclosingClassMethod = context.getDependencyInfo().getMethod(
new MethodReference(Class.class, "getEnclosingClass", Class.class));
if (getEnclosingClassMethod != null) {
String[] classNames = getEnclosingClassMethod.getVariable(0).getClassValueNode().getTypes();
for (String className : classNames) {
requirements.computeIfAbsent(className, k -> new RequiredClassMetadata()).enclosingClass = true;
}
}
return requirements;
}
private void addClassesRequiringName(Set<String> target, String[] source) {
private void addClassesRequiringName(Map<String, RequiredClassMetadata> target, String[] source) {
for (String typeName : source) {
if (typeName.startsWith("[")) {
if (!typeName.endsWith(";")) {
@ -654,7 +710,7 @@ public class Renderer implements RenderingManager {
}
typeName = typeName.substring(index, typeName.length() - 1).replace('/', '.');
}
target.add(typeName);
target.computeIfAbsent(typeName, k -> new RequiredClassMetadata()).name = true;
}
}
@ -1161,4 +1217,11 @@ public class Renderer implements RenderingManager {
private boolean isVirtual(MethodReference method) {
return context.isVirtual(method);
}
static class RequiredClassMetadata {
boolean name;
boolean simpleName;
boolean declaringClass;
boolean enclosingClass;
}
}

View File

@ -74,6 +74,8 @@ public class WasmClassGenerator {
DataPrimitives.ADDRESS, /* name */
DataPrimitives.ADDRESS, /* item type */
DataPrimitives.ADDRESS, /* array type */
DataPrimitives.ADDRESS, /* declaring class */
DataPrimitives.ADDRESS, /* enclosing class */
DataPrimitives.INT, /* isInstance function */
DataPrimitives.INT, /* init function */
DataPrimitives.ADDRESS, /* parent */
@ -81,7 +83,9 @@ public class WasmClassGenerator {
DataPrimitives.ADDRESS, /* interfaces */
DataPrimitives.ADDRESS, /* enum values */
DataPrimitives.ADDRESS, /* layout */
DataPrimitives.ADDRESS /* simple name */);
DataPrimitives.ADDRESS, /* simple name */
DataPrimitives.ADDRESS, /* simple name cache */
DataPrimitives.ADDRESS /* canonical name cache */);
private IntegerArray staticGcRoots = new IntegerArray(1);
private int staticGcRootsAddress;
@ -92,12 +96,14 @@ public class WasmClassGenerator {
private static final int CLASS_NAME = 5;
private static final int CLASS_ITEM_TYPE = 6;
private static final int CLASS_ARRAY_TYPE = 7;
private static final int CLASS_IS_INSTANCE = 8;
private static final int CLASS_INIT = 9;
private static final int CLASS_PARENT = 10;
private static final int CLASS_ENUM_VALUES = 13;
private static final int CLASS_LAYOUT = 14;
private static final int CLASS_SIMPLE_NAME = 15;
private static final int CLASS_DECLARING_CLASS = 8;
private static final int CLASS_ENCLOSING_CLASS = 9;
private static final int CLASS_IS_INSTANCE = 10;
private static final int CLASS_INIT = 11;
private static final int CLASS_PARENT = 12;
private static final int CLASS_ENUM_VALUES = 15;
private static final int CLASS_LAYOUT = 16;
private static final int CLASS_SIMPLE_NAME = 17;
public WasmClassGenerator(ClassReaderSource processedClassSource, ClassReaderSource classSource,
VirtualTableProvider vtableProvider, TagRegistry tagRegistry, BinaryWriter binaryWriter,
@ -275,6 +281,19 @@ public class WasmClassGenerator {
functionTable.add(names.forSupertypeFunction(ValueType.object(name)));
header.setAddress(CLASS_PARENT, parentPtr);
ClassReader cls = processedClassSource.get(name);
if (cls.getSimpleName() != null) {
header.setAddress(CLASS_SIMPLE_NAME, stringPool.getStringPointer(cls.getSimpleName()));
}
if (cls.getOwnerName() != null && processedClassSource.get(cls.getOwnerName()) != null) {
header.setAddress(CLASS_ENCLOSING_CLASS, getClassPointer(ValueType.object(cls.getOwnerName())));
}
if (cls.getDeclaringClassName() != null && processedClassSource.get(cls.getDeclaringClassName()) != null) {
header.setAddress(CLASS_DECLARING_CLASS, getClassPointer(ValueType.object(cls.getDeclaringClassName())));
}
if (vtable != null) {
fillVirtualTable(vtable, array);
}
@ -296,10 +315,14 @@ public class WasmClassGenerator {
staticGcRoots.add(binaryData.fieldLayout.get(field.getFieldName()));
}
ClassReader cls = processedClassSource.get(name);
if (cls != null && cls.hasModifier(ElementModifier.ENUM)) {
header.setAddress(CLASS_ENUM_VALUES, generateEnumValues(cls, binaryData));
flags |= RuntimeClass.ENUM;
if (cls != null) {
if (cls.hasModifier(ElementModifier.ENUM)) {
header.setAddress(CLASS_ENUM_VALUES, generateEnumValues(cls, binaryData));
flags |= RuntimeClass.ENUM;
}
if (cls.hasModifier(ElementModifier.SYNTHETIC)) {
flags |= RuntimeClass.SYNTHETIC;
}
}
if (cls != null && binaryData.start >= 0
@ -311,7 +334,6 @@ public class WasmClassGenerator {
}
header.setInt(CLASS_FLAGS, flags);
header.setAddress(CLASS_SIMPLE_NAME, 0);
return vtable != null ? wrapper : header;
}

View File

@ -25,12 +25,13 @@ import org.teavm.runtime.RuntimeClass;
public class PlatformClassMetadataIntrinsic implements WasmIntrinsic {
private static final String PLATFORM_CLASS_METADATA = "org.teavm.platform.PlatformClassMetadata";
private static final FieldReference ITEM_TYPE_FIELD = new FieldReference(
RuntimeClass.class.getName(), "itemType");
private static final FieldReference SUPERCLASS_FIELD = new FieldReference(
RuntimeClass.class.getName(), "parent");
private static final FieldReference NAME_FIELD = new FieldReference(
RuntimeClass.class.getName(), "name");
private static final String RUNTIME_CLASS = RuntimeClass.class.getName();
private static final FieldReference ITEM_TYPE_FIELD = new FieldReference(RUNTIME_CLASS, "itemType");
private static final FieldReference SUPERCLASS_FIELD = new FieldReference(RUNTIME_CLASS, "parent");
private static final FieldReference NAME_FIELD = new FieldReference(RUNTIME_CLASS, "name");
private static final FieldReference SIMPLE_NAME_FIELD = new FieldReference(RUNTIME_CLASS, "simpleName");
private static final FieldReference ENCLOSING_CLASS_FIELD = new FieldReference(RUNTIME_CLASS, "enclosingClass");
private static final FieldReference DECLARING_CLASS_FIELD = new FieldReference(RUNTIME_CLASS, "declaringClass");
@Override
public boolean isApplicable(MethodReference methodReference) {
@ -41,6 +42,9 @@ public class PlatformClassMetadataIntrinsic implements WasmIntrinsic {
case "getArrayItem":
case "getSuperclass":
case "getName":
case "getSimpleName":
case "getEnclosingClass":
case "getDeclaringClass":
return true;
}
return false;
@ -55,6 +59,12 @@ public class PlatformClassMetadataIntrinsic implements WasmIntrinsic {
return fieldAccess(manager, invocation, SUPERCLASS_FIELD);
case "getName":
return fieldAccess(manager, invocation, NAME_FIELD);
case "getSimpleName":
return fieldAccess(manager, invocation, SIMPLE_NAME_FIELD);
case "getEnclosingClass":
return fieldAccess(manager, invocation, ENCLOSING_CLASS_FIELD);
case "getDeclaringClass":
return fieldAccess(manager, invocation, DECLARING_CLASS_FIELD);
default:
return new WasmUnreachable();
}

View File

@ -30,6 +30,8 @@ class CachedClassReader extends CachedElement implements ClassReader {
GenericTypeParameter[] parameters;
GenericValueType.Object genericParent;
String owner;
String declaringClass;
String simpleName;
Set<String> interfaces;
Set<GenericValueType.Object> genericInterfaces;
Map<MethodDescriptor, CachedMethod> methods;
@ -84,4 +86,14 @@ class CachedClassReader extends CachedElement implements ClassReader {
public String getOwnerName() {
return owner;
}
@Override
public String getSimpleName() {
return simpleName;
}
@Override
public String getDeclaringClassName() {
return declaringClass;
}
}

View File

@ -59,6 +59,9 @@ public class ClassIO {
output.writeUnsigned(packModifiers(cls.readModifiers()));
output.writeUnsigned(cls.getParent() != null ? symbolTable.lookup(cls.getParent()) + 1 : 0);
output.writeUnsigned(cls.getOwnerName() != null ? symbolTable.lookup(cls.getOwnerName()) + 1 : 0);
output.writeUnsigned(cls.getDeclaringClassName() != null
? symbolTable.lookup(cls.getDeclaringClassName()) + 1 : 0);
output.writeUnsigned(cls.getSimpleName() != null ? symbolTable.lookup(cls.getSimpleName()) + 1 : 0);
output.writeUnsigned(cls.getInterfaces().size());
for (String iface : cls.getInterfaces()) {
output.writeUnsigned(symbolTable.lookup(iface));
@ -84,6 +87,11 @@ public class ClassIO {
cls.parent = parentIndex > 0 ? referenceCache.getCached(symbolTable.at(parentIndex - 1)) : null;
int ownerIndex = input.readUnsigned();
cls.owner = ownerIndex > 0 ? referenceCache.getCached(symbolTable.at(ownerIndex - 1)) : null;
int declaringClassIndex = input.readUnsigned();
cls.declaringClass = declaringClassIndex > 0
? referenceCache.getCached(symbolTable.at(declaringClassIndex - 1)) : null;
int simpleNameIndex = input.readUnsigned();
cls.simpleName = simpleNameIndex > 0 ? referenceCache.getCached(symbolTable.at(simpleNameIndex - 1)) : null;
int ifaceCount = input.readUnsigned();
Set<String> interfaces = new LinkedHashSet<>();
for (int i = 0; i < ifaceCount; ++i) {

View File

@ -26,6 +26,8 @@ public class ClassHolder extends ElementHolder implements ClassReader {
private Map<MethodDescriptor, MethodHolder> methods = new LinkedHashMap<>();
private Map<String, FieldHolder> fields = new LinkedHashMap<>();
private String ownerName;
private String simpleName;
private String declaringClassName;
public ClassHolder(String name) {
super(name);
@ -138,4 +140,22 @@ public class ClassHolder extends ElementHolder implements ClassReader {
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
@Override
public String getSimpleName() {
return simpleName;
}
public void setSimpleName(String simpleName) {
this.simpleName = simpleName;
}
@Override
public String getDeclaringClassName() {
return declaringClassName;
}
public void setDeclaringClassName(String declaringClassName) {
this.declaringClassName = declaringClassName;
}
}

View File

@ -38,4 +38,8 @@ public interface ClassReader extends ElementReader {
Collection<? extends FieldReader> getFields();
String getOwnerName();
String getSimpleName();
String getDeclaringClassName();
}

View File

@ -49,6 +49,8 @@ public final class ModelUtils {
target.addField(copyField(field));
}
target.setOwnerName(original.getOwnerName());
target.setDeclaringClassName(original.getDeclaringClassName());
target.setSimpleName(original.getSimpleName());
copyAnnotations(original.getAnnotations(), target.getAnnotations());
return target;
}

View File

@ -89,6 +89,9 @@ public class ClassRefsRenamer extends AbstractInstructionVisitor {
if (cls.getOwnerName() != null) {
renamedCls.setOwnerName(classNameMapper.apply(cls.getOwnerName()));
}
if (cls.getDeclaringClassName() != null) {
renamedCls.setDeclaringClassName(classNameMapper.apply(cls.getDeclaringClassName()));
}
rename(cls.getAnnotations(), renamedCls.getAnnotations());
for (String iface : cls.getInterfaces()) {
String mappedIfaceName = classNameMapper.apply(iface);

View File

@ -29,6 +29,7 @@ import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InnerClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.teavm.common.Graph;
import org.teavm.common.GraphUtils;
@ -302,14 +303,24 @@ public class Parser {
cls.addMethod(method);
method.updateReference(referenceCache);
}
if (node.outerClass != null) {
cls.setOwnerName(node.outerClass.replace('/', '.'));
} else {
int lastIndex = node.name.lastIndexOf('$');
if (lastIndex != -1) {
cls.setOwnerName(node.name.substring(0, lastIndex).replace('/', '.'));
}
if (node.innerClasses != null && !node.innerClasses.isEmpty()) {
for (InnerClassNode innerClassNode : node.innerClasses) {
if (node.name.equals(innerClassNode.name)) {
if (innerClassNode.outerName != null) {
cls.setDeclaringClassName(innerClassNode.outerName.replace('/', '.'));
cls.setOwnerName(cls.getDeclaringClassName());
}
cls.setSimpleName(innerClassNode.innerName);
break;
}
}
}
parseAnnotations(cls.getAnnotations(), node.visibleAnnotations, node.invisibleAnnotations);
return cls;
}

View File

@ -22,6 +22,7 @@ public class RuntimeClass extends RuntimeObject {
public static final int INITIALIZED = 1;
public static final int PRIMITIVE = 2;
public static final int ENUM = 4;
public static final int SYNTHETIC = 1024;
public static final int PRIMITIVE_SHIFT = 3;
public static final int PRIMITIVE_MASK = 15;
@ -49,6 +50,8 @@ public class RuntimeClass extends RuntimeObject {
public RuntimeObjectPtr name;
public RuntimeClass itemType;
public RuntimeClass arrayType;
public RuntimeClass declaringClass;
public RuntimeClass enclosingClass;
public IsSupertypeFunction isSupertypeOf;
public InitFunction init;
public RuntimeClass parent;
@ -56,7 +59,9 @@ public class RuntimeClass extends RuntimeObject {
public RuntimeClassPointer superinterfaces;
public Address enumValues;
public Address layout;
public RuntimeObject simpleName;
public RuntimeObjectPtr simpleName;
public RuntimeObject simpleNameCache;
public RuntimeObject canonicalName;
@Unmanaged
public static int computeCanary(int size, int tag) {

View File

@ -18,5 +18,5 @@ package org.teavm.runtime;
import org.teavm.interop.Structure;
public class RuntimeObjectPtr extends Structure {
RuntimeObject value;
public RuntimeObject value;
}

View File

@ -30,6 +30,8 @@ typedef struct TeaVM_Class {
TeaVM_Object** name;
struct TeaVM_Class* itemType;
struct TeaVM_Class* arrayType;
struct TeaVM_Class* declaringClass;
struct TeaVM_Class* enclosingClass;
int32_t (*isSupertypeOf)(struct TeaVM_Class*);
void (*init)();
struct TeaVM_Class* superclass;
@ -37,7 +39,9 @@ typedef struct TeaVM_Class {
struct TeaVM_Class** superinterfaces;
void* enumValues;
void* layout;
TeaVM_Object* simpleName;
TeaVM_Object** simpleName;
TeaVM_Object* simpleNameCache;
TeaVM_Object* canonicalName;
#if TEAVM_HEAP_DUMP
TeaVM_FieldDescriptors* fieldDescriptors;
TeaVM_StaticFieldDescriptors* staticFieldDescriptors;

View File

@ -108,8 +108,18 @@ function $rt_arraycls(cls) {
if (result === null) {
var arraycls = {};
var name = "[" + cls.$meta.binaryName;
arraycls.$meta = { item : cls, supertypes : [$rt_objcls()], primitive : false, superclass : $rt_objcls(),
name : name, binaryName : name, enum : false };
arraycls.$meta = {
item: cls,
supertypes: [$rt_objcls()],
primitive: false,
superclass: $rt_objcls(),
name: name,
binaryName: name,
enum: false,
simpleName: null,
declaringClass: null,
enclosingClass: null
};
arraycls.classObject = null;
arraycls.$array = null;
result = arraycls;
@ -121,7 +131,7 @@ function $rt_createcls() {
return {
$array : null,
classObject : null,
$meta : {
$meta: {
supertypes : [],
superclass : null
}
@ -134,6 +144,9 @@ function $rt_createPrimitiveCls(name, binaryName) {
cls.$meta.binaryName = binaryName;
cls.$meta.enum = false;
cls.$meta.item = null;
cls.$meta.simpleName = null;
cls.$meta.declaringClass = null;
cls.$meta.enclosingClass = null;
return cls;
}
var $rt_booleanclsCache = null;
@ -453,6 +466,20 @@ function $rt_metadata(data) {
m.accessLevel = data[i++];
var innerClassInfo = data[i++];
if (innerClassInfo === 0) {
m.simpleName = null;
m.declaringClass = null;
m.enclosingClass = null;
} else {
var enclosingClass = innerClassInfo[0];
m.enclosingClass = enclosingClass !== 0 ? enclosingClass : null;
var declaringClass = innerClassInfo[1];
m.declaringClass = declaringClass !== 0 ? declaringClass : null;
var simpleName = innerClassInfo[2];
m.simpleName = simpleName !== 0 ? simpleName : null;
}
var clinit = data[i++];
cls.$clinit = clinit !== 0 ? clinit : function() {};

View File

@ -246,6 +246,24 @@ public final class Platform {
return cls.getMetadata().getName();
}
@Unmanaged
@PluggableDependency(PlatformGenerator.class)
public static String getSimpleName(PlatformClass cls) {
return cls.getMetadata().getSimpleName();
}
@Unmanaged
@PluggableDependency(PlatformGenerator.class)
public static PlatformClass getEnclosingClass(PlatformClass cls) {
return cls.getMetadata().getEnclosingClass();
}
@Unmanaged
@PluggableDependency(PlatformGenerator.class)
public static PlatformClass getDeclaringClass(PlatformClass cls) {
return cls.getMetadata().getDeclaringClass();
}
@PlatformMarker(Platforms.LOW_LEVEL)
private static boolean isLowLevel() {
return false;

View File

@ -46,4 +46,16 @@ public interface PlatformClassMetadata extends JSObject {
@JSProperty
int getAccessLevel();
@JSProperty
@Unmanaged
String getSimpleName();
@JSProperty
@Unmanaged
PlatformClass getEnclosingClass();
@JSProperty
@Unmanaged
PlatformClass getDeclaringClass();
}

View File

@ -60,8 +60,13 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin
method.getResult().propagate(agent.getType("[Ljava/lang/Enum;"));
break;
case "getName":
case "getSimpleName":
method.getResult().propagate(agent.getType("java.lang.String"));
break;
case "getEnclosingClass":
case "getDeclaringClass":
method.getResult().propagate(agent.getType("java.lang.Class"));
break;
}
}

View File

@ -39,12 +39,7 @@ public class ClassTest {
@Test
public void classSimpleNameEvaluated() {
assertEquals("Object", Object.class.getSimpleName());
assertEquals("Object[]", Object[].class.getSimpleName());
assertEquals("int", int.class.getSimpleName());
assertEquals("int[]", int[].class.getSimpleName());
assertEquals("InnerClass", InnerClass.class.getSimpleName());
assertEquals("", new Object() { }.getClass().getSimpleName());
}
@Test
@ -126,8 +121,36 @@ public class ClassTest {
}
@Test
public void declaringClassFound() {
assertEquals(ClassTest.class, new A().getClass().getDeclaringClass());
public void classProperties() {
class B {
}
@SuppressWarnings("Convert2Lambda")
Runnable r = new Runnable() {
@Override
public void run() {
}
};
String testName = "org.teavm.classlib.java.lang.ClassTest";
testClassProperties(getClass(), "ClassTest", testName, null, null);
testClassProperties(new ClassTest[0].getClass(), "ClassTest[]", testName + "[]", null, null);
testClassProperties(int.class, "int", "int", null, null);
testClassProperties(new int[0].getClass(), "int[]", "int[]", null, null);
testClassProperties(new A().getClass(), "A", testName + ".A", ClassTest.class, ClassTest.class);
testClassProperties(new A[0].getClass(), "A[]", testName + ".A[]", null, null);
testClassProperties(new B().getClass(), "B", null, null, ClassTest.class);
testClassProperties(new B[0].getClass(), "B[]", null, null, null);
testClassProperties(r.getClass(), "", null, null, ClassTest.class);
}
private void testClassProperties(Class<?> cls, String expectedSimpleName, String expectedCanonicalName,
Class<?> expectedDeclaringClass, Class<?> expectedEnclosingClass) {
assertEquals(expectedSimpleName, cls.getSimpleName());
assertEquals(expectedCanonicalName, cls.getCanonicalName());
assertEquals(expectedDeclaringClass, cls.getDeclaringClass());
assertEquals(expectedEnclosingClass, cls.getEnclosingClass());
}
@Test