blob: 1bddd9aed7d0a6eb550e6227ea90804fb28d7361 [file] [log] [blame]
/*
* 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.js;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameOf;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/**
* Replace references to functions which have post-obfuscation duplicate bodies
* by reference to a canonical one. Intended to run only when stack trace
* stripping is enabled.
*/
public class JsDuplicateFunctionRemover {
private class DuplicateFunctionBodyRecorder extends JsVisitor {
private final Set<JsName> dontReplace = new IdentityHashSet<JsName>();
private final Map<JsName, JsName> duplicateOriginalMap = new IdentityHashMap<JsName, JsName>();
private final Map<JsFunction, JsFunction> duplicateMethodOriginalMap = new IdentityHashMap<JsFunction, JsFunction>();
private final Stack<JsNameRef> invocationQualifiers = new Stack<JsNameRef>();
// static / global methods
private final Map<String, JsName> uniqueBodies = new HashMap<String, JsName>();
// vtable methods
private final Map<String, JsFunction> uniqueMethodBodies = new HashMap<String, JsFunction>();
public DuplicateFunctionBodyRecorder() {
// Add sentinel to stop Stack.peek() from throwing exception.
invocationQualifiers.add(null);
}
@Override
public void endVisit(JsInvocation x, JsContext ctx) {
if (x.getQualifier() instanceof JsNameRef) {
invocationQualifiers.pop();
}
}
@Override
public void endVisit(JsNameOf x, JsContext ctx) {
dontReplace.add(x.getName());
}
@Override
public void endVisit(JsNameRef x, JsContext ctx) {
if (x != invocationQualifiers.peek()) {
if (x.getName() != null) {
dontReplace.add(x.getName());
}
}
}
public Set<JsName> getBlacklist() {
return dontReplace;
}
public Map<JsName, JsName> getDuplicateMap() {
return duplicateOriginalMap;
}
public Map<JsFunction, JsFunction> getDuplicateMethodMap() {
return duplicateMethodOriginalMap;
}
@Override
public boolean visit(JsFunction x, JsContext ctx) {
String fnSource = x.toSource();
String body = fnSource.substring(fnSource.indexOf("("));
/*
* Static function processed separate from virtual functions
*/
if (x.getName() != null) {
JsName original = uniqueBodies.get(body);
if (original != null) {
duplicateOriginalMap.put(x.getName(), original);
} else {
uniqueBodies.put(body, x.getName());
}
} else if (x.isFromJava()) {
JsFunction original = uniqueMethodBodies.get(body);
if (original != null) {
duplicateMethodOriginalMap.put(x, original);
} else {
uniqueMethodBodies.put(body, x);
}
}
return true;
}
@Override
public boolean visit(JsInvocation x, JsContext ctx) {
if (x.getQualifier() instanceof JsNameRef) {
invocationQualifiers.push((JsNameRef) x.getQualifier());
}
return true;
}
}
private class ReplaceDuplicateInvocationNameRefs extends JsModVisitor {
private final Set<JsName> blacklist;
private final Map<JsFunction, JsFunction> dupMethodMap;
private final Map<JsFunction, JsName> hoistMap;
private final Map<JsName, JsName> duplicateMap;
public ReplaceDuplicateInvocationNameRefs(Map<JsName, JsName> duplicateMap,
Set<JsName> blacklist, Map<JsFunction, JsFunction> dupMethodMap,
Map<JsFunction, JsName> hoistMap) {
this.duplicateMap = duplicateMap;
this.blacklist = blacklist;
this.dupMethodMap = dupMethodMap;
this.hoistMap = hoistMap;
}
@Override
public void endVisit(JsFunction x, JsContext ctx) {
if (dupMethodMap.containsKey(x)) {
ctx.replaceMe(hoistMap.get(dupMethodMap.get(x)).makeRef(x.getSourceInfo()));
} else if (hoistMap.containsKey(x)) {
ctx.replaceMe(hoistMap.get(x).makeRef(x.getSourceInfo()));
}
}
@Override
public void endVisit(JsNameRef x, JsContext ctx) {
JsName orig = duplicateMap.get(x.getName());
if (orig != null && x.getName() != null
&& x.getName().getEnclosing() == program.getScope()
&& !blacklist.contains(x.getName()) && !blacklist.contains(orig)) {
ctx.replaceMe(orig.makeRef(x.getSourceInfo()));
}
}
}
// Needed for OptimizerTestBase
public static boolean exec(JsProgram program) {
return new JsDuplicateFunctionRemover(program).execImpl(program.getFragmentBlock(0));
}
public static boolean exec(JsProgram program, JsBlock fragment) {
return new JsDuplicateFunctionRemover(program).execImpl(fragment);
}
private final JsProgram program;
public JsDuplicateFunctionRemover(JsProgram program) {
this.program = program;
}
private boolean execImpl(JsBlock fragment) {
DuplicateFunctionBodyRecorder dfbr = new DuplicateFunctionBodyRecorder();
dfbr.accept(fragment);
int count = 0;
Map<JsFunction, JsName> hoistMap = new HashMap<JsFunction, JsName>();
// Hoist all anonymous versions
Map<JsFunction, JsFunction> dupMethodMap = dfbr.getDuplicateMethodMap();
for (JsFunction x : dupMethodMap.values()) {
if (!hoistMap.containsKey(x)) {
// move function to top scope and re-declaring it with a unique name
JsName newName = program.getScope().declareName("_DUP" + count++);
JsFunction newFunc = new JsFunction(x.getSourceInfo(),
program.getScope(), newName, x.isFromJava());
// we're not using the old function anymore, we can use reuse the body instead of cloning it
newFunc.setBody(x.getBody());
// also copy the parameters from the old function
newFunc.getParameters().addAll(x.getParameters());
// add the new function to the top level list of statements
fragment.getStatements().add(newFunc.makeStmt());
hoistMap.put(x, newName);
}
}
ReplaceDuplicateInvocationNameRefs rdup = new ReplaceDuplicateInvocationNameRefs(
dfbr.getDuplicateMap(), dfbr.getBlacklist(), dupMethodMap, hoistMap);
rdup.accept(fragment);
return rdup.didChange();
}
}