| /* |
| * Copyright 2010 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.jjs; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JDeclaredType; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| import com.google.gwt.dev.jjs.ast.JNonNullType; |
| import com.google.gwt.dev.jjs.ast.JParameter; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReturnStatement; |
| import com.google.gwt.dev.jjs.ast.JThisRef; |
| |
| import java.util.List; |
| |
| /** |
| * Performs optimizations on Enums. |
| */ |
| public class EnumNameObfuscator { |
| |
| private static class EnumNameCallChecker extends JModVisitor { |
| |
| private final TreeLogger logger; |
| private final JMethod enumNameMethod; |
| private final JMethod enumToStringMethod; |
| private final JMethod enumValueOfMethod; |
| private final JDeclaredType classType; |
| private final JDeclaredType enumType; |
| private final JDeclaredType stringType; |
| |
| public EnumNameCallChecker(JProgram jprogram, TreeLogger logger) { |
| this.logger = logger; |
| this.enumNameMethod = jprogram.getIndexedMethod("Enum.name"); |
| this.enumToStringMethod = jprogram.getIndexedMethod("Enum.toString"); |
| this.classType = jprogram.getIndexedType("Class"); |
| this.enumType = jprogram.getIndexedType("Enum"); |
| this.stringType = jprogram.getIndexedType("String"); |
| |
| /* |
| * Find the correct version of enumValueOfMethod. |
| * Note: it doesn't work to check against a ref returned by |
| * jprogram.getIndexedMethod("Enum.valueOf"), since there are |
| * 2 different versions of Enum.valueOf in our jre emulation library, |
| * and the indexed ref won't reliably flag the public instance, which |
| * is the one we want here (i.e. Enum.valueOf(Class<T>,String)). |
| * The other version is protected, but is called by the generated |
| * constructors for sub-classes of Enum, and we don't want to warn for |
| * those cases. |
| */ |
| JMethod foundMethod = null; |
| List<JMethod> enumMethods = enumType.getMethods(); |
| for (JMethod enumMethod : enumMethods) { |
| if ("valueOf".equals(enumMethod.getName())) { |
| List<JParameter> jParameters = enumMethod.getParams(); |
| if (jParameters.size() == 2 && |
| jParameters.get(0).getType() == classType && |
| jParameters.get(1).getType() == stringType) { |
| foundMethod = enumMethod; |
| break; |
| } |
| } |
| } |
| this.enumValueOfMethod = foundMethod; |
| } |
| |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JMethod target = x.getTarget(); |
| JDeclaredType type = target.getEnclosingType(); |
| |
| if (type instanceof JClassType) { |
| JClassType cType = (JClassType) type; |
| |
| if (target == enumNameMethod || |
| target == enumToStringMethod || |
| target == enumValueOfMethod) { |
| warn(x); |
| } else if (cType.isEnumOrSubclass() != null) { |
| if ("valueOf".equals(target.getName())) { |
| /* |
| * Check for calls to the auto-generated EnumSubType.valueOf(String). |
| * Note, the check of the signature for the single String arg version |
| * is to avoid flagging user-defined overloaded versions of the |
| * method, which are presumably ok. |
| */ |
| List<JParameter> jParameters = target.getParams(); |
| if (jParameters.size() == 1 && |
| jParameters.get(0).getType() == stringType) { |
| warn(x); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean visit(JClassType x, Context ctx) { |
| if (x == enumType) { |
| // don't traverse into Enum class itself, don't warn on internal method calls |
| return false; |
| } |
| return true; |
| } |
| |
| private void warn(JMethodCall x) { |
| /* TODO: add a way to suppress warning with annotation if you know what |
| * you're doing. |
| */ |
| logger.log(TreeLogger.WARN, "Call to Enum method " |
| + x.getTarget().getName() + " when enum obfuscation is enabled: " |
| + x.getSourceInfo().getFileName() + ":" |
| + x.getSourceInfo().getStartLine()); |
| } |
| } |
| |
| private static class EnumNameReplacer extends JModVisitor { |
| |
| private final TreeLogger logger; |
| private final JProgram jprogram; |
| private final JMethod enumObfuscatedName; |
| private final JNonNullType enumType; |
| |
| public EnumNameReplacer(JProgram jprogram, TreeLogger logger) { |
| this.logger = logger; |
| this.jprogram = jprogram; |
| this.enumType = jprogram.getNonNullType(jprogram.getIndexedType("Enum")); |
| this.enumObfuscatedName = jprogram.getIndexedMethod("Enum.obfuscatedName"); |
| } |
| |
| @Override |
| public void endVisit(JReturnStatement x, Context ctx) { |
| info(x); |
| JReturnStatement toReturn = new JReturnStatement(x.getSourceInfo(), |
| new JMethodCall(x.getSourceInfo(), |
| new JThisRef(x.getSourceInfo(),enumType), enumObfuscatedName)); |
| ctx.replaceMe(toReturn); |
| } |
| |
| public void exec() { |
| accept(jprogram.getIndexedMethod("Enum.name")); |
| } |
| |
| private void info(JReturnStatement x) { |
| logger.log(TreeLogger.INFO, "Replacing Enum.name method : " |
| + x.getSourceInfo().getFileName() + ":" |
| + x.getSourceInfo().getStartLine()); |
| } |
| } |
| |
| public static void exec(JProgram jprogram, TreeLogger logger) { |
| new EnumNameCallChecker(jprogram, logger).accept(jprogram); |
| new EnumNameReplacer(jprogram, logger).exec(); |
| } |
| } |