| /* |
| * 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 Stack<JsNameRef> invocationQualifiers = new Stack<JsNameRef>(); |
| |
| private final Map<String, JsName> uniqueBodies = new HashMap<String, JsName>(); |
| |
| 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; |
| } |
| |
| @Override |
| public boolean visit(JsFunction x, JsContext ctx) { |
| /* |
| * Don't process anonymous functions. |
| */ |
| if (x.getName() != null) { |
| String fnSource = x.toSource(); |
| String body = fnSource.substring(fnSource.indexOf("(")); |
| JsName original = uniqueBodies.get(body); |
| if (original != null) { |
| duplicateOriginalMap.put(x.getName(), original); |
| } else { |
| uniqueBodies.put(body, x.getName()); |
| } |
| } |
| 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<JsName, JsName> duplicateMap; |
| |
| public ReplaceDuplicateInvocationNameRefs(Map<JsName, JsName> duplicateMap, |
| Set<JsName> blacklist) { |
| this.duplicateMap = duplicateMap; |
| this.blacklist = blacklist; |
| } |
| |
| @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); |
| ReplaceDuplicateInvocationNameRefs rdup = new ReplaceDuplicateInvocationNameRefs( |
| dfbr.getDuplicateMap(), dfbr.getBlacklist()); |
| rdup.accept(fragment); |
| return rdup.didChange(); |
| } |
| } |