/* * 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.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 JDeclaredType classType; private final JMethod enumNameMethod; private final JMethod enumToStringMethod; private final JDeclaredType enumType; private final JMethod enumValueOfMethod; private final TreeLogger logger; 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 JMethod enumObfuscatedName; private final JClassType enumType; private final JProgram jprogram; private final TreeLogger logger; public EnumNameReplacer(JProgram jprogram, TreeLogger logger) { this.logger = logger; this.jprogram = jprogram; this.enumType = (JClassType) 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) { if (logger.isLoggable(TreeLogger.INFO)) { 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(); } }