| /* |
| * 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(); |
| } |
| } |