Integrate type-removal branch into trunk. Adds -XdisableClassMetadata flag. Adds RemateServiceObfuscateTypeNames module. svn merge --reintegrate https://google-web-toolkit.googlecode.com/svn/changes/bobv/elide_rpc_type_names_r4602 Patch by: bobv Review by: jgw git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4790 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/HostedModeBase.java b/dev/core/src/com/google/gwt/dev/HostedModeBase.java index 92b24f1..f4c8616 100644 --- a/dev/core/src/com/google/gwt/dev/HostedModeBase.java +++ b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
@@ -33,6 +33,7 @@ import com.google.gwt.dev.shell.ShellModuleSpaceHost; import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization; +import com.google.gwt.dev.util.arg.ArgHandlerDisableClassMetadata; import com.google.gwt.dev.util.arg.ArgHandlerDraftCompile; import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions; import com.google.gwt.dev.util.arg.ArgHandlerGenDir; @@ -319,6 +320,7 @@ registerHandler(new ArgHandlerScriptStyle(options)); registerHandler(new ArgHandlerEnableAssertions(options)); registerHandler(new ArgHandlerDisableAggressiveOptimization(options)); + registerHandler(new ArgHandlerDisableClassMetadata(options)); registerHandler(new ArgHandlerDraftCompile(options)); } }
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java index daa5afc..9ff1ce5 100644 --- a/dev/core/src/com/google/gwt/dev/Precompile.java +++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -44,6 +44,7 @@ import com.google.gwt.dev.util.PerfLogger; import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization; +import com.google.gwt.dev.util.arg.ArgHandlerDisableClassMetadata; import com.google.gwt.dev.util.arg.ArgHandlerDisableRunAsync; import com.google.gwt.dev.util.arg.ArgHandlerDraftCompile; import com.google.gwt.dev.util.arg.ArgHandlerDisableUpdateCheck; @@ -83,6 +84,7 @@ registerHandler(new ArgHandlerScriptStyle(options)); registerHandler(new ArgHandlerEnableAssertions(options)); registerHandler(new ArgHandlerDisableAggressiveOptimization(options)); + registerHandler(new ArgHandlerDisableClassMetadata(options)); registerHandler(new ArgHandlerValidateOnlyFlag(options)); registerHandler(new ArgHandlerDisableRunAsync(options)); registerHandler(new ArgHandlerDraftCompile(options)); @@ -131,6 +133,10 @@ return jjsOptions.isAggressivelyOptimize(); } + public boolean isClassMetadataDisabled() { + return jjsOptions.isClassMetadataDisabled(); + } + public boolean isDraftCompile() { return jjsOptions.isDraftCompile(); } @@ -159,6 +165,10 @@ jjsOptions.setAggressivelyOptimize(aggressivelyOptimize); } + public void setClassMetadataDisabled(boolean disabled) { + jjsOptions.setClassMetadataDisabled(disabled); + } + public void setDisableUpdateCheck(boolean disabled) { disableUpdateCheck = disabled; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java index 7989c6d..050e236 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
@@ -16,6 +16,7 @@ package com.google.gwt.dev.jjs; import com.google.gwt.dev.util.arg.OptionAggressivelyOptimize; +import com.google.gwt.dev.util.arg.OptionDisableClassMetadata; import com.google.gwt.dev.util.arg.OptionDraftCompile; import com.google.gwt.dev.util.arg.OptionEnableAssertions; import com.google.gwt.dev.util.arg.OptionRunAsyncEnabled; @@ -26,6 +27,6 @@ * Controls options for the {@link JavaToJavaScriptCompiler}. */ public interface JJSOptions extends OptionAggressivelyOptimize, - OptionDraftCompile, OptionEnableAssertions, OptionRunAsyncEnabled, - OptionScriptStyle, OptionSoycEnabled { + OptionDisableClassMetadata, OptionDraftCompile, OptionEnableAssertions, + OptionRunAsyncEnabled, OptionScriptStyle, OptionSoycEnabled { }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java index e6827fd..5403614 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
@@ -23,6 +23,7 @@ public class JJSOptionsImpl implements JJSOptions, Serializable { private boolean aggressivelyOptimize = true; + private boolean disableClassMetadata = false; private boolean draftCompile = false; private boolean enableAssertions; private JsOutputOption output = JsOutputOption.OBFUSCATED; @@ -38,6 +39,7 @@ public void copyFrom(JJSOptions other) { setAggressivelyOptimize(other.isAggressivelyOptimize()); + setClassMetadataDisabled(other.isClassMetadataDisabled()); setDraftCompile(other.isDraftCompile()); setEnableAssertions(other.isEnableAssertions()); setOutput(other.getOutput()); @@ -53,6 +55,10 @@ return aggressivelyOptimize; } + public boolean isClassMetadataDisabled() { + return disableClassMetadata; + } + public boolean isDraftCompile() { return draftCompile; } @@ -73,6 +79,10 @@ this.aggressivelyOptimize = aggressivelyOptimize; } + public void setClassMetadataDisabled(boolean disabled) { + disableClassMetadata = disabled; + } + public void setDraftCompile(boolean draft) { this.draftCompile = draft; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index ff003f3..c64996e 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -340,7 +340,7 @@ // (2) Create our own Java AST from the JDT AST. GenerateJavaAST.exec(allTypeDeclarations, typeMap, jprogram, jsProgram, - options.isEnableAssertions()); + options); // GenerateJavaAST can uncover semantic JSNI errors; report & abort checkForErrors(logger, goldenCuds, true);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayType.java index 0ac6def..67869ad 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayType.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayType.java
@@ -87,6 +87,7 @@ public void traverse(JVisitor visitor, Context ctx) { if (visitor.visit(this, ctx)) { + visitor.acceptWithInsertRemove(fields); } visitor.endVisit(this, ctx); }
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 032c381..aeada25 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
@@ -36,6 +36,20 @@ String typeName = getTypeName(program, type); JMethod method = program.getIndexedMethod(type.getClassLiteralFactoryMethod()); + + /* + * Use the classForEnum() constructor even for enum subtypes to aid in + * pruning supertype data. + */ + boolean isEnumOrSubclass = false; + if (type instanceof JClassType) { + JEnumType maybeEnum = ((JClassType) type).isEnumOrSubclass(); + if (maybeEnum != null) { + isEnumOrSubclass = true; + method = program.getIndexedMethod(maybeEnum.getClassLiteralFactoryMethod()); + } + } + assert method != null; JMethodCall call = new JMethodCall(program, info, null, method); @@ -80,9 +94,16 @@ JsniMethodRef jsniMethodRef = new JsniMethodRef(program, info, null, valuesMethod); call.getArgs().add(jsniMethodRef); + } else if (isEnumOrSubclass) { + // A subclass of an enum class + call.getArgs().add(program.getLiteralNull()); } + } else if (type instanceof JArrayType) { + JArrayType arrayType = (JArrayType) type; + JClassLiteral componentLiteral = program.getLiteralClass(arrayType.getElementType()); + call.getArgs().add(componentLiteral); } else { - assert (type instanceof JArrayType || type instanceof JInterfaceType || type instanceof JPrimitiveType); + assert (type instanceof JInterfaceType || type instanceof JPrimitiveType); } return call; }
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 2d8dfb4..01b2667 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
@@ -574,6 +574,7 @@ public JReferenceType getFromTypeMap(String qualifiedBinaryOrSourceName) { String srcTypeName = qualifiedBinaryOrSourceName.replace('$', '.'); + return typeNameMap.get(srcTypeName); } @@ -817,6 +818,46 @@ return typeSpecialClassLiteralHolder; } + /** + * Returns the JType corresponding to a JSNI type reference. + */ + public JType getTypeFromJsniRef(String className) { + int dim = 0; + while (className.endsWith("[]")) { + dim++; + className = className.substring(0, className.length() - 2); + } + + JType type; + if ("Z".equals(className)) { + type = program.getTypePrimitiveBoolean(); + } else if ("B".equals(className)) { + type = program.getTypePrimitiveByte(); + } else if ("C".equals(className)) { + type = program.getTypePrimitiveChar(); + } else if ("D".equals(className)) { + type = program.getTypePrimitiveDouble(); + } else if ("F".equals(className)) { + type = program.getTypePrimitiveFloat(); + } else if ("I".equals(className)) { + type = program.getTypePrimitiveInt(); + } else if ("J".equals(className)) { + type = program.getTypePrimitiveLong(); + } else if ("S".equals(className)) { + type = program.getTypePrimitiveShort(); + } else if ("V".equals(className)) { + type = program.getTypeVoid(); + } else { + type = getFromTypeMap(className); + } + + if (type == null || dim == 0) { + return type; + } else { + return getTypeArray(type, dim); + } + } + public int getTypeId(JClassType classType) { Integer integer = typeIdMap.get(classType); if (integer == null) { @@ -955,6 +996,7 @@ public void traverse(JVisitor visitor, Context ctx) { if (visitor.visit(this, ctx)) { visitor.accept(allTypes); + visitor.accept(new ArrayList<JArrayType>(allArrayTypes)); } visitor.endVisit(this, ctx); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java index 5f85a48..f2868c3 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -163,22 +163,8 @@ @Override public boolean visit(JClassLiteral x, Context ctx) { - /* - * Rescue just slightly less than what would normally be rescued for a - * field reference to the literal's field. Rescue the field itself, and - * its initializer, but do NOT rescue the whole enclosing class. That - * would pull in the clinit of that class, which has initializers for all - * the class literals, which in turn have all of the strings of all of the - * class names. - * - * TODO: Model ClassLiteral access a different way to avoid special magic. - * See Pruner.transformToNullFieldRef()/transformToNullMethodCall(). - */ JField field = x.getField(); rescue(field); - accept(field.getInitializer()); - referencedTypes.add(field.getEnclosingType()); - liveFieldsAndMethods.add(field.getEnclosingType().methods.get(0)); return true; } @@ -542,6 +528,28 @@ * itself becomes live. */ accept(((JField) var).getLiteralInitializer()); + } else if (var instanceof JField + && (program.getTypeClassLiteralHolder().equals(((JField) var).getEnclosingType()))) { + /* + * Rescue just slightly less than what would normally be rescued for + * a field reference to the literal's field. Rescue the field + * itself, and its initializer, but do NOT rescue the whole + * enclosing class. That would pull in the clinit of that class, + * which has initializers for all the class literals, which in turn + * have all of the strings of all of the class names. + * + * This work is done in rescue() to allow JSNI references to class + * literals (via the @Foo::class syntax) to correctly rescue class + * literal initializers. + * + * TODO: Model ClassLiteral access a different way to avoid special + * magic. See + * Pruner.transformToNullFieldRef()/transformToNullMethodCall(). + */ + JField field = (JField) var; + accept(field.getInitializer()); + referencedTypes.add(field.getEnclosingType()); + liveFieldsAndMethods.add(field.getEnclosingType().methods.get(0)); } } }
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 466eb00..7b3c27f 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
@@ -17,6 +17,7 @@ import com.google.gwt.dev.jjs.HasSourceInfo; import com.google.gwt.dev.jjs.InternalCompilerException; +import com.google.gwt.dev.jjs.JJSOptions; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.HasEnclosingType; @@ -31,6 +32,7 @@ import com.google.gwt.dev.jjs.ast.JCaseStatement; import com.google.gwt.dev.jjs.ast.JCastOperation; import com.google.gwt.dev.jjs.ast.JCharLiteral; +import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JConditional; import com.google.gwt.dev.jjs.ast.JContinueStatement; @@ -307,7 +309,9 @@ private int[] currentSeparatorPositions; - private boolean enableAsserts; + private final boolean disableClassMetadata; + + private final boolean enableAsserts; private final Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap = new HashMap<JsniMethodBody, AbstractMethodDeclaration>(); @@ -318,10 +322,16 @@ private final TypeMap typeMap; public JavaASTGenerationVisitor(TypeMap typeMap, JProgram program, - boolean enableAsserts) { + JJSOptions options) { this.typeMap = typeMap; this.program = program; - this.enableAsserts = enableAsserts; + this.enableAsserts = options.isEnableAssertions(); + + /* + * TODO: Determine if this should be controlled by a compiler flag or a + * module property. + */ + this.disableClassMetadata = options.isClassMetadataDisabled(); autoboxUtils = new AutoboxUtils(program); } @@ -531,10 +541,33 @@ implementMethod(method, program.getLiteralBoolean(true)); } - // Implement Class.desiredAssertionStatus + // Implement various methods on Class if (currentClass == program.getTypeJavaLangClass()) { JMethod method = program.getIndexedMethod("Class.desiredAssertionStatus"); implementMethod(method, program.getLiteralBoolean(enableAsserts)); + + if (disableClassMetadata) { + SourceInfo info = currentClass.getSourceInfo().makeChild( + JavaASTGenerationVisitor.class, "Disabled class metadata"); + + JMethod nameMethod = program.getIndexedMethod("Class.getName"); + + // this.hashCode() + JMethodCall hashCall = new JMethodCall(program, info, + program.getExprThisRef(info, (JClassType) currentClass), + program.getIndexedMethod("Object.hashCode")); + + // "Class$" + hashCode() + JBinaryOperation op = new JBinaryOperation(program, info, + program.getTypeJavaLangString(), JBinaryOperator.ADD, + program.getLiteralString(info, "Class$"), hashCall); + + implementMethod(nameMethod, op); + + // Forget the superclass + JMethod superclassMethod = program.getIndexedMethod("Class.getSuperclass"); + implementMethod(superclassMethod, program.getLiteralNull()); + } } if (currentClass instanceof JEnumType) { @@ -2811,9 +2844,9 @@ } String className = parsed.className(); - JReferenceType type = null; + JType type = null; if (!className.equals("null")) { - type = program.getFromTypeMap(className); + type = program.getTypeFromJsniRef(className); if (type == null) { reportJsniError(info, methodDecl, "Unresolvable native reference to type '" + className + "'"); @@ -2828,9 +2861,24 @@ if (fieldName.equals("nullField")) { return program.getNullField(); } + + } else if (fieldName.equals("class")) { + JClassLiteral lit = program.getLiteralClass(type); + return lit.getField(); + + } else if (type instanceof JPrimitiveType) { + reportJsniError(info, methodDecl, + "May not refer to fields on primitive types"); + return null; + + } else if (type instanceof JArrayType) { + reportJsniError(info, methodDecl, + "May not refer to fields on array types"); + return null; + } else { - for (int i = 0; i < type.fields.size(); ++i) { - JField field = type.fields.get(i); + for (int i = 0; i < ((JReferenceType) type).fields.size(); ++i) { + JField field = ((JReferenceType) type).fields.get(i); if (field.getName().equals(fieldName)) { return field; } @@ -2841,6 +2889,12 @@ "Unresolvable native reference to field '" + fieldName + "' in type '" + className + "'"); return null; + + } else if (type instanceof JPrimitiveType) { + reportJsniError(info, methodDecl, + "May not refer to methods on primitive types"); + return null; + } else { // look for a method TreeSet<String> almostMatches = new TreeSet<String>(); @@ -2852,7 +2906,7 @@ } } else { Queue<JReferenceType> workList = new LinkedList<JReferenceType>(); - workList.add(type); + workList.add((JReferenceType) type); while (!workList.isEmpty()) { JReferenceType cur = workList.poll(); for (int i = 0; i < cur.methods.size(); ++i) { @@ -3037,10 +3091,10 @@ * a JProgram structure. */ public static void exec(TypeDeclaration[] types, TypeMap typeMap, - JProgram jprogram, JsProgram jsProgram, boolean enableAsserts) { + JProgram jprogram, JsProgram jsProgram, JJSOptions options) { // Construct the basic AST. JavaASTGenerationVisitor v = new JavaASTGenerationVisitor(typeMap, - jprogram, enableAsserts); + jprogram, options); for (int i = 0; i < types.length; ++i) { v.processType(types[i]); }
diff --git a/dev/core/src/com/google/gwt/dev/js/JsInliner.java b/dev/core/src/com/google/gwt/dev/js/JsInliner.java index bb6a184..faad0bb 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java +++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -15,6 +15,7 @@ */ package com.google.gwt.dev.js; +import com.google.gwt.dev.jjs.HasSourceInfo; import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.js.ast.JsArrayAccess; @@ -46,6 +47,7 @@ import com.google.gwt.dev.js.ast.JsPostfixOperation; import com.google.gwt.dev.js.ast.JsPrefixOperation; import com.google.gwt.dev.js.ast.JsProgram; +import com.google.gwt.dev.js.ast.JsProgramFragment; import com.google.gwt.dev.js.ast.JsRegExp; import com.google.gwt.dev.js.ast.JsReturn; import com.google.gwt.dev.js.ast.JsScope; @@ -153,6 +155,12 @@ return op.getOperator().equals(JsBinaryOperator.COMMA) ? op : null; } + private final List<JsName> localVariableNames; + + public CommaNormalizer(List<JsName> localVariableNames) { + this.localVariableNames = localVariableNames; + } + @Override public void endVisit(JsBinaryOperation x, JsContext<JsExpression> ctx) { if (isComma(x) == null) { @@ -178,6 +186,41 @@ x.setArg1(inner.getArg1()); didChange = true; } + + /* + * Eliminate the pattern (localVar = expr, localVar). This tends to + * occur when a method interacted with pruned fields or had statements + * removed. + */ + JsName assignmentRef = null; + JsExpression expr = null; + JsName returnRef = null; + + if (x.getArg1() instanceof JsBinaryOperation) { + JsBinaryOperation op = (JsBinaryOperation) x.getArg1(); + if (op.getOperator() == JsBinaryOperator.ASG + && op.getArg1() instanceof JsNameRef) { + JsNameRef nameRef = (JsNameRef) op.getArg1(); + if (nameRef.getQualifier() == null) { + assignmentRef = nameRef.getName(); + expr = op.getArg2(); + } + } + } + + if (x.getArg2() instanceof JsNameRef) { + JsNameRef nameRef = (JsNameRef) x.getArg2(); + if (nameRef.getQualifier() == null) { + returnRef = nameRef.getName(); + } + } + + if (assignmentRef != null && assignmentRef.equals(returnRef) + && localVariableNames.contains(assignmentRef)) { + assert expr != null; + localVariableNames.remove(assignmentRef); + ctx.replaceMe(expr); + } return; } @@ -680,10 +723,14 @@ private final Set<JsFunction> blacklist = new HashSet<JsFunction>(); private final Stack<JsFunction> functionStack = new Stack<JsFunction>(); private final InvocationCountingVisitor invocationCountingVisitor = new InvocationCountingVisitor(); - private final Stack<List<JsName>> newLocalVariableStack = new Stack<List<JsName>>(); private final JsProgram program; + /** + * Not a stack because program fragments aren't nested. + */ + private JsFunction programFunction; + public InliningVisitor(JsProgram program) { this.program = program; invocationCountingVisitor.accept(program); @@ -785,33 +832,10 @@ throw new InternalCompilerException("Unexpected function popped"); } + JsBlock body = x.getBody(); List<JsName> newLocalVariables = newLocalVariableStack.pop(); - // Nothing to do - if (newLocalVariables.isEmpty()) { - return; - } - - List<JsStatement> statements = x.getBody().getStatements(); - - // The body can't be empty if we have local variables to create - assert !statements.isEmpty(); - - // Find or create the JsVars as the first statement - SourceInfo sourceInfo = x.getSourceInfo().makeChild( - InliningVisitor.class, "Synthetic locals"); - JsVars vars; - if (statements.get(0) instanceof JsVars) { - vars = (JsVars) statements.get(0); - } else { - vars = new JsVars(sourceInfo); - statements.add(0, vars); - } - - // Add all variables - for (JsName name : newLocalVariables) { - vars.add(new JsVar(sourceInfo, name)); - } + addVars(x, body, newLocalVariables); } @Override @@ -837,13 +861,22 @@ return; } - List<JsName> localVariableNames = new ArrayList<JsName>(); - List<JsStatement> statements = new ArrayList<JsStatement>( - f.getBody().getStatements()); + List<JsStatement> statements; + if (f.getBody() != null) { + statements = new ArrayList<JsStatement>(f.getBody().getStatements()); + } else { + /* + * Will see this with certain classes whose clinits are folded into the + * main JsProgram body. + */ + statements = Collections.emptyList(); + } + List<JsExpression> hoisted = new ArrayList<JsExpression>( statements.size()); - + List<JsName> localVariableNames = new ArrayList<JsName>(); boolean sawReturnStatement = false; + for (JsStatement statement : statements) { if (sawReturnStatement) { /* @@ -875,10 +908,11 @@ return; } - hoisted.add(h); - if (isReturnStatement(statement)) { sawReturnStatement = true; + hoisted.add(h); + } else if (hasSideEffects(Collections.singletonList(h))) { + hoisted.add(h); } } @@ -940,7 +974,7 @@ op = v.accept(op); // Normalize any nested comma expressions that we may have generated. - op = (new CommaNormalizer()).accept(op); + op = (new CommaNormalizer(localVariableNames)).accept(op); /* * Compare the relative complexity of the original invocation versus the @@ -953,6 +987,12 @@ return; } + if (functionStack.peek() == programFunction + && localVariableNames.size() > 0) { + // Don't add additional variables to the top-level program. + return; + } + // We've committed to the inlining, ensure the vars are created newLocalVariableStack.peek().addAll(localVariableNames); @@ -971,12 +1011,78 @@ } @Override + public void endVisit(JsProgramFragment x, JsContext<JsProgramFragment> ctx) { + if (!functionStack.pop().equals(programFunction)) { + throw new InternalCompilerException("Unexpected function popped"); + } + + assert programFunction.getBody().getStatements().size() == 0 : "Should not have moved statements into program"; + + List<JsName> newLocalVariables = newLocalVariableStack.pop(); + assert newLocalVariables.size() == 0 : "Should not have tried to create variables in program"; + } + + @Override + public boolean visit(JsExprStmt x, JsContext<JsStatement> ctx) { + if (functionStack.peek() == programFunction) { + /* Don't inline top-level invocations. */ + if (x.getExpression() instanceof JsInvocation) { + return false; + } + } + return true; + } + + @Override public boolean visit(JsFunction x, JsContext<JsExpression> ctx) { functionStack.push(x); newLocalVariableStack.push(new ArrayList<JsName>()); return true; } + /** + * Create a synthetic context to attempt to simplify statements in the + * top-level of the program. + */ + @Override + public boolean visit(JsProgramFragment x, JsContext<JsProgramFragment> ctx) { + programFunction = new JsFunction(program.getSourceInfo(), + program.getScope()); + programFunction.setBody(new JsBlock(x.getSourceInfo())); + functionStack.push(programFunction); + newLocalVariableStack.push(new ArrayList<JsName>()); + return true; + } + + private void addVars(HasSourceInfo x, JsBlock body, + List<JsName> newLocalVariables) { + // Nothing to do + if (newLocalVariables.isEmpty()) { + return; + } + + List<JsStatement> statements = body.getStatements(); + + // The body can't be empty if we have local variables to create + assert !statements.isEmpty(); + + // Find or create the JsVars as the first statement + SourceInfo sourceInfo = x.getSourceInfo().makeChild( + InliningVisitor.class, "Synthetic locals"); + JsVars vars; + if (statements.get(0) instanceof JsVars) { + vars = (JsVars) statements.get(0); + } else { + vars = new JsVars(sourceInfo); + statements.add(0, vars); + } + + // Add all variables + for (JsName name : newLocalVariables) { + vars.add(new JsVar(sourceInfo, name)); + } + } + private boolean isInvokedMoreThanOnce(JsFunction f) { Integer count = invocationCountingVisitor.invocationCount(f); return count == null || count > 1;
diff --git a/dev/core/src/com/google/gwt/dev/js/rhino/TokenStream.java b/dev/core/src/com/google/gwt/dev/js/rhino/TokenStream.java index 4a98379..871fec7 100644 --- a/dev/core/src/com/google/gwt/dev/js/rhino/TokenStream.java +++ b/dev/core/src/com/google/gwt/dev/js/rhino/TokenStream.java
@@ -1506,6 +1506,17 @@ break; } } + + // Arrray-type reference + while (c == '[') { + if (']' == in.peek()) { + addToString('['); + addToString(in.read()); + c = in.read(); + } else { + break; + } + } // We have a non-ident char to classify. //
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java index 95b748a..eaa4575 100644 --- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java +++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -42,6 +42,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; @@ -194,7 +195,42 @@ */ private Class<?> getClassFromBinaryName(String binaryClassName) { try { - return Class.forName(binaryClassName, false, CompilingClassLoader.this); + int dims = 0; + while (binaryClassName.endsWith("[]")) { + dims++; + binaryClassName = binaryClassName.substring(0, + binaryClassName.length() - 2); + } + + Class<?> clazz; + if ("Z".equals(binaryClassName)) { + clazz = boolean.class; + } else if ("B".equals(binaryClassName)) { + clazz = byte.class; + } else if ("C".equals(binaryClassName)) { + clazz = char.class; + } else if ("D".equals(binaryClassName)) { + clazz = double.class; + } else if ("F".equals(binaryClassName)) { + clazz = float.class; + } else if ("I".equals(binaryClassName)) { + clazz = int.class; + } else if ("J".equals(binaryClassName)) { + clazz = long.class; + } else if ("S".equals(binaryClassName)) { + clazz = short.class; + } else if ("V".equals(binaryClassName)) { + clazz = void.class; + } else { + clazz = Class.forName(binaryClassName, false, + CompilingClassLoader.this); + } + + if (dims > 0) { + return Array.newInstance(clazz, new int[dims]).getClass(); + } else { + return clazz; + } } catch (ClassNotFoundException e) { return null; }
diff --git a/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java index 9b1cbfe..4140d30 100644 --- a/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java +++ b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
@@ -177,7 +177,6 @@ * or * * x.@java.lang.Object::equals(Ljava/lang/Object;)(y) - * */ // Get the methods on this class/interface. @@ -193,5 +192,8 @@ field.setAccessible(true); addMember(field, field.getName()); } + + // Add a magic field to access class literals from JSNI + addMember(new SyntheticClassMember(targetClass), "class"); } }
diff --git a/dev/core/src/com/google/gwt/dev/shell/JavaDispatchImpl.java b/dev/core/src/com/google/gwt/dev/shell/JavaDispatchImpl.java index 52b1d0b..7737996 100644 --- a/dev/core/src/com/google/gwt/dev/shell/JavaDispatchImpl.java +++ b/dev/core/src/com/google/gwt/dev/shell/JavaDispatchImpl.java
@@ -61,7 +61,19 @@ * @return the field */ public Field getField(int dispId) { - return (Field) getMember(dispId); + Member member = getMember(dispId); + + if (member instanceof SyntheticClassMember) { + try { + Field f = SyntheticClassMember.class.getDeclaredField("clazz"); + assert f != null; + return f; + } catch (SecurityException e) { + } catch (NoSuchFieldException e) { + } + assert false : "Should never get here"; + } + return (Field) member; } /** @@ -70,7 +82,13 @@ * @throws IllegalArgumentException */ public Object getFieldValue(int dispId) { - Field field = (Field) getMember(dispId); + Member member = getMember(dispId); + + if (member instanceof SyntheticClassMember) { + return member.getDeclaringClass(); + } + + Field field = (Field) member; try { return field.get(target); } catch (IllegalAccessException e) { @@ -108,7 +126,8 @@ return false; } - return getMember(dispId) instanceof Field; + Member member = getMember(dispId); + return member instanceof Field || member instanceof SyntheticClassMember; } /**
diff --git a/dev/core/src/com/google/gwt/dev/shell/SyntheticClassMember.java b/dev/core/src/com/google/gwt/dev/shell/SyntheticClassMember.java new file mode 100644 index 0000000..429d11e --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/shell/SyntheticClassMember.java
@@ -0,0 +1,46 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.dev.shell; + +import java.lang.reflect.Member; + +/** + * This class is used to represent a synthetic field called "class" that allows + * JSNI references to class literals. + */ +class SyntheticClassMember implements Member { + private final Class<?> clazz; + + public SyntheticClassMember(Class<?> clazz) { + this.clazz = clazz; + } + + public Class getDeclaringClass() { + return clazz; + } + + public int getModifiers() { + return Member.PUBLIC; + } + + public String getName() { + return "class"; + } + + public boolean isSynthetic() { + return false; + } +} \ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableClassMetadata.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableClassMetadata.java new file mode 100644 index 0000000..13590a6 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableClassMetadata.java
@@ -0,0 +1,46 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.dev.util.arg; + +import com.google.gwt.util.tools.ArgHandlerFlag; + +/** + * An ArgHandler to provide the -disableClassMetadata flag. + */ +public class ArgHandlerDisableClassMetadata extends ArgHandlerFlag { + + private final OptionDisableClassMetadata option; + + public ArgHandlerDisableClassMetadata(OptionDisableClassMetadata option) { + this.option = option; + } + + @Override + public String getPurpose() { + return "EXPERIMENTAL: Disables some java.lang.Class methods (e.g. getName())"; + } + + @Override + public String getTag() { + return "-XdisableClassMetadata"; + } + + @Override + public boolean setFlag() { + option.setClassMetadataDisabled(true); + return true; + } +}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionDisableClassMetadata.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionDisableClassMetadata.java new file mode 100644 index 0000000..eadc103 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionDisableClassMetadata.java
@@ -0,0 +1,25 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.dev.util.arg; + +/** + * Encapsulates a compiler option to disable {@link Class#getName()}. + */ +public interface OptionDisableClassMetadata { + boolean isClassMetadataDisabled(); + + void setClassMetadataDisabled(boolean disabled); +}
diff --git a/user/build.xml b/user/build.xml index 3931c74..df207ce 100755 --- a/user/build.xml +++ b/user/build.xml
@@ -133,6 +133,14 @@ </gwt.junit> </target> + <target name="test.web.disableClassMetadata" depends="compile, compile.tests" description="Run only web-mode tests for this project."> + <gwt.junit test.args="${test.args} -XdisableClassMetadata -out www -web" test.out="${junit.out}/${build.host.platform}-web-mode-disableClassMetadata" test.cases="default.web.tests" > + <extraclasspaths> + <pathelement location="${gwt.build}/out/dev/core/bin-test" /> + </extraclasspaths> + </gwt.junit> + </target> + <target name="test.web.draft" depends="compile, compile.tests" description="Run only web-mode tests for this project."> <gwt.junit test.args="${test.args} -draftCompile -out www -web" test.out="${junit.out}/${build.host.platform}-web-mode-draft" test.cases="default.web.tests" > <extraclasspaths> @@ -156,6 +164,7 @@ <antcall target="remoteweb-test"/> <antcall target="test.hosted"/> <antcall target="test.web"/> + <antcall target="test.web.disableClassMetadata"/> <antcall target="test.web.draft"/> </parallel> </limit>
diff --git a/user/src/com/google/gwt/core/client/impl/Impl.java b/user/src/com/google/gwt/core/client/impl/Impl.java index ab05a2f..c8426c7 100644 --- a/user/src/com/google/gwt/core/client/impl/Impl.java +++ b/user/src/com/google/gwt/core/client/impl/Impl.java
@@ -28,6 +28,9 @@ * expando. This method should not be used with <code>null</code> or any * String. The former will crash and the later will produce unstable results * when called repeatedly with a String primitive. + * <p> + * The sequence of hashcodes generated by this method are a + * monotonically-increasing sequence. */ public static native int getHashCode(Object o) /*-{ return o.$H || (o.$H = @com.google.gwt.core.client.impl.Impl::getNextHashId()()); @@ -50,7 +53,7 @@ i = s.lastIndexOf('/'); if (i != -1) s = s.substring(0, i); - + // Ensure a final slash if non-empty. return s.length > 0 ? s + "/" : ""; }-*/; @@ -64,7 +67,10 @@ }-*/; /** - * Called from JSNI. + * Called from JSNI. Do not change this implementation without updating: + * <ul> + * <li>{@link com.google.gwt.user.client.rpc.impl.SerializerBase}</li> + * </ul> */ @SuppressWarnings("unused") private static int getNextHashId() {
diff --git a/user/src/com/google/gwt/user/RemoteService.gwt.xml b/user/src/com/google/gwt/user/RemoteService.gwt.xml index 32649e5..d067669 100644 --- a/user/src/com/google/gwt/user/RemoteService.gwt.xml +++ b/user/src/com/google/gwt/user/RemoteService.gwt.xml
@@ -30,6 +30,11 @@ --> <set-property name="gwt.suppressNonStaticFinalFieldWarnings" value="false" /> + <!-- + If this is ever turned on by default, fix up RPCSuiteWithElision + --> + <set-configuration-property name="gwt.elideTypeNamesFromRPC" value="false" /> + <generate-with class="com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator"> <when-type-assignable class="com.google.gwt.user.client.rpc.RemoteService"/> </generate-with>
diff --git a/user/src/com/google/gwt/user/RemoteServiceObfuscateTypeNames.gwt.xml b/user/src/com/google/gwt/user/RemoteServiceObfuscateTypeNames.gwt.xml new file mode 100644 index 0000000..db872dd --- /dev/null +++ b/user/src/com/google/gwt/user/RemoteServiceObfuscateTypeNames.gwt.xml
@@ -0,0 +1,28 @@ +<!-- --> +<!-- Copyright 2009 Google Inc. --> +<!-- Licensed under the Apache License, Version 2.0 (the "License"); you --> +<!-- may not use this file except in compliance with the License. You may --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. --> + +<!-- + Inheriting this module will remove type names from the RPC payload. This + requires the server to be configured to use the RPC whitelist file. + --> +<module> + <inherits name="com.google.gwt.user.RemoteService" /> + + <!-- + Do not simply copy this. It is likely that the mechanism used to enable + elision will change in the future, and it may be the case that elision + will be automatically enabled in a future release. + --> + <set-configuration-property name="gwt.elideTypeNamesFromRPC" value="true" /> +</module> \ No newline at end of file
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java index 2177020..fec3660 100644 --- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java +++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
@@ -23,6 +23,11 @@ public abstract class AbstractSerializationStream { /** + * The default flags to be used by serialization streams. + */ + public static final int DEFAULT_FLAGS = 0; + + /** * The character used to separate fields in client->server RPC messages. * * Note that this character is referenced in the following places not using @@ -39,7 +44,12 @@ */ public static final int SERIALIZATION_STREAM_VERSION = 5; - private int flags = 0; + /** + * Indicates that obfuscated type names should be used in the RPC payload. + */ + public static final int FLAG_ELIDE_TYPE_NAMES = 0x1; + + private int flags = DEFAULT_FLAGS; private int version = SERIALIZATION_STREAM_VERSION; public final void addFlags(int flags) { @@ -54,6 +64,10 @@ return version; } + public final boolean hasFlags(int flags) { + return (getFlags() & flags) == flags; + } + public final void setFlags(int flags) { this.flags = flags; }
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java index c8aaf0a..7c28259 100644 --- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java +++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
@@ -183,7 +183,8 @@ * @param instance the instance to inspect * @return the type signature of the instance */ - protected abstract String getObjectTypeSignature(Object instance); + protected abstract String getObjectTypeSignature(Object instance) + throws SerializationException; /** * Gets the string table.
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java index f760109..fe14d16 100644 --- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java +++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
@@ -69,13 +69,13 @@ return /[\u0000\|\\\u0080-\uFFFF]/g; } else if (webkit < 522) { // Safari 2 doesn't handle \\uXXXX in regexes - // TODO(jat): should iPhone be treated specially? - return /[\x00\|\\]/g; + // TODO(jat): should iPhone be treated specially? + return /[\x00\|\\]/g; } else if (webkit > 0) { - // other WebKit-based browsers need some additional quoting - return /[\u0000\|\\\u0300-\u036F\u0590-\u05FF\uD800-\uFFFF]/g; + // other WebKit-based browsers need some additional quoting + return /[\u0000\|\\\u0300-\u036F\u0590-\u05FF\uD800-\uFFFF]/g; } else { - return /[\u0000\|\\\uD800-\uFFFF]/g; + return /[\u0000\|\\\uD800-\uFFFF]/g; } }-*/; @@ -201,13 +201,7 @@ clazz = e.getDeclaringClass(); } - String typeName = clazz.getName(); - - String serializationSignature = serializer.getSerializationSignature(typeName); - if (serializationSignature != null) { - typeName += "/" + serializationSignature; - } - return typeName; + return serializer.getSerializationSignature(clazz); } @Override
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/Serializer.java b/user/src/com/google/gwt/user/client/rpc/impl/Serializer.java index 323391a..7a24b57 100644 --- a/user/src/com/google/gwt/user/client/rpc/impl/Serializer.java +++ b/user/src/com/google/gwt/user/client/rpc/impl/Serializer.java
@@ -32,9 +32,9 @@ String typeSignature) throws SerializationException; /** - * Return the serialization signature for the given type name. + * Return the serialization signature for the given type. */ - String getSerializationSignature(String typeName); + String getSerializationSignature(Class<?> clazz); /** * Instantiate an object of the given typeName from the serialized stream.
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/SerializerBase.java b/user/src/com/google/gwt/user/client/rpc/impl/SerializerBase.java new file mode 100644 index 0000000..69f5361 --- /dev/null +++ b/user/src/com/google/gwt/user/client/rpc/impl/SerializerBase.java
@@ -0,0 +1,167 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc.impl; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.client.rpc.SerializationStreamReader; +import com.google.gwt.user.client.rpc.SerializationStreamWriter; + +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * Maps class literals to type signatures and type signatures to serialization + * methods. Relies on monotonic behavior of hashcodes in web mode defined in + * {@link com.google.gwt.core.client.impl.Impl#getHashCode(Object)} In hosted + * mode, we map the underlying signature JsArray onto a proper IdentityHashMap. + */ +public abstract class SerializerBase implements Serializer { + + /** + * Represents a collection of functions that perform type-specific functions. + */ + protected static final class MethodMap extends JavaScriptObject { + protected MethodMap() { + } + + native void deserialize(SerializationStreamReader stream, Object instance, + String signature) throws SerializationException /*-{ + this[signature][1](stream, instance); + }-*/; + + native JsArray<JavaScriptObject> get(String signature) /*-{ + return this[signature]; + }-*/; + + native Object instantiate(SerializationStreamReader stream, String signature) + throws SerializationException /*-{ + return this[signature][0](stream); + }-*/; + + native void put(String signature, JsArray<JavaScriptObject> methods) /*-{ + this[signature] = methods; + }-*/; + + native void serialize(SerializationStreamWriter stream, Object instance, + String signature) throws SerializationException /*-{ + this[signature][2](stream, instance); + }-*/; + } + + private static final Map<JsArrayString, Map<Class<?>, String>> hostedSignatureMaps; + + static { + if (GWT.isScript()) { + hostedSignatureMaps = null; + } else { + hostedSignatureMaps = new IdentityHashMap<JsArrayString, Map<Class<?>, String>>(); + } + } + + protected static final void registerMethods(MethodMap methodMap, + String signature, JsArray<JavaScriptObject> methods) { + assert signature != null : "signature"; + assert methodMap.get(signature) == null : "Duplicate signature " + + signature; + + methodMap.put(signature, methods); + } + + protected static final void registerSignature(JsArrayString signatureMap, + Class<?> clazz, String signature) { + assert clazz != null : "clazz"; + assert signature != null : "signature"; + + if (GWT.isScript()) { + assert signatureMap.get(clazz.hashCode()) == null : "Duplicate signature " + + signature; + signatureMap.set(clazz.hashCode(), signature); + + } else { + Map<Class<?>, String> subMap = getSubMap(signatureMap); + + assert !subMap.containsKey(clazz); + subMap.put(clazz, signature); + } + } + + /** + * Hashcodes in hosted mode are unpredictable. Each signature map is + * associated with a proper IdentityHashMap. This method should only be used + * in hosted mode. + */ + private static Map<Class<?>, String> getSubMap(JsArrayString signatureMap) { + assert !GWT.isScript() : "Should only use this in hosted mode"; + Map<Class<?>, String> subMap = hostedSignatureMaps.get(signatureMap); + if (subMap == null) { + subMap = new IdentityHashMap<Class<?>, String>(); + hostedSignatureMaps.put(signatureMap, subMap); + } + return subMap; + } + + public final void deserialize(SerializationStreamReader stream, + Object instance, String typeSignature) throws SerializationException { + check(typeSignature, 2); + + getMethodMap().deserialize(stream, instance, typeSignature); + } + + public final String getSerializationSignature(Class<?> clazz) { + assert clazz != null : "clazz"; + if (GWT.isScript()) { + return getSignatureMap().get(clazz.hashCode()); + } else { + return getSubMap(getSignatureMap()).get(clazz); + } + } + + public final Object instantiate(SerializationStreamReader stream, + String typeSignature) throws SerializationException { + check(typeSignature, 1); + + return getMethodMap().instantiate(stream, typeSignature); + } + + public final void serialize(SerializationStreamWriter stream, + Object instance, String typeSignature) throws SerializationException { + check(typeSignature, 3); + + getMethodMap().serialize(stream, instance, typeSignature); + } + + protected abstract MethodMap getMethodMap(); + + protected abstract JsArrayString getSignatureMap(); + + private void check(String typeSignature, int length) + throws SerializationException { + /* + * Probably trying to serialize a type that isn't supposed to be + * serializable. + */ + if (getMethodMap().get(typeSignature) == null) { + throw new SerializationException(typeSignature); + } + + assert getMethodMap().get(typeSignature).length() >= length : "Not enough methods, expecting " + + length + " saw " + getMethodMap().get(typeSignature).length(); + } +}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java index e237865..25fe79c 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java +++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
@@ -16,6 +16,7 @@ package com.google.gwt.user.rebind.rpc; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.ext.BadPropertyValueException; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; @@ -43,6 +44,7 @@ import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import com.google.gwt.user.server.rpc.SerializationPolicyLoader; +import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -148,16 +150,24 @@ * Take the union of two type arrays, and then sort the results * alphabetically. */ - private static JType[] unionOfTypeArrays(JType[] types1, JType[] types2) { + private static JType[] unionOfTypeArrays(JType[]... types) { Set<JType> typesList = new HashSet<JType>(); - typesList.addAll(Arrays.asList(types1)); - typesList.addAll(Arrays.asList(types2)); + for (JType[] a : types) { + typesList.addAll(Arrays.asList(a)); + } JType[] serializableTypes = typesList.toArray(new JType[0]); Arrays.sort(serializableTypes, SerializableTypeOracleBuilder.JTYPE_COMPARATOR); return serializableTypes; } + private boolean elideTypeNames; + + /** + * The possibly obfuscated type signatures used to represent a type. + */ + private Map<JType, String> typeStrings; + private JClassType serviceIntf; { @@ -242,6 +252,16 @@ throw new UnableToCompleteException(); } + try { + elideTypeNames = Boolean.parseBoolean(context.getPropertyOracle().getPropertyValue( + logger, TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC)); + } catch (BadPropertyValueException e) { + logger.log(TreeLogger.ERROR, "Configuration property " + + TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC + + " is not defined. Is RemoteService.gwt.xml inherited?"); + throw new UnableToCompleteException(); + } + // Create a resource file to receive all of the serialization information // computed by STOB and mark it as private so it does not end up in the // output. @@ -259,17 +279,27 @@ SerializationUtils.getTypeSerializerQualifiedName(serviceIntf)); tsc.realize(logger); + typeStrings = new HashMap<JType, String>(tsc.getTypeStrings()); + typeStrings.put(serviceIntf, TypeNameObfuscator.SERVICE_INTERFACE_ID); + String serializationPolicyStrongName = writeSerializationPolicyFile(logger, context, typesSentFromBrowser, typesSentToBrowser); + String remoteServiceInterfaceName = elideTypeNames + ? TypeNameObfuscator.SERVICE_INTERFACE_ID + : TypeOracleMediator.computeBinaryClassName(serviceIntf); generateProxyFields(srcWriter, typesSentFromBrowser, - serializationPolicyStrongName); + serializationPolicyStrongName, remoteServiceInterfaceName); generateProxyContructor(javadocAnnotationDeprecationBranch, srcWriter); generateProxyMethods(srcWriter, typesSentFromBrowser, syncMethToAsyncMethMap); + if (elideTypeNames) { + generateStreamWriterOverride(srcWriter); + } + srcWriter.commit(logger); return getProxyQualifiedName(); @@ -300,10 +330,10 @@ */ private void generateProxyFields(SourceWriter srcWriter, SerializableTypeOracle serializableTypeOracle, - String serializationPolicyStrongName) { + String serializationPolicyStrongName, String remoteServiceInterfaceName) { // Initialize a field with binary name of the remote service interface - srcWriter.println("private static final String REMOTE_SERVICE_INTERFACE_NAME = \"" - + TypeOracleMediator.computeBinaryClassName(serviceIntf) + "\";"); + srcWriter.println("private static final String REMOTE_SERVICE_INTERFACE_NAME = " + + "\"" + remoteServiceInterfaceName + "\";"); srcWriter.println("private static final String SERIALIZATION_POLICY =\"" + serializationPolicyStrongName + "\";"); String typeSerializerName = SerializationUtils.getTypeSerializerQualifiedName(serviceIntf); @@ -392,10 +422,16 @@ JParameter[] syncParams = syncMethod.getParameters(); w.println(streamWriterName + ".writeInt(" + syncParams.length + ");"); for (JParameter param : syncParams) { - w.println(streamWriterName - + ".writeString(\"" - + TypeOracleMediator.computeBinaryClassName(param.getType().getErasedType()) - + "\");"); + JType paramType = param.getType().getErasedType(); + String typeName; + if (typeStrings.containsKey(paramType)) { + typeName = typeStrings.get(paramType); + } else { + typeName = TypeOracleMediator.computeBinaryClassName(paramType); + } + assert typeName != null : "Could not compute a type name for " + + paramType.getQualifiedSourceName(); + w.println(streamWriterName + ".writeString(\"" + typeName + "\");"); } // Encode all of the arguments to the asynchronous method, but exclude the @@ -484,6 +520,17 @@ } } + private void generateStreamWriterOverride(SourceWriter srcWriter) { + srcWriter.println("@Override"); + srcWriter.println("public ClientSerializationStreamWriter createStreamWriter() {"); + srcWriter.indent(); + srcWriter.println("ClientSerializationStreamWriter toReturn = super.createStreamWriter();"); + srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter.FLAG_ELIDE_TYPE_NAMES);"); + srcWriter.println("return toReturn;"); + srcWriter.outdent(); + srcWriter.println("}"); + } + private String getProxyQualifiedName() { String[] name = Shared.synthesizeTopLevelClassName(serviceIntf, PROXY_SUFFIX); @@ -565,11 +612,12 @@ ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(baos, SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING); + TypeOracle oracle = ctx.getTypeOracle(); PrintWriter pw = new PrintWriter(osw); JType[] serializableTypes = unionOfTypeArrays( serializationSto.getSerializableTypes(), - deserializationSto.getSerializableTypes()); + deserializationSto.getSerializableTypes(), new JType[] {serviceIntf}); for (int i = 0; i < serializableTypes.length; ++i) { JType type = serializableTypes[i]; @@ -581,7 +629,16 @@ + Boolean.toString(deserializationSto.maybeInstantiated(type))); pw.print(", " + Boolean.toString(serializationSto.isSerializable(type))); pw.print(", " - + Boolean.toString(serializationSto.maybeInstantiated(type)) + '\n'); + + Boolean.toString(serializationSto.maybeInstantiated(type))); + pw.print(", " + typeStrings.get(type)); + + /* + * Include the serialization signature to bump the RPC file name if + * obfuscated identifiers are used. + */ + pw.print(", " + + SerializationUtils.getSerializationSignature(oracle, type)); + pw.print('\n'); } // Closes the wrapped streams.
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java index 55f2b53..19f311d 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java +++ b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
@@ -17,6 +17,8 @@ package com.google.gwt.user.rebind.rpc; import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.core.ext.BadPropertyValueException; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; @@ -30,14 +32,18 @@ import com.google.gwt.user.client.rpc.SerializationStreamReader; import com.google.gwt.user.client.rpc.SerializationStreamWriter; import com.google.gwt.user.client.rpc.impl.Serializer; +import com.google.gwt.user.client.rpc.impl.SerializerBase; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -48,6 +54,11 @@ public class TypeSerializerCreator { /** + * Configuration property to use type indices instead of type signatures. + */ + public static final String GWT_ELIDE_TYPE_NAMES_FROM_RPC = "gwt.elideTypeNamesFromRPC"; + + /** * Default number of types to split createMethodMap entries into. Zero means * no sharding occurs. Stored as a string since it is used as a default * property value. @@ -59,23 +70,11 @@ */ private static final String DEFAULT_CREATEMETHODMAP_SHARD_SIZE = "0"; - private static final String DESERIALIZE_METHOD_SIGNATURE = "public native void deserialize(" - + "SerializationStreamReader streamReader, Object instance, String typeSignature)" - + " throws SerializationException"; - /** * Java system property name to override the above. */ private static final String GWT_CREATEMETHODMAP_SHARD_SIZE = "gwt.typecreator.shard.size"; - private static final String INSTANTIATE_METHOD_SIGNATURE = "public native Object instantiate(" - + "SerializationStreamReader streamReader, String typeSignature)" - + " throws SerializationException"; - - private static final String SERIALIZE_METHOD_SIGNATURE = "public native void serialize(" - + "SerializationStreamWriter streamWriter, Object instance, String typeSignature)" - + " throws SerializationException"; - private static int shardSize = -1; private static void computeShardSize(TreeLogger logger) @@ -100,6 +99,8 @@ private final SerializableTypeOracle deserializationOracle; + private final boolean elideTypeNames; + private final SerializableTypeOracle serializationOracle; private final JType[] serializableTypes; @@ -110,6 +111,8 @@ private final String typeSerializerClassName; + private final Map<JType, String> typeStrings = new IdentityHashMap<JType, String>(); + public TypeSerializerCreator(TreeLogger logger, SerializableTypeOracle serializationOracle, SerializableTypeOracle deserializationOracle, GeneratorContext context, @@ -125,6 +128,8 @@ typesSet.addAll(Arrays.asList(serializationOracle.getSerializableTypes())); typesSet.addAll(Arrays.asList(deserializationOracle.getSerializableTypes())); serializableTypes = typesSet.toArray(new JType[0]); + Arrays.sort(serializableTypes, + SerializableTypeOracleBuilder.JTYPE_COMPARATOR); srcWriter = getSourceWriter(logger, context); if (shardSize < 0) { @@ -132,6 +137,21 @@ } logger.log(TreeLogger.TRACE, "Using a shard size of " + shardSize + " for TypeSerializerCreator createMethodMap"); + + try { + String value = context.getPropertyOracle().getPropertyValue(logger, + GWT_ELIDE_TYPE_NAMES_FROM_RPC); + elideTypeNames = Boolean.parseBoolean(value); + } catch (BadPropertyValueException e) { + logger.log(TreeLogger.ERROR, "The configuration property " + + GWT_ELIDE_TYPE_NAMES_FROM_RPC + + " was not defined. Is RemoteService.gwt.xml inherited?"); + throw new UnableToCompleteException(); + } + } + + public Map<JType, String> getTypeStrings() { + return Collections.unmodifiableMap(typeStrings); } public String realize(TreeLogger logger) throws UnableToCompleteException { @@ -147,22 +167,16 @@ writeStaticFields(); + writeStaticInitializer(); + writeCreateMethods(); - writeCreateMethodMapMethod(logger); + writeRegisterSignatures(); - writeCreateSignatureMapMethod(); + writeRegisterMethods(); writeRaiseSerializationException(); - writeDeserializeMethod(); - - writeGetSerializationSignatureMethod(); - - writeInstantiateMethod(); - - writeSerializeMethod(); - srcWriter.commit(logger); return typeSerializerName; @@ -252,12 +266,13 @@ packageName, className); composerFactory.addImport(JavaScriptObject.class.getName()); + composerFactory.addImport(JsArrayString.class.getName()); composerFactory.addImport(Serializer.class.getName()); composerFactory.addImport(SerializationException.class.getName()); composerFactory.addImport(SerializationStreamReader.class.getName()); composerFactory.addImport(SerializationStreamWriter.class.getName()); - composerFactory.addImplementedInterface("Serializer"); + composerFactory.setSuperclass(SerializerBase.class.getName()); return composerFactory.createSourceWriter(ctx, printWriter); } @@ -309,34 +324,6 @@ return true; } - /** - * Generate the createMethodMap function, possibly splitting it into smaller - * pieces if necessary to avoid an old Mozilla crash when dealing with - * excessively large JS functions. - * - * @param logger TreeLogger instance - * @throws UnableToCompleteException if an error is logged - */ - private void writeCreateMethodMapMethod(TreeLogger logger) - throws UnableToCompleteException { - ArrayList<JType> filteredTypes = new ArrayList<JType>(); - JType[] types = getSerializableTypes(); - int n = types.length; - for (int index = 0; index < n; ++index) { - JType type = types[index]; - if (serializationOracle.maybeInstantiated(type) - || deserializationOracle.maybeInstantiated(type)) { - filteredTypes.add(type); - } - } - if (shardSize > 0 && filteredTypes.size() > shardSize) { - writeShardedCreateMethodMapMethod(filteredTypes, shardSize); - } else { - writeSingleCreateMethodMapMethod(filteredTypes); - } - srcWriter.println(); - } - private void writeCreateMethods() { JType[] types = getSerializableTypes(); for (int typeIndex = 0; typeIndex < types.length; ++typeIndex) { @@ -366,87 +353,6 @@ } } - private void writeCreateSignatureMapMethod() { - srcWriter.println("private static native JavaScriptObject createSignatureMap() /*-" + '{'); - { - srcWriter.indent(); - srcWriter.println("return {"); - JType[] types = getSerializableTypes(); - boolean needComma = false; - for (int index = 0; index < types.length; ++index) { - JType type = types[index]; - if (!serializationOracle.maybeInstantiated(type) - && !deserializationOracle.maybeInstantiated(type)) { - continue; - } - if (needComma) { - srcWriter.println(","); - } else { - needComma = true; - } - - srcWriter.print("\"" + TypeOracleMediator.computeBinaryClassName(type) - + "\":\"" - + SerializationUtils.getSerializationSignature(typeOracle, type) - + "\""); - } - srcWriter.println(); - srcWriter.println("};"); - srcWriter.outdent(); - } - srcWriter.println("}-*/;"); - srcWriter.println(); - } - - private void writeDeserializeMethod() { - srcWriter.print(DESERIALIZE_METHOD_SIGNATURE); - srcWriter.println(" /*-" + '{'); - { - String serializerTypeName = getTypeSerializerClassName(); - srcWriter.indent(); - srcWriter.println("var methodTable = @" + serializerTypeName - + "::methodMap[typeSignature];"); - srcWriter.println("if (!methodTable) {"); - srcWriter.indentln("@" + serializerTypeName - + "::raiseSerializationException(Ljava/lang/String;)(typeSignature);"); - srcWriter.println("}"); - srcWriter.println("methodTable[1](streamReader, instance);"); - srcWriter.outdent(); - } - srcWriter.println("}-*/;"); - srcWriter.println(); - } - - private void writeGetSerializationSignatureMethod() { - String serializerTypeName = getTypeSerializerClassName(); - srcWriter.println("public native String getSerializationSignature(String typeName) /*-" + '{'); - srcWriter.indent(); - srcWriter.println("return @" + serializerTypeName - + "::signatureMap[typeName];"); - srcWriter.outdent(); - srcWriter.println("}-*/;"); - srcWriter.println(); - } - - private void writeInstantiateMethod() { - srcWriter.print(INSTANTIATE_METHOD_SIGNATURE); - srcWriter.println(" /*-" + '{'); - { - String serializerTypeName = getTypeSerializerClassName(); - srcWriter.indent(); - srcWriter.println("var methodTable = @" + serializerTypeName - + "::methodMap[typeSignature];"); - srcWriter.println("if (!methodTable) {"); - srcWriter.indentln("@" + serializerTypeName - + "::raiseSerializationException(Ljava/lang/String;)(typeSignature);"); - srcWriter.println("}"); - srcWriter.println("return methodTable[0](streamReader);"); - srcWriter.outdent(); - } - srcWriter.println("}-*/;"); - srcWriter.println(); - } - private void writeRaiseSerializationException() { srcWriter.println("private static void raiseSerializationException(String msg) throws SerializationException {"); srcWriter.indentln("throw new SerializationException(msg);"); @@ -454,95 +360,107 @@ srcWriter.println(); } - private void writeSerializeMethod() { - srcWriter.print(SERIALIZE_METHOD_SIGNATURE); - srcWriter.println(" /*-" + '{'); - { - String serializerTypeName = getTypeSerializerClassName(); - srcWriter.indent(); - srcWriter.println("var methodTable = @" + serializerTypeName - + "::methodMap[typeSignature];"); - srcWriter.println("if (!methodTable) {"); - srcWriter.indentln("@" + serializerTypeName - + "::raiseSerializationException(Ljava/lang/String;)(typeSignature);"); - srcWriter.println("}"); - srcWriter.println("methodTable[2](streamWriter, instance);"); - srcWriter.outdent(); - } - srcWriter.println("}-*/;"); - srcWriter.println(); - } - - /** - * Create a createMethodMap method which is sharded into smaller methods. This - * avoids a crash in old Mozilla dealing with very large JS functions being - * evaluated. - * - * @param types list of types to include - * @param shardSize batch size for sharding - */ - private void writeShardedCreateMethodMapMethod(List<JType> types, - int shardSize) { - srcWriter.println("private static JavaScriptObject createMethodMap() {"); - int n = types.size(); + private void writeRegisterMethods() { + srcWriter.println("private static native void registerMethods() /*-{"); srcWriter.indent(); - srcWriter.println("JavaScriptObject map = JavaScriptObject.createObject();"); - for (int i = 0; i < n; i += shardSize) { - srcWriter.println("createMethodMap_" + i + "(map);"); + + List<JType> filteredTypes = new ArrayList<JType>(); + JType[] types = getSerializableTypes(); + int n = types.length; + for (int index = 0; index < n; ++index) { + JType type = types[index]; + if (serializationOracle.maybeInstantiated(type) + || deserializationOracle.maybeInstantiated(type)) { + filteredTypes.add(type); + } } - srcWriter.println("return map;"); - srcWriter.outdent(); - srcWriter.println("}"); - srcWriter.println(); - for (int outerIndex = 0; outerIndex < n; outerIndex += shardSize) { - srcWriter.println("@SuppressWarnings(\"restriction\")"); - srcWriter.println("private static native void createMethodMap_" - + outerIndex + "(JavaScriptObject map) /*-" + '{'); + + for (JType type : filteredTypes) { + + srcWriter.println("@com.google.gwt.user.client.rpc.impl.SerializerBase" + + "::registerMethods(" + + "Lcom/google/gwt/user/client/rpc/impl/SerializerBase$MethodMap;" + + "Ljava/lang/String;" + "Lcom/google/gwt/core/client/JsArray;)("); + + srcWriter.indentln("@" + typeSerializerClassName + "::methodMap,"); + + String typeString = typeStrings.get(type); + assert typeString != null : "Missing type signature for " + + type.getQualifiedSourceName(); + srcWriter.indentln("\"" + typeString + "\" , ["); + srcWriter.indent(); - int last = outerIndex + shardSize; - if (last > n) { - last = n; - } - for (int i = outerIndex; i < last; ++i) { - JType type = types.get(i); - String typeString = getTypeString(type); - srcWriter.print("map[\"" + typeString + "\"]=["); - writeTypeMethods(type); - srcWriter.println("];"); - } + writeTypeMethods(type); srcWriter.outdent(); - srcWriter.println("}-*/;"); + + srcWriter.indentln("]);"); srcWriter.println(); } - } - private void writeSingleCreateMethodMapMethod(List<JType> types) { - srcWriter.println("@SuppressWarnings(\"restriction\")"); - srcWriter.println("private static native JavaScriptObject createMethodMap() /*-" + '{'); - srcWriter.indent(); - srcWriter.println("return {"); - int n = types.size(); - for (int i = 0; i < n; ++i) { - if (i > 0) { - srcWriter.println(","); - } - JType type = types.get(i); - String typeString = getTypeString(type); - srcWriter.print("\"" + typeString + "\":["); - writeTypeMethods(type); - srcWriter.print("]"); - } - srcWriter.println("};"); srcWriter.outdent(); srcWriter.println("}-*/;"); + srcWriter.println(); + } + + private void writeRegisterSignatures() { + srcWriter.println("private static native void registerSignatures() /*-{"); + srcWriter.indent(); + + int index = 0; + + for (JType type : getSerializableTypes()) { + + String typeString; + if (elideTypeNames) { + typeString = Integer.toString(++index, Character.MAX_RADIX); + } else { + typeString = getTypeString(type); + } + typeStrings.put(type, typeString); + + if (!serializationOracle.maybeInstantiated(type) + && !deserializationOracle.maybeInstantiated(type)) { + continue; + } + + String jsniTypeRef; + jsniTypeRef = TypeOracleMediator.computeBinaryClassName(type.getLeafType()); + while (type.isArray() != null) { + jsniTypeRef += "[]"; + type = type.isArray().getComponentType(); + } + srcWriter.println("@com.google.gwt.user.client.rpc.impl.SerializerBase" + + "::registerSignature(" + + "Lcom/google/gwt/core/client/JsArrayString;" + "Ljava/lang/Class;" + + "Ljava/lang/String;)("); + srcWriter.indent(); + srcWriter.println("@" + typeSerializerClassName + "::signatureMap,"); + srcWriter.println("@" + jsniTypeRef + "::class,"); + srcWriter.println("\"" + typeString + "\");"); + srcWriter.outdent(); + srcWriter.println(); + } + + srcWriter.outdent(); + srcWriter.println("}-*/;"); + srcWriter.println(); } private void writeStaticFields() { - srcWriter.println("private static final JavaScriptObject methodMap = createMethodMap();"); - srcWriter.println("private static final JavaScriptObject signatureMap = createSignatureMap();"); + srcWriter.println("private static final MethodMap methodMap = JavaScriptObject.createObject().cast();"); + srcWriter.println("private static final JsArrayString signatureMap = JavaScriptObject.createArray().cast();"); + srcWriter.println("protected MethodMap getMethodMap() { return methodMap; }"); + srcWriter.println("protected JsArrayString getSignatureMap() { return signatureMap; }"); srcWriter.println(); } + private void writeStaticInitializer() { + srcWriter.println("static {"); + srcWriter.indentln("registerMethods();"); + srcWriter.indentln("registerSignatures();"); + srcWriter.println("}"); + } + /** * Write an entry in the createMethodMap method for one type. *
diff --git a/user/src/com/google/gwt/user/server/rpc/RPC.java b/user/src/com/google/gwt/user/server/rpc/RPC.java index 20ee0ef..5a6ffa7 100644 --- a/user/src/com/google/gwt/user/server/rpc/RPC.java +++ b/user/src/com/google/gwt/user/server/rpc/RPC.java
@@ -18,9 +18,11 @@ import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream; import com.google.gwt.user.server.rpc.impl.LegacySerializationPolicy; import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader; import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter; +import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -37,14 +39,14 @@ * reused by framework implementors such as Spring and G4jsf to support a wide * range of service invocation policies. * - * <h3>Canonical Example</h3> - * The following example demonstrates the canonical way to use this class. + * <h3>Canonical Example</h3> The following example demonstrates the canonical + * way to use this class. * - * {@example com.google.gwt.examples.rpc.server.CanonicalExample#processCall(String)} + * {@example + * com.google.gwt.examples.rpc.server.CanonicalExample#processCall(String)} * - * <h3>Advanced Example</h3> - * The following example shows a more advanced way of using this class to create - * an adapter between GWT RPC entities and POJOs. + * <h3>Advanced Example</h3> The following example shows a more advanced way of + * using this class to create an adapter between GWT RPC entities and POJOs. * * {@example com.google.gwt.examples.rpc.server.AdvancedExample#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)} */ @@ -124,14 +126,14 @@ /** * Returns an {@link RPCRequest} that is built by decoding the contents of an * encoded RPC request and optionally validating that type can handle the - * request. If the type parameter is not <code>null</code>, the - * implementation checks that the type is assignable to the - * {@link RemoteService} interface requested in the encoded request string. + * request. If the type parameter is not <code>null</code>, the implementation + * checks that the type is assignable to the {@link RemoteService} interface + * requested in the encoded request string. * * <p> * Invoking this method with <code>null</code> for the type parameter, - * <code>decodeRequest(encodedRequest, null)</code>, is equivalent to - * calling <code>decodeRequest(encodedRequest)</code>. + * <code>decodeRequest(encodedRequest, null)</code>, is equivalent to calling + * <code>decodeRequest(encodedRequest)</code>. * </p> * * @param encodedRequest a string that encodes the {@link RemoteService} @@ -167,13 +169,13 @@ /** * Returns an {@link RPCRequest} that is built by decoding the contents of an * encoded RPC request and optionally validating that type can handle the - * request. If the type parameter is not <code>null</code>, the - * implementation checks that the type is assignable to the - * {@link RemoteService} interface requested in the encoded request string. + * request. If the type parameter is not <code>null</code>, the implementation + * checks that the type is assignable to the {@link RemoteService} interface + * requested in the encoded request string. * * <p> - * If the serializationPolicyProvider parameter is not <code>null</code>, - * it is asked for a {@link SerializationPolicy} to use to restrict the set of + * If the serializationPolicyProvider parameter is not <code>null</code>, it + * is asked for a {@link SerializationPolicy} to use to restrict the set of * types that can be decoded from the request. If this parameter is * <code>null</code>, then only subtypes of * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or @@ -182,8 +184,8 @@ * * <p> * Invoking this method with <code>null</code> for the type parameter, - * <code>decodeRequest(encodedRequest, null)</code>, is equivalent to - * calling <code>decodeRequest(encodedRequest)</code>. + * <code>decodeRequest(encodedRequest, null)</code>, is equivalent to calling + * <code>decodeRequest(encodedRequest)</code>. * </p> * * @param encodedRequest a string that encodes the {@link RemoteService} @@ -234,7 +236,8 @@ streamReader.prepareToRead(encodedRequest); // Read the name of the RemoteService interface - String serviceIntfName = streamReader.readString(); + String serviceIntfName = maybeDeobfuscate(streamReader, + streamReader.readString()); if (type != null) { if (!implementsInterface(type, serviceIntfName)) { @@ -269,7 +272,9 @@ Class<?>[] parameterTypes = new Class[paramCount]; for (int i = 0; i < parameterTypes.length; i++) { - String paramClassName = streamReader.readString(); + String paramClassName = maybeDeobfuscate(streamReader, + streamReader.readString()); + try { parameterTypes[i] = getClassFromSerializedName(paramClassName, classLoader); @@ -287,7 +292,8 @@ parameterValues[i] = streamReader.deserializeValue(parameterTypes[i]); } - return new RPCRequest(method, parameterValues, serializationPolicy); + return new RPCRequest(method, parameterValues, serializationPolicy, + streamReader.getFlags()); } catch (NoSuchMethodException e) { throw new IncompatibleRemoteServiceException( @@ -301,8 +307,8 @@ /** * Returns a string that encodes an exception. If method is not - * <code>null</code>, it is an error if the exception is not in the - * method's list of checked exceptions. + * <code>null</code>, it is an error if the exception is not in the method's + * list of checked exceptions. * * @param serviceMethod the method that threw the exception, may be * <code>null</code> @@ -322,13 +328,13 @@ /** * Returns a string that encodes an exception. If method is not - * <code>null</code>, it is an error if the exception is not in the - * method's list of checked exceptions. + * <code>null</code>, it is an error if the exception is not in the method's + * list of checked exceptions. * * <p> - * If the serializationPolicy parameter is not <code>null</code>, it is - * used to determine what types can be encoded as part of this response. If - * this parameter is <code>null</code>, then only subtypes of + * If the serializationPolicy parameter is not <code>null</code>, it is used + * to determine what types can be encoded as part of this response. If this + * parameter is <code>null</code>, then only subtypes of * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or * types which have custom field serializers may be encoded. * </p> @@ -348,6 +354,13 @@ public static String encodeResponseForFailure(Method serviceMethod, Throwable cause, SerializationPolicy serializationPolicy) throws SerializationException { + return encodeResponseForFailure(serviceMethod, cause, serializationPolicy, + AbstractSerializationStream.DEFAULT_FLAGS); + } + + public static String encodeResponseForFailure(Method serviceMethod, + Throwable cause, SerializationPolicy serializationPolicy, int flags) + throws SerializationException { if (cause == null) { throw new NullPointerException("cause cannot be null"); } @@ -362,7 +375,8 @@ + "' threw an unexpected exception: " + cause.toString(), cause); } - return encodeResponse(cause.getClass(), cause, true, serializationPolicy); + return encodeResponse(cause.getClass(), cause, true, flags, + serializationPolicy); } /** @@ -390,9 +404,9 @@ * an object that is not assignable to the service method's return type. * * <p> - * If the serializationPolicy parameter is not <code>null</code>, it is - * used to determine what types can be encoded as part of this response. If - * this parameter is <code>null</code>, then only subtypes of + * If the serializationPolicy parameter is not <code>null</code>, it is used + * to determine what types can be encoded as part of this response. If this + * parameter is <code>null</code>, then only subtypes of * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or * types which have custom field serializers may be encoded. * </p> @@ -412,6 +426,13 @@ public static String encodeResponseForSuccess(Method serviceMethod, Object object, SerializationPolicy serializationPolicy) throws SerializationException { + return encodeResponseForSuccess(serviceMethod, object, serializationPolicy, + AbstractSerializationStream.DEFAULT_FLAGS); + } + + public static String encodeResponseForSuccess(Method serviceMethod, + Object object, SerializationPolicy serializationPolicy, int flags) + throws SerializationException { if (serviceMethod == null) { throw new NullPointerException("serviceMethod cannot be null"); } @@ -438,7 +459,8 @@ } } - return encodeResponse(methodReturnType, object, false, serializationPolicy); + return encodeResponse(methodReturnType, object, false, flags, + serializationPolicy); } /** @@ -483,9 +505,9 @@ * could be the value returned by the method or an exception thrown by it. * * <p> - * If the serializationPolicy parameter is not <code>null</code>, it is - * used to determine what types can be encoded as part of this response. If - * this parameter is <code>null</code>, then only subtypes of + * If the serializationPolicy parameter is not <code>null</code>, it is used + * to determine what types can be encoded as part of this response. If this + * parameter is <code>null</code>, then only subtypes of * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or * types which have custom field serializers may be encoded. * </p> @@ -514,6 +536,14 @@ public static String invokeAndEncodeResponse(Object target, Method serviceMethod, Object[] args, SerializationPolicy serializationPolicy) throws SerializationException { + return invokeAndEncodeResponse(target, serviceMethod, args, + serializationPolicy, AbstractSerializationStream.DEFAULT_FLAGS); + } + + public static String invokeAndEncodeResponse(Object target, + Method serviceMethod, Object[] args, + SerializationPolicy serializationPolicy, int flags) + throws SerializationException { if (serviceMethod == null) { throw new NullPointerException("serviceMethod"); } @@ -527,7 +557,7 @@ Object result = serviceMethod.invoke(target, args); responsePayload = encodeResponseForSuccess(serviceMethod, result, - serializationPolicy); + serializationPolicy, flags); } catch (IllegalAccessException e) { SecurityException securityException = new SecurityException( formatIllegalAccessErrorMessage(target, serviceMethod)); @@ -544,7 +574,7 @@ Throwable cause = e.getCause(); responsePayload = encodeResponseForFailure(serviceMethod, cause, - serializationPolicy); + serializationPolicy, flags); } return responsePayload; @@ -562,11 +592,12 @@ * @throws SerializationException if the object cannot be serialized */ private static String encodeResponse(Class<?> responseClass, Object object, - boolean wasThrown, SerializationPolicy serializationPolicy) + boolean wasThrown, int flags, SerializationPolicy serializationPolicy) throws SerializationException { ServerSerializationStreamWriter stream = new ServerSerializationStreamWriter( serializationPolicy); + stream.setFlags(flags); stream.prepareToWrite(); if (responseClass != void.class) { @@ -781,6 +812,34 @@ } /** + * Given a type identifier in the stream, attempt to deobfuscate it. Retuns + * the original identifier if deobfuscation is unnecessary or no mapping is + * known. + */ + private static String maybeDeobfuscate( + ServerSerializationStreamReader streamReader, String name) + throws SerializationException { + int index; + if (streamReader.hasFlags(AbstractSerializationStream.FLAG_ELIDE_TYPE_NAMES)) { + SerializationPolicy serializationPolicy = streamReader.getSerializationPolicy(); + if (!(serializationPolicy instanceof TypeNameObfuscator)) { + throw new IncompatibleRemoteServiceException( + "RPC request was encoded with obfuscated type names, " + + "but the SerializationPolicy in use does not implement " + + TypeNameObfuscator.class.getName()); + } + + String maybe = ((TypeNameObfuscator) serializationPolicy).getClassNameForTypeId(name); + if (maybe != null) { + return maybe; + } + } else if ((index = name.indexOf('/')) != -1) { + return name.substring(0, index); + } + return name; + } + + /** * Straight copy from * {@link com.google.gwt.dev.util.TypeInfo#getSourceRepresentation(Class)} to * avoid runtime dependency on gwt-dev.
diff --git a/user/src/com/google/gwt/user/server/rpc/RPCRequest.java b/user/src/com/google/gwt/user/server/rpc/RPCRequest.java index b691d52..8f14fdd 100644 --- a/user/src/com/google/gwt/user/server/rpc/RPCRequest.java +++ b/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
@@ -24,6 +24,11 @@ public final class RPCRequest { /** + * The flags associated with the RPC request. + */ + private final int flags; + + /** * The method for this request. */ private final Method method; @@ -43,10 +48,15 @@ * Construct an RPCRequest. */ public RPCRequest(Method method, Object[] parameters, - SerializationPolicy serializationPolicy) { + SerializationPolicy serializationPolicy, int flags) { this.method = method; this.parameters = parameters; this.serializationPolicy = serializationPolicy; + this.flags = flags; + } + + public int getFlags() { + return flags; } /**
diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java index c43128c..16dde6f 100644 --- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java +++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
@@ -164,7 +164,8 @@ RPCRequest rpcRequest = RPC.decodeRequest(payload, this.getClass(), this); onAfterRequestDeserialized(rpcRequest); return RPC.invokeAndEncodeResponse(this, rpcRequest.getMethod(), - rpcRequest.getParameters(), rpcRequest.getSerializationPolicy()); + rpcRequest.getParameters(), rpcRequest.getSerializationPolicy(), + rpcRequest.getFlags()); } catch (IncompatibleRemoteServiceException ex) { log( "An IncompatibleRemoteServiceException was thrown while processing this call.",
diff --git a/user/src/com/google/gwt/user/server/rpc/SerializationPolicyLoader.java b/user/src/com/google/gwt/user/server/rpc/SerializationPolicyLoader.java index 0a4ca4a..6755414 100644 --- a/user/src/com/google/gwt/user/server/rpc/SerializationPolicyLoader.java +++ b/user/src/com/google/gwt/user/server/rpc/SerializationPolicyLoader.java
@@ -16,6 +16,7 @@ package com.google.gwt.user.server.rpc; import com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy; +import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator; import java.io.BufferedReader; import java.io.IOException; @@ -36,7 +37,8 @@ */ public static final String SERIALIZATION_POLICY_FILE_ENCODING = "UTF-8"; - private static final String FORMAT_ERROR_MESSAGE = "Expected: className, [true | false], [true | false], [true | false], [true | false]"; + private static final String FORMAT_ERROR_MESSAGE = "Expected: className, " + + "[true | false], [true | false], [true | false], [true | false], typeId, signature"; /** * Returns the serialization policy file name from the from the serialization @@ -101,6 +103,7 @@ Map<Class<?>, Boolean> whitelistSer = new HashMap<Class<?>, Boolean>(); Map<Class<?>, Boolean> whitelistDeser = new HashMap<Class<?>, Boolean>(); + Map<Class<?>, String> typeIds = new HashMap<Class<?>, String>(); InputStreamReader isr = new InputStreamReader(inputStream, SERIALIZATION_POLICY_FILE_ENCODING); @@ -113,7 +116,7 @@ if (line.length() > 0) { String[] components = line.split(","); - if (components.length != 2 && components.length != 5) { + if (components.length != 2 && components.length != 7) { throw new ParseException(FORMAT_ERROR_MESSAGE, lineNum); } @@ -123,16 +126,18 @@ throw new ParseException(FORMAT_ERROR_MESSAGE, lineNum); } } - String[] fields = new String[components.length]; String binaryTypeName = components[0].trim(); boolean fieldSer; boolean instantSer; boolean fieldDeser; boolean instantDeser; + String typeId; + if (components.length == 2) { fieldSer = fieldDeser = true; instantSer = instantDeser = Boolean.valueOf(components[1]); + typeId = binaryTypeName; } else { int idx = 1; // TODO: Validate the instantiable string better. @@ -140,11 +145,13 @@ instantSer = Boolean.valueOf(components[idx++]); fieldDeser = Boolean.valueOf(components[idx++]); instantDeser = Boolean.valueOf(components[idx++]); + typeId = components[idx++]; - if (!fieldSer && !fieldDeser) { + if (!fieldSer && !fieldDeser + && !TypeNameObfuscator.SERVICE_INTERFACE_ID.equals(typeId)) { throw new ParseException("Type " + binaryTypeName - + " is neither field serializable nor field deserializable", - lineNum); + + " is neither field serializable, field deserializable " + + "nor the service interface", lineNum); } } @@ -159,6 +166,7 @@ if (fieldDeser) { whitelistDeser.put(clazz, instantDeser); } + typeIds.put(clazz, typeId); } catch (ClassNotFoundException ex) { // Ignore the error, but add it to the list of errors if one was // provided. @@ -172,7 +180,8 @@ lineNum++; } - return new StandardSerializationPolicy(whitelistSer, whitelistDeser); + return new StandardSerializationPolicy(whitelistSer, whitelistDeser, + typeIds); } private SerializationPolicyLoader() {
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicy.java b/user/src/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicy.java index 9155533..6357467 100644 --- a/user/src/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicy.java +++ b/user/src/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicy.java
@@ -43,7 +43,11 @@ * serialized as super types of a legal type. * </p> */ -public class LegacySerializationPolicy extends SerializationPolicy { +public class LegacySerializationPolicy extends SerializationPolicy implements + TypeNameObfuscator { + + private static final String ELISION_ERROR = "Type name elision in RPC " + + "payloads is only supported if the RPC whitelist file is used."; /** * Many JRE types would appear to be {@link Serializable} on the server. @@ -55,27 +59,26 @@ * be serializable via a custom serializer. */ private static final Class<?>[] JRE_BLACKLIST = { - java.lang.ArrayStoreException.class, java.lang.AssertionError.class, - java.lang.Boolean.class, java.lang.Byte.class, java.lang.Character.class, - java.lang.Class.class, java.lang.ClassCastException.class, - java.lang.Double.class, java.lang.Error.class, - java.lang.Float.class, java.lang.IllegalArgumentException.class, - java.lang.IllegalStateException.class, - java.lang.IndexOutOfBoundsException.class, java.lang.Integer.class, - java.lang.Long.class, java.lang.NegativeArraySizeException.class, - java.lang.NullPointerException.class, java.lang.Number.class, - java.lang.NumberFormatException.class, - java.lang.Short.class, java.lang.StackTraceElement.class, - java.lang.String.class, java.lang.StringBuffer.class, - java.lang.StringIndexOutOfBoundsException.class, - java.lang.UnsupportedOperationException.class, - java.util.ArrayList.class, - java.util.ConcurrentModificationException.class, java.util.Date.class, - java.util.EmptyStackException.class, java.util.EventObject.class, - java.util.HashMap.class, java.util.HashSet.class, - java.util.MissingResourceException.class, - java.util.NoSuchElementException.class, java.util.Stack.class, - java.util.TooManyListenersException.class, java.util.Vector.class}; + java.lang.ArrayStoreException.class, java.lang.AssertionError.class, + java.lang.Boolean.class, java.lang.Byte.class, java.lang.Character.class, + java.lang.Class.class, java.lang.ClassCastException.class, + java.lang.Double.class, java.lang.Error.class, java.lang.Float.class, + java.lang.IllegalArgumentException.class, + java.lang.IllegalStateException.class, + java.lang.IndexOutOfBoundsException.class, java.lang.Integer.class, + java.lang.Long.class, java.lang.NegativeArraySizeException.class, + java.lang.NullPointerException.class, java.lang.Number.class, + java.lang.NumberFormatException.class, java.lang.Short.class, + java.lang.StackTraceElement.class, java.lang.String.class, + java.lang.StringBuffer.class, + java.lang.StringIndexOutOfBoundsException.class, + java.lang.UnsupportedOperationException.class, java.util.ArrayList.class, + java.util.ConcurrentModificationException.class, java.util.Date.class, + java.util.EmptyStackException.class, java.util.EventObject.class, + java.util.HashMap.class, java.util.HashSet.class, + java.util.MissingResourceException.class, + java.util.NoSuchElementException.class, java.util.Stack.class, + java.util.TooManyListenersException.class, java.util.Vector.class}; private static final Set<Class<?>> JRE_BLACKSET = new HashSet<Class<?>>( Arrays.asList(JRE_BLACKLIST)); @@ -92,57 +95,49 @@ private LegacySerializationPolicy() { } - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.server.rpc.SerializationPolicy#shouldDerializeFields(java.lang.String) + /** + * Implemented to fail with a useful error message. */ + public final String getClassNameForTypeId(String id) + throws SerializationException { + throw new SerializationException(ELISION_ERROR); + } + + /** + * Implemented to fail with a useful error message. + */ + public final String getTypeIdForClass(Class<?> clazz) + throws SerializationException { + throw new SerializationException(ELISION_ERROR); + } + @Override public boolean shouldDeserializeFields(Class<?> clazz) { return isFieldSerializable(clazz); } - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.server.rpc.SerializationPolicy#shouldSerializeFields(java.lang.String) - */ @Override public boolean shouldSerializeFields(Class<?> clazz) { return isFieldSerializable(clazz); } - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.server.rpc.SerializationPolicy#validateDeserialize(java.lang.String) - */ @Override public void validateDeserialize(Class<?> clazz) throws SerializationException { if (!isInstantiable(clazz)) { - throw new SerializationException( - "Type '" - + clazz.getName() - + "' was not assignable to '" - + IsSerializable.class.getName() - + "' and did not have a custom field serializer. For security purposes, this type will not be deserialized."); + throw new SerializationException("Type '" + clazz.getName() + + "' was not assignable to '" + IsSerializable.class.getName() + + "' and did not have a custom field serializer. " + + "For security purposes, this type will not be deserialized."); } } - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.server.rpc.SerializationPolicy#validateSerialize(java.lang.String) - */ @Override public void validateSerialize(Class<?> clazz) throws SerializationException { if (!isInstantiable(clazz)) { - throw new SerializationException( - "Type '" - + clazz.getName() - + "' was not assignable to '" - + IsSerializable.class.getName() - + "' and did not have a custom field serializer. For security purposes, this type will not be serialized."); + throw new SerializationException("Type '" + clazz.getName() + + "' was not assignable to '" + IsSerializable.class.getName() + + "' and did not have a custom field serializer." + + "For security purposes, this type will not be serialized."); } }
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java index 92e1cd3..3dfc3d9 100644 --- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java +++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -400,7 +400,7 @@ if (idx == 0) { throw new IncompatibleRemoteServiceException( "Malformed or old RPC message received - expecting version " - + SERIALIZATION_STREAM_VERSION); + + SERIALIZATION_STREAM_VERSION); } else { int version = Integer.valueOf(encodedTokens.substring(0, idx)); throw new IncompatibleRemoteServiceException("Expecting version " @@ -479,18 +479,31 @@ protected Object deserialize(String typeSignature) throws SerializationException { Object instance = null; - SerializedInstanceReference serializedInstRef = SerializabilityUtil.decodeSerializedInstanceReference(typeSignature); try { - Class<?> instanceClass = Class.forName(serializedInstRef.getName(), - false, classLoader); + Class<?> instanceClass; + if (hasFlags(FLAG_ELIDE_TYPE_NAMES)) { + if (getSerializationPolicy() instanceof TypeNameObfuscator) { + TypeNameObfuscator obfuscator = (TypeNameObfuscator) getSerializationPolicy(); + String instanceClassName = obfuscator.getClassNameForTypeId(typeSignature); + instanceClass = Class.forName(instanceClassName, false, classLoader); + } else { + throw new SerializationException( + "The GWT module was compiled with RPC type name elision enabled, but " + + getSerializationPolicy().getClass().getName() + + " does not implement " + TypeNameObfuscator.class.getName()); + } + } else { + SerializedInstanceReference serializedInstRef = SerializabilityUtil.decodeSerializedInstanceReference(typeSignature); + instanceClass = Class.forName(serializedInstRef.getName(), false, + classLoader); + validateTypeVersions(instanceClass, serializedInstRef); + } assert (serializationPolicy != null); serializationPolicy.validateDeserialize(instanceClass); - validateTypeVersions(instanceClass, serializedInstRef); - Class<?> customSerializer = SerializabilityUtil.hasCustomFieldSerializer(instanceClass); int index = reserveDecodedObjectIndex(); @@ -625,8 +638,8 @@ while (idx >= 0) { buf.append(str.substring(pos, idx)); if (++idx == str.length()) { - throw new SerializationException("Unmatched backslash: \"" - + str + "\""); + throw new SerializationException("Unmatched backslash: \"" + str + + "\""); } char ch = str.charAt(idx); pos = idx + 1; @@ -642,7 +655,8 @@ break; case 'u': try { - ch = (char) Integer.parseInt(str.substring(idx + 1, idx + 5), 16); + ch = (char) Integer.parseInt(str.substring(idx + 1, idx + 5), + 16); } catch (NumberFormatException e) { throw new SerializationException( "Invalid Unicode escape sequence in \"" + str + "\"");
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java index 8f837cd..bb9d203 100644 --- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java +++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
@@ -404,14 +404,10 @@ * consumed and/or interpreted as a special character when the JSON encoded * response is evaluated. For example, 0x2028 and 0x2029 are alternate line * endings for JS per ECMA-232, which are respected by Firefox and Mozilla. - * - * @param ch character to check - * @return <code>true</code> if the character requires the \\uXXXX unicode - * character escape - * + * <p> * Notes: * <ol> - * <li> The following cases are a more conservative set of cases which are are + * <li>The following cases are a more conservative set of cases which are are * in the future proofing space as opposed to the required minimal set. We * could remove these and still pass our tests. * <ul> @@ -426,18 +422,20 @@ * <li>Total Characters Escaped: 13515</li> * </ul> * </li> - * <li> The following cases are the minimal amount of escaping required to + * <li>The following cases are the minimal amount of escaping required to * prevent test failure. * <ul> * <li>LINE_SEPARATOR - 1</li> * <li>PARAGRAPH_SEPARATOR - 1</li> * <li>FORMAT - 32</li> * <li>SURROGATE - 2048</li> - * <li>Total Characters Escaped: 2082</li> - * </li> - * </ul> - * </li> + * <li>Total Characters Escaped: 2082</li></li> + * </ul> </li> * </ol> + * + * @param ch character to check + * @return <code>true</code> if the character requires the \\uXXXX unicode + * character escape */ private static boolean needsUnicodeEscape(char ch) { switch (ch) { @@ -450,8 +448,8 @@ // these must be quoted or they will break the protocol return true; case NON_BREAKING_HYPHEN: - // This can be expanded into a break followed by a hyphen - return true; + // This can be expanded into a break followed by a hyphen + return true; default: switch (Character.getType(ch)) { // Conservative @@ -479,9 +477,9 @@ } /** - * Writes a safe escape sequence for a character. Some characters have a - * short form, such as \n for U+000D, while others are represented as \\xNN - * or \\uNNNN. + * Writes a safe escape sequence for a character. Some characters have a short + * form, such as \n for U+000D, while others are represented as \\xNN or + * \\uNNNN. * * @param ch character to unicode escape * @param charVector char vector to receive the unicode escaped representation @@ -574,11 +572,23 @@ } @Override - protected String getObjectTypeSignature(Object instance) { + protected String getObjectTypeSignature(Object instance) + throws SerializationException { assert (instance != null); Class<?> clazz = getClassForSerialization(instance); - return SerializabilityUtil.encodeSerializedInstanceReference(clazz); + if (hasFlags(FLAG_ELIDE_TYPE_NAMES)) { + if (serializationPolicy instanceof TypeNameObfuscator) { + return ((TypeNameObfuscator) serializationPolicy).getTypeIdForClass(clazz); + } + + throw new SerializationException("The GWT module was compiled with RPC " + + "type name elision enabled, but " + + serializationPolicy.getClass().getName() + " does not implement " + + TypeNameObfuscator.class.getName()); + } else { + return SerializabilityUtil.encodeSerializedInstanceReference(clazz); + } } @Override
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicy.java b/user/src/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicy.java index 4c1f51b..8665980 100644 --- a/user/src/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicy.java +++ b/user/src/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicy.java
@@ -18,12 +18,14 @@ import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.server.rpc.SerializationPolicy; +import java.util.HashMap; import java.util.Map; /** * Standard implementation of a {@link SerializationPolicy}. */ -public class StandardSerializationPolicy extends SerializationPolicy { +public class StandardSerializationPolicy extends SerializationPolicy implements + TypeNameObfuscator { /** * Field serializable types are primitives and types on the specified * whitelist. @@ -50,27 +52,56 @@ } private final Map<Class<?>, Boolean> deserializationWhitelist; - private final Map<Class<?>, Boolean> serializationWhitelist; + private final Map<Class<?>, String> typeIds; + private final Map<String, Class<?>> typeIdsToClasses = new HashMap<String, Class<?>>(); /** * Constructs a {@link SerializationPolicy} from a {@link Map}. */ public StandardSerializationPolicy( Map<Class<?>, Boolean> serializationWhitelist, - Map<Class<?>, Boolean> deserializationWhitelist) { + Map<Class<?>, Boolean> deserializationWhitelist, + Map<Class<?>, String> obfuscatedTypeIds) { if (serializationWhitelist == null || deserializationWhitelist == null) { throw new NullPointerException("whitelist"); } this.serializationWhitelist = serializationWhitelist; this.deserializationWhitelist = deserializationWhitelist; + this.typeIds = obfuscatedTypeIds; + + for (Map.Entry<Class<?>, String> entry : obfuscatedTypeIds.entrySet()) { + assert entry.getKey() != null : "null key"; + assert entry.getValue() != null : "null value for " + + entry.getKey().getName(); + assert !typeIdsToClasses.containsKey(entry.getValue()) : "Duplicate type id " + + entry.getValue(); + typeIdsToClasses.put(entry.getValue(), entry.getKey()); + } + } + + public final String getClassNameForTypeId(String id) + throws SerializationException { + Class<?> clazz = typeIdsToClasses.get(id); + if (clazz == null) { + return null; + } + + return clazz.getName(); + } + + public final String getTypeIdForClass(Class<?> clazz) + throws SerializationException { + return typeIds.get(clazz); } /* * (non-Javadoc) * - * @see com.google.gwt.user.server.rpc.SerializationPolicy#shouldDerializeFields(java.lang.String) + * @see + * com.google.gwt.user.server.rpc.SerializationPolicy#shouldDerializeFields + * (java.lang.String) */ @Override public boolean shouldDeserializeFields(Class<?> clazz) { @@ -80,7 +111,9 @@ /* * (non-Javadoc) * - * @see com.google.gwt.user.server.rpc.SerializationPolicy#shouldSerializeFields(java.lang.String) + * @see + * com.google.gwt.user.server.rpc.SerializationPolicy#shouldSerializeFields + * (java.lang.String) */ @Override public boolean shouldSerializeFields(Class<?> clazz) { @@ -90,7 +123,9 @@ /* * (non-Javadoc) * - * @see com.google.gwt.user.server.rpc.SerializationPolicy#validateDeserialize(java.lang.String) + * @see + * com.google.gwt.user.server.rpc.SerializationPolicy#validateDeserialize( + * java.lang.String) */ @Override public void validateDeserialize(Class<?> clazz) throws SerializationException { @@ -105,7 +140,9 @@ /* * (non-Javadoc) * - * @see com.google.gwt.user.server.rpc.SerializationPolicy#validateSerialize(java.lang.String) + * @see + * com.google.gwt.user.server.rpc.SerializationPolicy#validateSerialize(java + * .lang.String) */ @Override public void validateSerialize(Class<?> clazz) throws SerializationException {
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/TypeNameObfuscator.java b/user/src/com/google/gwt/user/server/rpc/impl/TypeNameObfuscator.java new file mode 100644 index 0000000..53f1d62 --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/impl/TypeNameObfuscator.java
@@ -0,0 +1,62 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.server.rpc.impl; + +import com.google.gwt.user.client.rpc.SerializationException; + +/** + * This is a private interface that allows ProxyCreator to provide obfuscated + * type names to the server components via {@link StandardSerializationPolicy}. + * <p> + * The particulars of the implementation are deeply tied to the specifics of the + * RPC wire format and the code generated by TypeSerializerCreator. + * <p> + * This interface is not public in order to allow the API to be switched from + * Strings to ints in a future revision. + */ +public interface TypeNameObfuscator { + /** + * A reserved ID for specifying the identifier for the service interface + * itself. + */ + String SERVICE_INTERFACE_ID = "_"; + + /* + * TODO: Replace strings with integral constants once the RPC whitelist can be + * given as a hard requirement for deploying GWT-RPC. + */ + /** + * Returns the name of the class that should be instantiated based on an + * obfuscated identifier. + * + * @param id the type id that was present in the RPC payload + * @return the name of the class, suitable for use by {@link Class#forName}, + * to be instantiated + * @throws SerializationException if there is no class that corresponds to the + * obfuscated id + */ + String getClassNameForTypeId(String id) throws SerializationException; + + /** + * Returns the obfuscated identifier to be used to encode a class in the RPC + * wire format. + * + * @param clazz the class to be transmitted + * @return the obfuscated type identifier. + * @throws SerializationException + */ + String getTypeIdForClass(Class<?> clazz) throws SerializationException; +}
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 807358d..c02281b 100644 --- a/user/super/com/google/gwt/emul/java/lang/Class.java +++ b/user/super/com/google/gwt/emul/java/lang/Class.java
@@ -35,12 +35,13 @@ * * @skip */ - static <T> Class<T> createForArray(String packageName, String className) { + static <T> Class<T> createForArray(String packageName, String className, Class<?> componentType) { // Initialize here to avoid method inliner Class<T> clazz = new Class<T>(); clazz.typeName = packageName + className; clazz.modifiers = ARRAY; clazz.superclass = Object.class; + clazz.componentType = componentType; return clazz; } @@ -68,8 +69,8 @@ // Initialize here to avoid method inliner Class<T> clazz = new Class<T>(); clazz.typeName = packageName + className; - clazz.modifiers = ENUM; - clazz.superclass = superclass; + clazz.modifiers = (enumConstantsFunc != null) ? ENUM : 0; + clazz.superclass = clazz.enumSuperclass = superclass; clazz.enumConstantsFunc = enumConstantsFunc; return clazz; } @@ -99,11 +100,15 @@ clazz.modifiers = PRIMITIVE; return clazz; } + + private Class<?> componentType; @SuppressWarnings("unused") private JavaScriptObject enumConstantsFunc; + + private Class<? super T> enumSuperclass; - private int modifiers; + int modifiers; private String typeName; @@ -123,16 +128,29 @@ return false; } + public Class<?> getComponentType() { + return componentType; + } + public native T[] getEnumConstants() /*-{ return this.@java.lang.Class::enumConstantsFunc && (this.@java.lang.Class::enumConstantsFunc)(); }-*/; + /** + * Used by Enum to allow getSuperclass() to be pruned. + */ + public Class<? super T> getEnumSuperclass() { + return enumSuperclass; + } + public String getName() { + // This body may be replaced by the compiler return typeName; } public Class<? super T> getSuperclass() { + // This body may be replaced by the compiler return superclass; }
diff --git a/user/super/com/google/gwt/emul/java/lang/Enum.java b/user/super/com/google/gwt/emul/java/lang/Enum.java index cfbac7e..4469adf 100644 --- a/user/super/com/google/gwt/emul/java/lang/Enum.java +++ b/user/super/com/google/gwt/emul/java/lang/Enum.java
@@ -93,7 +93,12 @@ @SuppressWarnings("unchecked") public final Class<E> getDeclaringClass() { Class clazz = getClass(); - Class superclass = clazz.getSuperclass(); + assert clazz != null : "clazz"; + + // Don't use getSuperclass() to allow that method to be pruned for most + // class types + Class superclass = clazz.getEnumSuperclass(); + assert superclass != null : "superclass"; return (superclass == Enum.class) ? clazz : superclass; }
diff --git a/user/super/com/google/gwt/emul/java/lang/System.java b/user/super/com/google/gwt/emul/java/lang/System.java index a6d22e1..8567ef6 100644 --- a/user/super/com/google/gwt/emul/java/lang/System.java +++ b/user/super/com/google/gwt/emul/java/lang/System.java
@@ -43,14 +43,17 @@ if (src == null || dest == null) { throw new NullPointerException(); } - - // TODO: use Class objects when Class.getComponentType() is supported. - String srcTypeName = src.getClass().getName(); - String destTypeName = dest.getClass().getName(); - if (srcTypeName.charAt(0) != '[' || destTypeName.charAt(0) != '[') { + + Class<?> srcType = src.getClass(); + Class<?> destType = dest.getClass(); + if (!srcType.isArray() || !destType.isArray()) { throw new ArrayStoreException("Must be array types"); } - if (srcTypeName.charAt(1) != destTypeName.charAt(1)) { + + Class<?> srcComp = srcType.getComponentType(); + Class<?> destComp = destType.getComponentType(); + if (srcComp.modifiers != destComp.modifiers + || (srcComp.isPrimitive() && !srcComp.equals(destComp))) { throw new ArrayStoreException("Array types must match"); } int srclen = getArrayLength(src); @@ -64,8 +67,8 @@ * can copy them in native code for speed. Otherwise, we have to copy them * in Java so we get appropriate errors. */ - if ((srcTypeName.charAt(1) == 'L' || srcTypeName.charAt(1) == '[') - && !srcTypeName.equals(destTypeName)) { + if ((!srcComp.isPrimitive() || srcComp.isArray()) + && !srcType.equals(destType)) { // copy in Java to make sure we get ArrayStoreExceptions if the values // aren't compatible Object[] srcArray = (Object[]) src; @@ -125,7 +128,7 @@ /** * Copy an array using native Javascript. The destination array must be a real - * Java array (ie, already has the GWT type info on it). No error checking is + * Java array (ie, already has the GWT type info on it). No error checking is * performed -- the caller is expected to have verified everything first. * * @param src source array for copy
diff --git a/user/super/com/google/gwt/emul/java/util/Arrays.java b/user/super/com/google/gwt/emul/java/util/Arrays.java index 56d1778..a7a2a70 100644 --- a/user/super/com/google/gwt/emul/java/util/Arrays.java +++ b/user/super/com/google/gwt/emul/java/util/Arrays.java
@@ -411,14 +411,14 @@ if (obj1.equals(obj2)) { continue; } - String class1 = obj1.getClass().getName(); - String class2 = obj2.getClass().getName(); + Class<?> class1 = obj1.getClass(); + Class<?> class2 = obj2.getClass(); // We have to test and see if these are two arrays of the same type, // then see what types of arrays they are and dispatch to the // appropriate equals - if (!class1.startsWith("[") || !class1.equals(class2)) { + if (!class1.isArray() || !class1.equals(class2)) { return false; } @@ -1165,7 +1165,7 @@ Object obj = a[i]; if (obj == null) { b.append("null"); - } else if (obj.getClass().getName().startsWith("[")) { + } else if (obj.getClass().isArray()) { if (obj instanceof Object[]) { if (arraysIveSeen.contains(obj)) { b.append("[...]");
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 8cb569a..94a3d54 100644 --- a/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java
@@ -52,11 +52,13 @@ public void testArray() { Object o = new Foo[3]; assertEquals(Foo[].class, o.getClass()); - assertEquals(Object.class, o.getClass().getSuperclass()); - assertEquals("[Lcom.google.gwt.dev.jjs.test.ClassObjectTest$Foo;", - o.getClass().getName()); - assertEquals("class [Lcom.google.gwt.dev.jjs.test.ClassObjectTest$Foo;", - o.getClass().toString()); + if (expectClassMetadata()) { + assertEquals(Object.class, o.getClass().getSuperclass()); + assertEquals("[Lcom.google.gwt.dev.jjs.test.ClassObjectTest$Foo;", + o.getClass().getName()); + assertEquals("class [Lcom.google.gwt.dev.jjs.test.ClassObjectTest$Foo;", + o.getClass().toString()); + } assertTrue(o.getClass().isArray()); assertFalse(o.getClass().isEnum()); assertFalse(o.getClass().isInterface()); @@ -81,11 +83,13 @@ public void testClass() { Object o = new Foo(); assertEquals(Foo.class, o.getClass()); - assertEquals(Object.class, o.getClass().getSuperclass()); - assertEquals("com.google.gwt.dev.jjs.test.ClassObjectTest$Foo", - Foo.class.getName()); - assertEquals("class com.google.gwt.dev.jjs.test.ClassObjectTest$Foo", - Foo.class.toString()); + if (expectClassMetadata()) { + assertEquals(Object.class, o.getClass().getSuperclass()); + assertEquals("com.google.gwt.dev.jjs.test.ClassObjectTest$Foo", + Foo.class.getName()); + assertEquals("class com.google.gwt.dev.jjs.test.ClassObjectTest$Foo", + Foo.class.toString()); + } assertFalse(Foo.class.isArray()); assertFalse(Foo.class.isEnum()); assertFalse(Foo.class.isInterface()); @@ -95,18 +99,22 @@ public void testCloneClassLiteral() { // getBarClass() should inline, causing a clone of a class literal - assertEquals("com.google.gwt.dev.jjs.test.ClassObjectTest$Bar", - getBarClass().getName()); + if (expectClassMetadata()) { + assertEquals("com.google.gwt.dev.jjs.test.ClassObjectTest$Bar", + getBarClass().getName()); + } } public void testEnum() { Object o = Bar.BAR; assertEquals(Bar.class, o.getClass()); - assertEquals(Enum.class, o.getClass().getSuperclass()); - assertEquals("com.google.gwt.dev.jjs.test.ClassObjectTest$Bar", - o.getClass().getName()); - assertEquals("class com.google.gwt.dev.jjs.test.ClassObjectTest$Bar", - o.getClass().toString()); + if (expectClassMetadata()) { + assertEquals(Enum.class, o.getClass().getSuperclass()); + assertEquals("com.google.gwt.dev.jjs.test.ClassObjectTest$Bar", + o.getClass().getName()); + assertEquals("class com.google.gwt.dev.jjs.test.ClassObjectTest$Bar", + o.getClass().toString()); + } assertFalse(o.getClass().isArray()); assertTrue(o.getClass().isEnum()); assertFalse(o.getClass().isInterface()); @@ -116,27 +124,32 @@ public void testEnumSubclass() { Object o = Bar.BAZ; - assertNotSame(Bar.class, o.getClass()); - assertEquals(Bar.class, o.getClass().getSuperclass()); - /* - * TODO: implement - */ - // assertEquals(Bar.class, o.getClass().getDeclaringClass()); - assertTrue(o.getClass().getName().endsWith("$1")); - assertTrue(o.getClass().toString().endsWith("$1")); - assertFalse(o.getClass().isArray()); - assertFalse(o.getClass().isEnum()); - assertFalse(o.getClass().isInterface()); - assertFalse(o.getClass().isPrimitive()); - assertNull(o.getClass().getEnumConstants()); + assertNotSame("Classes unexpectedly the same", Bar.class, o.getClass()); + if (expectClassMetadata()) { + assertEquals(Bar.class, o.getClass().getSuperclass()); + /* + * TODO: implement + */ + // assertEquals(Bar.class, o.getClass().getDeclaringClass()); + assertTrue(o.getClass().getName().endsWith("$1")); + assertTrue(o.getClass().toString().endsWith("$1")); + } + assertFalse("Should not be array", o.getClass().isArray()); + assertFalse("Should not be enum", o.getClass().isEnum()); + assertFalse("Should not be interface", o.getClass().isInterface()); + assertFalse("Should not be primitive", o.getClass().isPrimitive()); + assertNull("Constands should be null", o.getClass().getEnumConstants()); } public void testInterface() { assertNull(IFoo.class.getSuperclass()); - assertEquals("com.google.gwt.dev.jjs.test.ClassObjectTest$IFoo", - IFoo.class.getName()); - assertEquals("interface com.google.gwt.dev.jjs.test.ClassObjectTest$IFoo", - IFoo.class.toString()); + if (expectClassMetadata()) { + assertEquals("com.google.gwt.dev.jjs.test.ClassObjectTest$IFoo", + IFoo.class.getName()); + assertEquals( + "interface com.google.gwt.dev.jjs.test.ClassObjectTest$IFoo", + IFoo.class.toString()); + } assertFalse(IFoo.class.isArray()); assertFalse(IFoo.class.isEnum()); assertTrue(IFoo.class.isInterface()); @@ -146,8 +159,10 @@ public void testPrimitive() { assertNull(int.class.getSuperclass()); - assertEquals("int", int.class.getName()); - assertEquals("int", int.class.toString()); + if (expectClassMetadata()) { + assertEquals("int", int.class.getName()); + assertEquals("int", int.class.toString()); + } assertFalse(int.class.isArray()); assertFalse(int.class.isEnum()); assertFalse(int.class.isInterface()); @@ -155,6 +170,18 @@ assertNull(int.class.getEnumConstants()); } + private boolean expectClassMetadata() { + String name = Object.class.getName(); + + if (name.equals("java.lang.Object")) { + return true; + } else if (name.startsWith("Class$")) { + return false; + } + + throw new RuntimeException("Unexpected class name " + name); + } + private Class<? extends Bar> getBarClass() { return Bar.class; }
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java index 3f744d8..97ad1f0 100644 --- a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
@@ -277,6 +277,11 @@ } public void testClassLiterals() { + if (Object.class.getName().startsWith("Class$")) { + // If class metadata is disabled + return; + } + assertEquals("void", void.class.toString()); assertEquals("int", int.class.toString()); assertEquals("class java.lang.String", String.class.toString());
diff --git a/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java b/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java index 9d161ce..17f21bd 100644 --- a/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java
@@ -335,8 +335,13 @@ private void testClassLiteralAccess() { // ClassLiteralAccess o = Super.class; - assertEquals("class com.google.gwt.dev.jjs.test.CoverageTest$Super", - o.toString()); + String str = o.toString(); + + // Class metadata could be disabled + if (!str.startsWith("class Class$")) { + assertEquals("class com.google.gwt.dev.jjs.test.CoverageTest$Super", + str); + } } private void testCompoundAssignment() {
diff --git a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java index 151379a..5bf60b0 100644 --- a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
@@ -430,8 +430,11 @@ assertEquals(JavaScriptObject.class, Bar.class); assertEquals(Foo.class, Bar.class); - assertEquals("com.google.gwt.core.client.JavaScriptObject$", - JavaScriptObject.class.getName()); + if (!JavaScriptObject.class.getName().startsWith("Class$")) { + // Class metadata could be disabled + assertEquals("com.google.gwt.core.client.JavaScriptObject$", + JavaScriptObject.class.getName()); + } } public void testClassLiteralsArray() { @@ -451,8 +454,11 @@ assertEquals(JavaScriptObject[][].class, Bar[][].class); assertEquals(Foo[][].class, Bar[][].class); - assertEquals("[[Lcom.google.gwt.core.client.JavaScriptObject$;", - JavaScriptObject[][].class.getName()); + if (!JavaScriptObject.class.getName().startsWith("Class$")) { + // Class metadata could be disabled + assertEquals("[[Lcom.google.gwt.core.client.JavaScriptObject$;", + JavaScriptObject[][].class.getName()); + } } public void testEquality() {
diff --git a/user/test/com/google/gwt/dev/jjs/test/MiscellaneousTest.java b/user/test/com/google/gwt/dev/jjs/test/MiscellaneousTest.java index ad114c5..121934f 100644 --- a/user/test/com/google/gwt/dev/jjs/test/MiscellaneousTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/MiscellaneousTest.java
@@ -101,8 +101,10 @@ { // thwart optimizer Object f1 = FALSE ? (Object) new PolyA() : (Object) new IFoo[1]; - assertEquals("[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$IFoo;", - f1.getClass().getName()); + if (expectClassMetadata()) { + assertEquals("[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$IFoo;", + f1.getClass().getName()); + } assertFalse(f1 instanceof PolyA[][]); assertFalse(f1 instanceof IFoo[][]); assertFalse(f1 instanceof PolyA[]); @@ -121,8 +123,10 @@ { // thwart optimizer Object a1 = FALSE ? (Object) new PolyA() : (Object) new PolyA[1]; - assertEquals("[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$PolyA;", - a1.getClass().getName()); + if (expectClassMetadata()) { + assertEquals("[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$PolyA;", + a1.getClass().getName()); + } assertFalse(a1 instanceof PolyA[][]); assertFalse(a1 instanceof IFoo[][]); assertTrue(a1 instanceof PolyA[]); @@ -140,8 +144,10 @@ { // thwart optimizer Object f2 = FALSE ? (Object) new PolyA() : (Object) new IFoo[1][]; - assertEquals("[[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$IFoo;", - f2.getClass().getName()); + if (expectClassMetadata()) { + assertEquals("[[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$IFoo;", + f2.getClass().getName()); + } assertFalse(f2 instanceof PolyA[][]); assertTrue(f2 instanceof IFoo[][]); assertFalse(f2 instanceof PolyA[]); @@ -159,8 +165,10 @@ { // thwart optimizer Object a2 = FALSE ? (Object) new PolyA() : (Object) new PolyA[1][]; - assertEquals("[[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$PolyA;", - a2.getClass().getName()); + if (expectClassMetadata()) { + assertEquals("[[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$PolyA;", + a2.getClass().getName()); + } assertTrue(a2 instanceof PolyA[][]); assertTrue(a2 instanceof IFoo[][]); assertFalse(a2 instanceof PolyA[]); @@ -178,14 +186,16 @@ public void testArrays() { int[] c = new int[] {1, 2}; - assertEquals("[I", c.getClass().getName()); int[][] d = new int[][] { {1, 2}, {3, 4}}; - assertEquals("[[I", d.getClass().getName()); - assertEquals("[I", d[1].getClass().getName()); int[][][] e = new int[][][] { { {1, 2}, {3, 4}}, { {5, 6}, {7, 8}}}; - assertEquals("[[[I", e.getClass().getName()); - assertEquals("[[I", e[1].getClass().getName()); - assertEquals("[I", e[1][1].getClass().getName()); + if (expectClassMetadata()) { + assertEquals("[I", c.getClass().getName()); + assertEquals("[[I", d.getClass().getName()); + assertEquals("[I", d[1].getClass().getName()); + assertEquals("[[[I", e.getClass().getName()); + assertEquals("[[I", e[1].getClass().getName()); + assertEquals("[I", e[1][1].getClass().getName()); + } assertEquals(2, c[1]); assertEquals(3, d[1][0]); assertEquals(8, e[1][1][1]); @@ -276,8 +286,9 @@ FALSE = false; } else if (FALSE) { TRUE = true; - } else + } else { noOp(); + } } public void testString() { @@ -335,4 +346,15 @@ return "com.google.gwt.dev.jjs.test.MiscellaneousTest"; } + private boolean expectClassMetadata() { + String name = Object.class.getName(); + + if (name.equals("java.lang.Object")) { + return true; + } else if (name.startsWith("Class$")) { + return false; + } + + throw new RuntimeException("Unexpected class name " + name); + } }
diff --git a/user/test/com/google/gwt/i18n/client/I18NTest.java b/user/test/com/google/gwt/i18n/client/I18NTest.java index 043af2c..1f1ed33 100644 --- a/user/test/com/google/gwt/i18n/client/I18NTest.java +++ b/user/test/com/google/gwt/i18n/client/I18NTest.java
@@ -551,9 +551,9 @@ assertEquals("'A', 'arg', ','", singleQuotes); String testSomeObjectTypes = typed.testSomeObjectTypes(new I18NTest(), new StringBuffer("hello"), new Integer("34"), null); - assertEquals( - "this(null(com.google.gwt.i18n.client.I18NTest)), StringBuffer(hello), Integer(34), " - + "null(null);", testSomeObjectTypes); + assertEquals("this(null(" + I18NTest.class.getName() + + ")), StringBuffer(hello), Integer(34), " + "null(null);", + testSomeObjectTypes); } private void assertArrayEquals(String[] shouldBe, String[] test) {
diff --git a/user/test/com/google/gwt/user/RPCSuite.java b/user/test/com/google/gwt/user/RPCSuite.java index 499446a..b32b50f 100644 --- a/user/test/com/google/gwt/user/RPCSuite.java +++ b/user/test/com/google/gwt/user/RPCSuite.java
@@ -18,12 +18,19 @@ import com.google.gwt.dev.BootStrapPlatform; import com.google.gwt.junit.tools.GWTTestSuite; import com.google.gwt.user.client.rpc.CollectionsTest; +import com.google.gwt.user.client.rpc.CollectionsTestWithTypeObfuscation; import com.google.gwt.user.client.rpc.CustomFieldSerializerTest; +import com.google.gwt.user.client.rpc.CustomFieldSerializerTestWithTypeObfuscation; import com.google.gwt.user.client.rpc.EnumsTest; +import com.google.gwt.user.client.rpc.EnumsTestWithTypeObfuscation; import com.google.gwt.user.client.rpc.InheritanceTest; +import com.google.gwt.user.client.rpc.InheritanceTestWithTypeObfuscation; import com.google.gwt.user.client.rpc.ObjectGraphTest; +import com.google.gwt.user.client.rpc.ObjectGraphTestWithTypeObfuscation; import com.google.gwt.user.client.rpc.UnicodeEscapingTest; +import com.google.gwt.user.client.rpc.UnicodeEscapingTestWithTypeObfuscation; import com.google.gwt.user.client.rpc.ValueTypesTest; +import com.google.gwt.user.client.rpc.ValueTypesTestWithTypeObfuscation; import com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilderTest; import com.google.gwt.user.rebind.rpc.TypeHierarchyUtilsTest; import com.google.gwt.user.server.rpc.RPCRequestTest; @@ -53,9 +60,18 @@ GWTTestSuite suite = new GWTTestSuite( "Test for com.google.gwt.user.client.rpc"); + // Non GWTTestCases suite.addTestSuite(SerializableTypeOracleBuilderTest.class); suite.addTestSuite(TypeHierarchyUtilsTest.class); suite.addTestSuite(RPCTest.class); + suite.addTestSuite(com.google.gwt.user.server.rpc.RemoteServiceServletTest.class); + suite.addTestSuite(LegacySerializationPolicyTest.class); + suite.addTestSuite(StandardSerializationPolicyTest.class); + suite.addTestSuite(SerializationPolicyLoaderTest.class); + suite.addTestSuite(RPCServletUtilsTest.class); + suite.addTestSuite(RPCRequestTest.class); + + // GWTTestCases suite.addTestSuite(ValueTypesTest.class); suite.addTestSuite(EnumsTest.class); suite.addTestSuite(InheritanceTest.class); @@ -63,13 +79,17 @@ suite.addTestSuite(CustomFieldSerializerTest.class); suite.addTestSuite(ObjectGraphTest.class); suite.addTestSuite(com.google.gwt.user.client.rpc.RemoteServiceServletTest.class); - suite.addTestSuite(com.google.gwt.user.server.rpc.RemoteServiceServletTest.class); suite.addTestSuite(UnicodeEscapingTest.class); - suite.addTestSuite(LegacySerializationPolicyTest.class); - suite.addTestSuite(StandardSerializationPolicyTest.class); - suite.addTestSuite(SerializationPolicyLoaderTest.class); - suite.addTestSuite(RPCServletUtilsTest.class); - suite.addTestSuite(RPCRequestTest.class); + + // This test turns on the type-elision feature of RPC + suite.addTestSuite(ValueTypesTestWithTypeObfuscation.class); + suite.addTestSuite(EnumsTestWithTypeObfuscation.class); + suite.addTestSuite(InheritanceTestWithTypeObfuscation.class); + suite.addTestSuite(CollectionsTestWithTypeObfuscation.class); + suite.addTestSuite(CustomFieldSerializerTestWithTypeObfuscation.class); + suite.addTestSuite(ObjectGraphTestWithTypeObfuscation.class); + suite.addTestSuite(com.google.gwt.user.client.rpc.RemoteServiceServletTestWithTypeObfuscation.class); + suite.addTestSuite(UnicodeEscapingTestWithTypeObfuscation.class); return suite; } }
diff --git a/user/test/com/google/gwt/user/RPCSuiteWithObfuscation.gwt.xml b/user/test/com/google/gwt/user/RPCSuiteWithObfuscation.gwt.xml new file mode 100644 index 0000000..4f427b3 --- /dev/null +++ b/user/test/com/google/gwt/user/RPCSuiteWithObfuscation.gwt.xml
@@ -0,0 +1,18 @@ +<!-- --> +<!-- Copyright 2009 Google Inc. --> +<!-- Licensed under the Apache License, Version 2.0 (the "License"); you --> +<!-- may not use this file except in compliance with the License. You may --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. --> + +<module> + <inherits name="com.google.gwt.user.RPCSuite" /> + <inherits name="com.google.gwt.user.RemoteServiceObfuscateTypeNames" /> +</module>
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestWithTypeObfuscation.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestWithTypeObfuscation.java new file mode 100644 index 0000000..54d74c2 --- /dev/null +++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestWithTypeObfuscation.java
@@ -0,0 +1,26 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc; + +/** + * + */ +public class CollectionsTestWithTypeObfuscation extends CollectionsTest { + @Override + public String getModuleName() { + return "com.google.gwt.user.RPCSuiteWithObfuscation"; + } +}
diff --git a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestWithTypeObfuscation.java b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestWithTypeObfuscation.java new file mode 100644 index 0000000..8b22287 --- /dev/null +++ b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestWithTypeObfuscation.java
@@ -0,0 +1,26 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc; + +/** + * + */ +public class CustomFieldSerializerTestWithTypeObfuscation extends CustomFieldSerializerTest { + @Override + public String getModuleName() { + return "com.google.gwt.user.RPCSuiteWithObfuscation"; + } +}
diff --git a/user/test/com/google/gwt/user/client/rpc/EnumsTestWithTypeObfuscation.java b/user/test/com/google/gwt/user/client/rpc/EnumsTestWithTypeObfuscation.java new file mode 100644 index 0000000..476ac40 --- /dev/null +++ b/user/test/com/google/gwt/user/client/rpc/EnumsTestWithTypeObfuscation.java
@@ -0,0 +1,26 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc; + +/** + * + */ +public class EnumsTestWithTypeObfuscation extends EnumsTest { + @Override + public String getModuleName() { + return "com.google.gwt.user.RPCSuiteWithObfuscation"; + } +}
diff --git a/user/test/com/google/gwt/user/client/rpc/InheritanceTestWithTypeObfuscation.java b/user/test/com/google/gwt/user/client/rpc/InheritanceTestWithTypeObfuscation.java new file mode 100644 index 0000000..9761152 --- /dev/null +++ b/user/test/com/google/gwt/user/client/rpc/InheritanceTestWithTypeObfuscation.java
@@ -0,0 +1,26 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc; + +/** + * + */ +public class InheritanceTestWithTypeObfuscation extends InheritanceTest { + @Override + public String getModuleName() { + return "com.google.gwt.user.RPCSuiteWithObfuscation"; + } +}
diff --git a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java index 1819e85..ce6b964 100644 --- a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java +++ b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java
@@ -20,6 +20,7 @@ import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode; import com.google.gwt.user.client.rpc.TestSetFactory.SerializablePrivateNoArg; import com.google.gwt.user.client.rpc.TestSetFactory.SerializableWithTwoArrays; +import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream; /** * TODO: document me. @@ -84,7 +85,7 @@ } }); } - + public void testDoublyReferencedArray() { delayTestFinish(TEST_DELAY); @@ -102,7 +103,24 @@ } }); } - + + public void testElision() throws SerializationException { + ObjectGraphTestServiceAsync async = getServiceAsync(); + + SerializationStreamWriter writer = ((SerializationStreamFactory) async).createStreamWriter(); + AbstractSerializationStream stream = (AbstractSerializationStream) writer; + assertEquals("Missing flag", expectedObfuscationState(), + stream.hasFlags(AbstractSerializationStream.FLAG_ELIDE_TYPE_NAMES)); + + SerializableDoublyLinkedNode node = new SerializableDoublyLinkedNode(); + writer.writeObject(node); + String s = writer.toString(); + + // Don't use class.getName() due to conflict with removal of type names + assertEquals("Checking for SerializableDoublyLinkedNode", + expectedObfuscationState(), !s.contains("SerializableDoublyLinkedNode")); + } + public void testPrivateNoArg() { delayTestFinish(TEST_DELAY); @@ -139,6 +157,10 @@ }); } + protected boolean expectedObfuscationState() { + return false; + } + private ObjectGraphTestServiceAsync getServiceAsync() { if (objectGraphTestService == null) { objectGraphTestService = (ObjectGraphTestServiceAsync) GWT.create(ObjectGraphTestService.class);
diff --git a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestWithTypeObfuscation.java b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestWithTypeObfuscation.java new file mode 100644 index 0000000..53a149c --- /dev/null +++ b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestWithTypeObfuscation.java
@@ -0,0 +1,31 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc; + +/** + * + */ +public class ObjectGraphTestWithTypeObfuscation extends ObjectGraphTest { + @Override + public String getModuleName() { + return "com.google.gwt.user.RPCSuiteWithObfuscation"; + } + + @Override + protected boolean expectedObfuscationState() { + return true; + } +}
diff --git a/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java b/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java index 52251c8..4145b16 100644 --- a/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java +++ b/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java
@@ -27,9 +27,9 @@ * hierarchy looking for the service interface. Prior to this test the servlet * would only look into the concrete class but not in any of its super classes. * - * See <a - * href="http://code.google.com/p/google-web-toolkit/issues/detail?id=50&can=3&q=">Bug - * 50</a> for more details. + * See <a href= + * "http://code.google.com/p/google-web-toolkit/issues/detail?id=50&can=3&q=" + * >Bug 50</a> for more details. * <p> * This test works in conjunction with * {@link com.google.gwt.user.server.rpc.RemoteServiceServletTestServiceImpl}. @@ -38,7 +38,7 @@ public class RemoteServiceServletTest extends GWTTestCase { private static final int TEST_DELAY = 10000; - private static RemoteServiceServletTestServiceAsync getAsyncService() { + protected static RemoteServiceServletTestServiceAsync getAsyncService() { RemoteServiceServletTestServiceAsync service = (RemoteServiceServletTestServiceAsync) GWT.create(RemoteServiceServletTestService.class); ((ServiceDefTarget) service).setServiceEntryPoint(GWT.getModuleBaseURL()
diff --git a/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestWithTypeObfuscation.java b/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestWithTypeObfuscation.java new file mode 100644 index 0000000..82ad29f --- /dev/null +++ b/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestWithTypeObfuscation.java
@@ -0,0 +1,27 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc; + +/** + * + */ +public class RemoteServiceServletTestWithTypeObfuscation extends + RemoteServiceServletTest { + @Override + public String getModuleName() { + return "com.google.gwt.user.RPCSuiteWithObfuscation"; + } +}
diff --git a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTestWithTypeObfuscation.java b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTestWithTypeObfuscation.java new file mode 100644 index 0000000..2a9a23a --- /dev/null +++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTestWithTypeObfuscation.java
@@ -0,0 +1,26 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc; + +/** + * + */ +public class UnicodeEscapingTestWithTypeObfuscation extends UnicodeEscapingTest { + @Override + public String getModuleName() { + return "com.google.gwt.user.RPCSuiteWithObfuscation"; + } +}
diff --git a/user/test/com/google/gwt/user/client/rpc/ValueTypesTest.java b/user/test/com/google/gwt/user/client/rpc/ValueTypesTest.java index 7f82ce2..ff8eee4 100644 --- a/user/test/com/google/gwt/user/client/rpc/ValueTypesTest.java +++ b/user/test/com/google/gwt/user/client/rpc/ValueTypesTest.java
@@ -22,6 +22,7 @@ * TODO: document me. */ public class ValueTypesTest extends GWTTestCase { + private static final int TEST_DELAY = 5000; private ValueTypesTestServiceAsync primitiveTypeTestService;
diff --git a/user/test/com/google/gwt/user/client/rpc/ValueTypesTestWithTypeObfuscation.java b/user/test/com/google/gwt/user/client/rpc/ValueTypesTestWithTypeObfuscation.java new file mode 100644 index 0000000..aa30cff --- /dev/null +++ b/user/test/com/google/gwt/user/client/rpc/ValueTypesTestWithTypeObfuscation.java
@@ -0,0 +1,27 @@ +/* + * Copyright 2009 Google Inc. + * + * 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 com.google.gwt.user.client.rpc; + +/** + * This is a top-level type because our test-runner doesn't like running static + * nested classes. + */ +public class ValueTypesTestWithTypeObfuscation extends ValueTypesTest { + @Override + public String getModuleName() { + return "com.google.gwt.user.RPCSuiteWithObfuscation"; + } +} \ No newline at end of file
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCRequestTest.java b/user/test/com/google/gwt/user/server/rpc/RPCRequestTest.java index a8d4bfa..73bd3fe 100644 --- a/user/test/com/google/gwt/user/server/rpc/RPCRequestTest.java +++ b/user/test/com/google/gwt/user/server/rpc/RPCRequestTest.java
@@ -43,7 +43,7 @@ // Test simple case Object params[] = new Object[] {"abcdefg", 1234}; - RPCRequest request = new RPCRequest(method, params, null); + RPCRequest request = new RPCRequest(method, params, null, 0); String strRequest = request.toString(); assertEquals("com.google.gwt.user.server.rpc.RPCRequestTest$" + "MockRequestImplementation.doSomething(\"abcdefg\", 1234)", @@ -51,7 +51,7 @@ // Test case with a string that needs escaping Object params2[] = new Object[] {"ab\"cde\"fg", 1234}; - RPCRequest request2 = new RPCRequest(method, params2, null); + RPCRequest request2 = new RPCRequest(method, params2, null, 0); String strRequest2 = request2.toString(); System.out.println(strRequest2); assertEquals("com.google.gwt.user.server.rpc.RPCRequestTest$"
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCTest.java b/user/test/com/google/gwt/user/server/rpc/RPCTest.java index 20dd1b9..404c8f4 100644 --- a/user/test/com/google/gwt/user/server/rpc/RPCTest.java +++ b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
@@ -23,9 +23,11 @@ import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream; import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader; +import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator; import junit.framework.TestCase; +import java.io.Serializable; import java.lang.reflect.Method; /** @@ -34,6 +36,24 @@ @SuppressWarnings("deprecation") public class RPCTest extends TestCase { + /** + * Test serialization class. + * + * @see RPCTest#testElision() + */ + public static class C implements Serializable { + int i = 0; + } + + /** + * Test serialization class. + * + * @see RPCTest#testElision() + */ + private static interface CC { + C c(); + } + private static interface A extends RemoteService { void method1() throws SerializableException; @@ -46,9 +66,9 @@ void method1(); } - private static final String VALID_ENCODED_REQUEST = "" + - AbstractSerializationStream.SERIALIZATION_STREAM_VERSION + - RPC_SEPARATOR_CHAR + // version + private static final String VALID_ENCODED_REQUEST = "" + + AbstractSerializationStream.SERIALIZATION_STREAM_VERSION + + RPC_SEPARATOR_CHAR + // version "0" + RPC_SEPARATOR_CHAR + // flags "4" + RPC_SEPARATOR_CHAR + // string table entry count A.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1 @@ -61,9 +81,9 @@ "2" + RPC_SEPARATOR_CHAR + // method name "0" + RPC_SEPARATOR_CHAR; // param count - private static final String INVALID_METHOD_REQUEST = "" + - AbstractSerializationStream.SERIALIZATION_STREAM_VERSION + - RPC_SEPARATOR_CHAR + // version + private static final String INVALID_METHOD_REQUEST = "" + + AbstractSerializationStream.SERIALIZATION_STREAM_VERSION + + RPC_SEPARATOR_CHAR + // version "0" + RPC_SEPARATOR_CHAR + // flags "4" + RPC_SEPARATOR_CHAR + // string table entry count A.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1 @@ -76,9 +96,9 @@ "2" + RPC_SEPARATOR_CHAR + // method name "0" + RPC_SEPARATOR_CHAR; // param count - private static final String INVALID_INTERFACE_REQUEST = "" + - AbstractSerializationStream.SERIALIZATION_STREAM_VERSION + - RPC_SEPARATOR_CHAR + // version + private static final String INVALID_INTERFACE_REQUEST = "" + + AbstractSerializationStream.SERIALIZATION_STREAM_VERSION + + RPC_SEPARATOR_CHAR + // version "0" + RPC_SEPARATOR_CHAR + // flags "4" + RPC_SEPARATOR_CHAR + // string table entry count B.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1 @@ -91,9 +111,9 @@ "2" + RPC_SEPARATOR_CHAR + // method name "0" + RPC_SEPARATOR_CHAR; // param count - private static final String STRING_QUOTE_REQUEST = "" + - AbstractSerializationStream.SERIALIZATION_STREAM_VERSION + - RPC_SEPARATOR_CHAR + // version + private static final String STRING_QUOTE_REQUEST = "" + + AbstractSerializationStream.SERIALIZATION_STREAM_VERSION + + RPC_SEPARATOR_CHAR + // version "0" + RPC_SEPARATOR_CHAR + // flags "7" + RPC_SEPARATOR_CHAR + // string table entry count A.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1 @@ -106,8 +126,7 @@ "3" + RPC_SEPARATOR_CHAR + // module base URL "4" + RPC_SEPARATOR_CHAR + // whitelist hashcode "5" + RPC_SEPARATOR_CHAR + // begin test data - "6" + RPC_SEPARATOR_CHAR + - "7" + RPC_SEPARATOR_CHAR; + "6" + RPC_SEPARATOR_CHAR + "7" + RPC_SEPARATOR_CHAR; private static final String VALID_V2_ENCODED_REQUEST = "2\uffff" + // version "0\uffff" + // flags @@ -174,7 +193,8 @@ /** * Tests for method {@link RPC#decodeRequest(String)} * - * <p/> Cases: + * <p/> + * Cases: * <ol> * <li>String == null</li> * <li>String == ""</li> @@ -205,7 +225,8 @@ /** * Tests for method {@link RPC#decodeRequest(String, Class)} * - * <p/> Cases: + * <p/> + * Cases: * <ol> * <li>String == null</li> * <li>String == ""</li> @@ -265,6 +286,83 @@ } } + public void testElision() throws SecurityException, SerializationException, + NoSuchMethodException { + class TestPolicy extends SerializationPolicy implements TypeNameObfuscator { + private static final String C_NAME = "__c__"; + + public String getClassNameForTypeId(String id) + throws SerializationException { + assertEquals(C_NAME, id); + return C.class.getName(); + } + + public String getTypeIdForClass(Class<?> clazz) + throws SerializationException { + assertEquals(C.class, clazz); + return C_NAME; + } + + @Override + public boolean shouldDeserializeFields(Class<?> clazz) { + return C.class.equals(clazz); + } + + @Override + public boolean shouldSerializeFields(Class<?> clazz) { + return C.class.equals(clazz); + } + + @Override + public void validateDeserialize(Class<?> clazz) + throws SerializationException { + } + + @Override + public void validateSerialize(Class<?> clazz) + throws SerializationException { + } + } + + String rpc = RPC.encodeResponseForSuccess(CC.class.getMethod("c"), new C(), + new TestPolicy(), AbstractSerializationStream.FLAG_ELIDE_TYPE_NAMES); + assertTrue(rpc.contains(TestPolicy.C_NAME)); + assertFalse(rpc.contains(C.class.getName())); + } + + public void testElisionWithNoObfuscator() throws SecurityException, + NoSuchMethodException { + class TestPolicy extends SerializationPolicy { + @Override + public boolean shouldDeserializeFields(Class<?> clazz) { + return C.class.equals(clazz); + } + + @Override + public boolean shouldSerializeFields(Class<?> clazz) { + return C.class.equals(clazz); + } + + @Override + public void validateDeserialize(Class<?> clazz) + throws SerializationException { + } + + @Override + public void validateSerialize(Class<?> clazz) + throws SerializationException { + } + } + + try { + RPC.encodeResponseForSuccess(CC.class.getMethod("c"), new C(), + new TestPolicy(), AbstractSerializationStream.FLAG_ELIDE_TYPE_NAMES); + fail("Should have thrown a SerializationException"); + } catch (SerializationException e) { + // OK + } + } + /** * Tests for method {@link RPC#encodeResponseForFailure(Method, Throwable)}. * @@ -307,8 +405,8 @@ } // Case 4 - String str = RPC.encodeResponseForFailure( - A.class.getMethod("method1"), new SerializableException()); + String str = RPC.encodeResponseForFailure(A.class.getMethod("method1"), + new SerializableException()); assertTrue(str.indexOf("SerializableException") != -1); } @@ -451,9 +549,10 @@ } }, A_method1, null); } - + public void testSerializationStreamDequote() throws SerializationException { - ServerSerializationStreamReader reader = new ServerSerializationStreamReader(null, null); + ServerSerializationStreamReader reader = new ServerSerializationStreamReader( + null, null); reader.prepareToRead(STRING_QUOTE_REQUEST); assertEquals("Raw backslash \\", reader.readString()); assertEquals("Quoted separator " + RPC_SEPARATOR_CHAR, reader.readString());
diff --git a/user/test/com/google/gwt/user/server/rpc/SerializationPolicyLoaderTest.java b/user/test/com/google/gwt/user/server/rpc/SerializationPolicyLoaderTest.java index 4cea99e..f3ac42a 100644 --- a/user/test/com/google/gwt/user/server/rpc/SerializationPolicyLoaderTest.java +++ b/user/test/com/google/gwt/user/server/rpc/SerializationPolicyLoaderTest.java
@@ -16,6 +16,7 @@ package com.google.gwt.user.server.rpc; import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator; import junit.framework.TestCase; @@ -39,6 +40,9 @@ static class B { } + static class I { + } + private static final String OLD_VALID_POLICY_FILE_CONTENTS = A.class.getName() + ", true"; @@ -48,8 +52,10 @@ private static final String POLICY_FILE_TRIGGERS_CLASSNOTFOUND = "C,false"; private static final String VALID_POLICY_FILE_CONTENTS = A.class.getName() - + ", true, true, false, false\n" + B.class.getName() - + ", false, false, true, false\n"; + + ", true, true, false, false, a, 1234\n" + B.class.getName() + + ", false, false, true, false, b, 5678\n" + I.class.getName() + + ", false, false, false, false, " + + TypeNameObfuscator.SERVICE_INTERFACE_ID + ", 999\n"; public static InputStream getInputStreamFromString(String content) throws UnsupportedEncodingException { @@ -82,6 +88,17 @@ assertCannotDeserialize(sp, B.class); assertTrue(sp.shouldDeserializeFields(B.class)); assertCannotDeserialize(sp, B.class); + + assertTrue(sp instanceof TypeNameObfuscator); + TypeNameObfuscator ob = (TypeNameObfuscator) sp; + assertEquals("a", ob.getTypeIdForClass(A.class)); + assertEquals(A.class.getName(), ob.getClassNameForTypeId("a")); + assertEquals("b", ob.getTypeIdForClass(B.class)); + assertEquals(B.class.getName(), ob.getClassNameForTypeId("b")); + assertEquals(TypeNameObfuscator.SERVICE_INTERFACE_ID, + ob.getTypeIdForClass(I.class)); + assertEquals(I.class.getName(), + ob.getClassNameForTypeId(TypeNameObfuscator.SERVICE_INTERFACE_ID)); } /**
diff --git a/user/test/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicyTest.java b/user/test/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicyTest.java index e81ead5..9d28e6a 100644 --- a/user/test/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicyTest.java +++ b/user/test/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicyTest.java
@@ -20,6 +20,7 @@ import junit.framework.TestCase; import java.util.HashMap; +import java.util.Map; /** * Tests for the {@link StandardSerializationPolicy} class. @@ -116,9 +117,14 @@ } StandardSerializationPolicy getStandardSerializationPolicy() { - java.util.Map map = new HashMap(); + Map map = new HashMap(); map.put(A.class, Boolean.TRUE); map.put(C.class, Boolean.FALSE); - return new StandardSerializationPolicy(map, map); + + Map typeIds = new HashMap(); + typeIds.put(A.class, "A"); + typeIds.put(B.class, "B"); + + return new StandardSerializationPolicy(map, map, typeIds); } }