When class metadata is disabled, use the class seed functions to derive predicable names that can be deobfuscated via the symbol map files.
This implementation should be reconsidered as part of a larger cleanup of how class literals are modeled.
Patch by: bobv
Review by: scottb
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5226 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassLiteral.java
index 4bab76f..bd1f230 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassLiteral.java
@@ -65,6 +65,9 @@
assert (type instanceof JClassType);
JClassType classType = (JClassType) type;
+ // createForClass wants a reference to the seed function
+ call.getArgs().add(program.getLiteralClassSeed(classType));
+
JLiteral superclassLiteral;
if (classType.extnds != null) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 2f8b5ea..4f7a170 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -767,9 +767,6 @@
return classLiteral;
}
- /**
- * TODO: unreferenced; remove this and JClassSeed?
- */
public JClassSeed getLiteralClassSeed(JClassType type) {
// could be interned
return new JClassSeed(createSourceInfoSynthetic(JProgram.class,
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index db0d928..a36e497 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -467,17 +467,16 @@
JavaASTGenerationVisitor.class, "Disabled class metadata");
JMethod nameMethod = program.getIndexedMethod("Class.getName");
+ JMethod simpleNameMethod = program.getIndexedMethod("Class.getSimpleName");
- // this.hashCode()
- JMethodCall hashCall = new JMethodCall(info, program.getExprThisRef(info, (JClassType) currentClass),
- program.getIndexedMethod("Object.hashCode"));
+ // this.getNameFromClassSeed()
+ JMethodCall seedCall = new JMethodCall(info,
+ program.getExprThisRef(info, (JClassType) currentClass),
+ program.getIndexedMethod("Class.getNameFromClassSeed"));
- // "Class$" + hashCode()
- JBinaryOperation op = new JBinaryOperation(info, program.getTypeJavaLangString(),
- JBinaryOperator.ADD, program.getLiteralString(info, "Class$"),
- hashCall);
-
- implementMethod(nameMethod, op);
+ implementMethod(nameMethod, seedCall);
+ implementMethod(simpleNameMethod, new CloneExpressionVisitor(
+ program).cloneExpression(seedCall));
// Forget the superclass
JMethod superclassMethod = program.getIndexedMethod("Class.getSuperclass");
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 2ea57f3..2b6364d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -547,7 +547,16 @@
@Override
public void endVisit(JClassSeed x, Context ctx) {
- push(names.get(x.getRefType()).makeRef(x.getSourceInfo()));
+ /*
+ * Uninstantiated types, JSOs, and Strings don't have seed functions.
+ */
+ if (!program.typeOracle.isInstantiatedType(x.getRefType())
+ || program.isJavaScriptObject(x.getRefType())
+ || x.getRefType().equals(program.getTypeJavaLangString())) {
+ push(jsProgram.getNullLiteral());
+ } else {
+ push(names.get(x.getRefType()).makeRef(x.getSourceInfo()));
+ }
}
@SuppressWarnings("unchecked")
diff --git a/user/super/com/google/gwt/emul/java/lang/Class.java b/user/super/com/google/gwt/emul/java/lang/Class.java
index 515fecf..acb4b52 100644
--- a/user/super/com/google/gwt/emul/java/lang/Class.java
+++ b/user/super/com/google/gwt/emul/java/lang/Class.java
@@ -35,9 +35,11 @@
*
* @skip
*/
- static <T> Class<T> createForArray(String packageName, String className, Class<?> componentType) {
+ static <T> Class<T> createForArray(String packageName, String className,
+ Class<?> componentType) {
// Initialize here to avoid method inliner
Class<T> clazz = new Class<T>();
+ clazz.simpleName = componentType.getSimpleName() + "[]";
clazz.typeName = packageName + className;
clazz.modifiers = ARRAY;
clazz.superclass = Object.class;
@@ -51,10 +53,12 @@
* @skip
*/
static <T> Class<T> createForClass(String packageName, String className,
- Class<? super T> superclass) {
+ JavaScriptObject classSeed, Class<? super T> superclass) {
// Initialize here to avoid method inliner
Class<T> clazz = new Class<T>();
+ clazz.simpleName = className;
clazz.typeName = packageName + className;
+ clazz.classSeed = classSeed;
clazz.superclass = superclass;
return clazz;
}
@@ -65,10 +69,13 @@
* @skip
*/
static <T> Class<T> createForEnum(String packageName, String className,
- Class<? super T> superclass, JavaScriptObject enumConstantsFunc) {
+ JavaScriptObject classSeed, Class<? super T> superclass,
+ JavaScriptObject enumConstantsFunc) {
// Initialize here to avoid method inliner
Class<T> clazz = new Class<T>();
+ clazz.simpleName = className;
clazz.typeName = packageName + className;
+ clazz.classSeed = classSeed;
clazz.modifiers = (enumConstantsFunc != null) ? ENUM : 0;
clazz.superclass = clazz.enumSuperclass = superclass;
clazz.enumConstantsFunc = enumConstantsFunc;
@@ -83,6 +90,7 @@
static <T> Class<T> createForInterface(String packageName, String className) {
// Initialize here to avoid method inliner
Class<T> clazz = new Class<T>();
+ clazz.simpleName = className;
clazz.typeName = packageName + className;
clazz.modifiers = INTERFACE;
return clazz;
@@ -96,20 +104,31 @@
static Class<?> createForPrimitive(String packageName, String className) {
// Initialize here to avoid method inliner
Class<?> clazz = new Class<Object>();
+ clazz.simpleName = className;
clazz.typeName = packageName + className;
clazz.modifiers = PRIMITIVE;
return clazz;
}
-
+
int modifiers;
+ private JavaScriptObject classSeed;
+
+ /**
+ * This is a separate field from typeName so that typeName and the
+ * createForFoo parameters can be pruned when class metadata is disabled.
+ */
+ private String classSeedName;
+
private Class<?> componentType;
@SuppressWarnings("unused")
private JavaScriptObject enumConstantsFunc;
-
+
private Class<? super T> enumSuperclass;
+ private String simpleName;
+
private String typeName;
private Class<? super T> superclass;
@@ -123,8 +142,10 @@
}
public boolean desiredAssertionStatus() {
- // This body is ignored by the JJS compiler and a new one is
- // synthesized at compile-time based on the actual compilation arguments.
+ /*
+ * This body is ignored by the JJS compiler and a new one is synthesized at
+ * compile-time based on the actual compilation arguments.
+ */
return false;
}
@@ -149,6 +170,40 @@
return typeName;
}
+ /**
+ * Used by the compiler to implement {@link #getName()} when class metadata
+ * has been disabled.
+ */
+ public String getNameFromClassSeed() {
+ if (classSeedName != null) {
+ // Do nothing
+
+ } else if (classSeed == null) {
+ /*
+ * Uninstantiable types will have stable, but inconsistent type names.
+ * This would tend to occur when referencing interface types or concrete
+ * types that were pruned.
+ */
+ classSeedName = "Class$" + System.identityHashCode(this);
+
+ } else {
+ // Compute the name from the class seed
+ String fnToString = classSeed.toString().trim();
+ int index = fnToString.indexOf("(");
+ assert index != -1;
+ int start = fnToString.startsWith("function") ? 8 : 0;
+ classSeedName = "Class$" + fnToString.substring(start, index).trim();
+ }
+
+ assert classSeedName != null;
+ return classSeedName;
+ }
+
+ public String getSimpleName() {
+ // This body may be replaced by the compiler
+ return simpleName;
+ }
+
public Class<? super T> getSuperclass() {
// This body may be replaced by the compiler
return superclass;
diff --git a/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java b/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java
index 94a3d54..0849b3a 100644
--- a/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java
@@ -105,6 +105,37 @@
}
}
+ /**
+ * Tests specific to the disabled-metadata code-generation.
+ */
+ public void testDisabledClassMetadata() {
+ if (expectClassMetadata()) {
+ return;
+ }
+
+ /*
+ * IFoo is an interface and therefore uninstantiable, so it should have a
+ * purely numeric class ident.
+ */
+ String iFooName = IFoo.class.getName();
+ assertTrue("Expecting " + iFooName + " to start with Class$",
+ iFooName.startsWith("Class$"));
+ Integer.valueOf(iFooName.substring(6), 10);
+ // Make sure it has a stable name
+ assertEquals(iFooName, IFoo.class.getName());
+
+ String fooName = Foo.class.getName();
+
+ Object o = "Defeat type tightening";
+ o = new Foo();
+ String foo2Name = o.getClass().getName();
+ assertEquals(fooName, foo2Name);
+ assertTrue("Expecting " + fooName + " to start with Class$",
+ fooName.startsWith("Class$"));
+ assertTrue("Expecting " + fooName.substring(6) + " to be a function",
+ isFunction(fooName.substring(6)));
+ }
+
public void testEnum() {
Object o = Bar.BAR;
assertEquals(Bar.class, o.getClass());
@@ -185,4 +216,12 @@
private Class<? extends Bar> getBarClass() {
return Bar.class;
}
+
+ /**
+ * This is fragile, because it assumes that the GWT functions are defined in
+ * the local window's scope.
+ */
+ private native boolean isFunction(String name)/*-{
+ return typeof window[name] == 'function';
+ }-*/;
}