blob: 3089112b7f571f9c25c2a77b2a4101dc9430c829 [file] [log] [blame]
/*
* 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.impl;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.cfg.ConfigProps;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JExpression;
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.JNewInstance;
import com.google.gwt.dev.jjs.ast.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import java.util.List;
/**
* Performs optimizations on Enums.
*/
public class EnumNameObfuscator {
private static final String ENUM_NAME_OBFUSCATION_PROPERTY =
"compiler.enum.obfuscate.names";
private static final String ENUM_NAME_OBFUSCATION_BLACKLIST_PROPERTY =
"compiler.enum.obfuscate.names.blacklist";
private static class EnumNameCallChecker extends JVisitor {
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 List<String> blacklistedEnums;
private final JDeclaredType stringType;
private EnumNameCallChecker(JProgram jprogram, TreeLogger logger,
List<String> blacklistedEnums) {
this.logger = logger;
this.blacklistedEnums = blacklistedEnums;
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) {
List<JParameter> params = enumMethod.getParams();
if (enumMethod.getName().equals("valueOf") &&
params.size() == 2 && params.get(0).getType() == classType
&& params.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)) {
return;
}
JClassType cType = (JClassType) type;
if (typeInBlacklist(blacklistedEnums, cType)) {
return;
}
if (target == enumNameMethod || target == enumToStringMethod || target == enumValueOfMethod) {
warn(x);
return;
}
if (cType.isEnumOrSubclass() != null) {
return;
}
/*
* 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> params = target.getParams();
if (target.getName().equals("valueOf") &&
params.size() == 1 && params.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 JProgram jprogram;
private final JMethod makeEnumName;
private List<String> blacklistedEnums;
private boolean closureMode;
private final TreeLogger logger;
private EnumNameReplacer(JProgram jprogram, TreeLogger logger,
List<String> blacklistedEnums, boolean closureMode) {
this.logger = logger;
this.jprogram = jprogram;
this.blacklistedEnums = blacklistedEnums;
this.closureMode = closureMode;
this.makeEnumName = jprogram.getIndexedMethod("Util.makeEnumName");
}
private void exec() {
accept(jprogram);
}
@Override
public boolean visit(JClassType x, Context ctx) {
if (x.isEnumOrSubclass() == null || typeInBlacklist(blacklistedEnums, x)) {
return false;
}
trace(x);
return true;
}
@Override
public void endVisit(JNewInstance x, Context ctx) {
JConstructor target = x.getTarget();
JClassType enclosingType = target.getEnclosingType();
if (enclosingType.isEnumOrSubclass() == null) {
return;
}
if (typeInBlacklist(blacklistedEnums, enclosingType)) {
return;
}
JNewInstance newEnum = new JNewInstance(x);
List<JExpression> args = Lists.newArrayList(x.getArgs());
// replace first argument with null or the closure obfuscation function.
args.set(0, closureMode ?
new JMethodCall(x.getSourceInfo(), null, makeEnumName, args.get(0)) :
JNullLiteral.INSTANCE);
newEnum.addArgs(args);
ctx.replaceMe(newEnum);
}
private void trace(JClassType x) {
if (logger.isLoggable(TreeLogger.TRACE)) {
logger.log(TreeLogger.TRACE, "Obfuscating enum " + x + x.getSourceInfo().getFileName() + ":"
+ x.getSourceInfo().getStartLine());
}
}
}
private static boolean typeInBlacklist(List<String> blacklistedEnums, JClassType cType) {
return blacklistedEnums.contains(cType.getName().replace('$', '.'));
}
public static void exec(JProgram jprogram, TreeLogger logger, ConfigProps configProps) {
String enumObfucationProperty = configProps.getString(
EnumNameObfuscator.ENUM_NAME_OBFUSCATION_PROPERTY, "false");
// TODO(rluble): Replace configuration property by the command line option.
boolean closureMode = enumObfucationProperty.equals("closure");
if (enumObfucationProperty.equals("false")) {
return;
}
List<String> blacklistedEnums = configProps.getCommaSeparatedStrings(
EnumNameObfuscator.ENUM_NAME_OBFUSCATION_BLACKLIST_PROPERTY);
new EnumNameCallChecker(jprogram, logger, blacklistedEnums).accept(jprogram);
new EnumNameReplacer(jprogram, logger, blacklistedEnums, closureMode).exec();
}
}