All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.js.JsDuplicateFunctionRemover Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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 dontReplace = new IdentityHashSet();

    private final Map duplicateOriginalMap = new IdentityHashMap();

    private final Map duplicateMethodOriginalMap = new IdentityHashMap();


    private final Stack invocationQualifiers = new Stack();

    // static / global methods
    private final Map uniqueBodies = new HashMap();

    // vtable methods
    private final Map uniqueMethodBodies = new HashMap();

    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 getBlacklist() {
      return dontReplace;
    }

    public Map getDuplicateMap() {
      return duplicateOriginalMap;
    }

    public Map 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 blacklist;
    private final Map dupMethodMap;
    private final Map hoistMap;

    private final Map duplicateMap;

    public ReplaceDuplicateInvocationNameRefs(Map duplicateMap,
        Set blacklist, Map dupMethodMap,
        Map 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 hoistMap = new HashMap();
    // Hoist all anonymous versions
    Map 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();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy