Skip to content

Commit

Permalink
buggy files form Closure #126
Browse files Browse the repository at this point in the history
  • Loading branch information
tdurieux committed Mar 7, 2017
1 parent 3a7f492 commit ec4c481
Showing 1 changed file with 312 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
/*
* Copyright 2009 The Closure Compiler Authors.
*
* 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.javascript.jscomp;

import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;

/**
* Transform the structure of the AST so that the number of explicit exits
* are minimized.
*
* @author [email protected] (John Lenz)
*/
class MinimizeExitPoints
extends AbstractPostOrderCallback
implements CompilerPass {

AbstractCompiler compiler;

MinimizeExitPoints(AbstractCompiler compiler) {
this.compiler = compiler;
}

@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}

@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.LABEL:
tryMinimizeExits(
n.getLastChild(), Token.BREAK, n.getFirstChild().getString());
break;

case Token.FOR:
case Token.WHILE:
tryMinimizeExits(NodeUtil.getLoopCodeBlock(n), Token.CONTINUE, null);
break;

case Token.DO:
tryMinimizeExits(NodeUtil.getLoopCodeBlock(n), Token.CONTINUE, null);

Node cond = NodeUtil.getConditionExpression(n);
if (NodeUtil.getImpureBooleanValue(cond) == TernaryValue.FALSE) {
// Normally, we wouldn't be able to optimize BREAKs inside a loop
// but as we know the condition will always false, we can treat them
// as we would a CONTINUE.
tryMinimizeExits(n.getFirstChild(), Token.BREAK, null);
}
break;

case Token.FUNCTION:
tryMinimizeExits(n.getLastChild(), Token.RETURN, null);
break;
}
}

/**
* Attempts to minimize the number of explicit exit points in a control
* structure to take advantage of the implied exit at the end of the
* structure. This is accomplished by removing redundant statements, and
* moving statements following a qualifying IF node into that node.
* For example:
*
* function () {
* if (x) return;
* else blah();
* foo();
* }
*
* becomes:
*
* function () {
* if (x) ;
* else {
* blah();
* foo();
* }
*
* @param n The execution node of a parent to inspect.
* @param exitType The type of exit to look for.
* @param labelName If parent is a label the name of the label to look for,
* null otherwise.
* @nullable labelName non-null only for breaks within labels.
*/
void tryMinimizeExits(Node n, int exitType, String labelName) {

// Just an 'exit'.
if (matchingExitNode(n, exitType, labelName)) {
NodeUtil.removeChild(n.getParent(), n);
compiler.reportCodeChange();
return;
}

// Just an 'if'.
if (n.isIf()) {
Node ifBlock = n.getFirstChild().getNext();
tryMinimizeExits(ifBlock, exitType, labelName);
Node elseBlock = ifBlock.getNext();
if (elseBlock != null) {
tryMinimizeExits(elseBlock, exitType, labelName);
}
return;
}

// Just a 'try/catch/finally'.
if (n.isTry()) {
Node tryBlock = n.getFirstChild();
tryMinimizeExits(tryBlock, exitType, labelName);
Node allCatchNodes = NodeUtil.getCatchBlock(n);
if (NodeUtil.hasCatchHandler(allCatchNodes)) {
Preconditions.checkState(allCatchNodes.hasOneChild());
Node catchNode = allCatchNodes.getFirstChild();
Node catchCodeBlock = catchNode.getLastChild();
tryMinimizeExits(catchCodeBlock, exitType, labelName);
}
/* Don't try to minimize the exits of finally blocks, as this
* can cause problems if it changes the completion type of the finally
* block. See ECMA 262 Sections 8.9 & 12.14
*/
if (NodeUtil.hasFinally(n)) {
Node finallyBlock = n.getLastChild();
tryMinimizeExits(finallyBlock, exitType, labelName);
}
}

// Just a 'label'.
if (n.isLabel()) {
Node labelBlock = n.getLastChild();
tryMinimizeExits(labelBlock, exitType, labelName);
}

// TODO(johnlenz): The last case of SWITCH statement?

// The rest assumes a block with at least one child, bail on anything else.
if (!n.isBlock() || n.getLastChild() == null) {
return;
}

// Multiple if-exits can be converted in a single pass.
// Convert "if (blah) break; if (blah2) break; other_stmt;" to
// become "if (blah); else { if (blah2); else { other_stmt; } }"
// which will get converted to "if (!blah && !blah2) { other_stmt; }".
for (Node c : n.children()) {

// An 'if' block to process below.
if (c.isIf()) {
Node ifTree = c;
Node trueBlock, falseBlock;

// First, the true condition block.
trueBlock = ifTree.getFirstChild().getNext();
falseBlock = trueBlock.getNext();
tryMinimizeIfBlockExits(trueBlock, falseBlock,
ifTree, exitType, labelName);

// Now the else block.
// The if blocks may have changed, get them again.
trueBlock = ifTree.getFirstChild().getNext();
falseBlock = trueBlock.getNext();
if (falseBlock != null) {
tryMinimizeIfBlockExits(falseBlock, trueBlock,
ifTree, exitType, labelName);
}
}

if (c == n.getLastChild()) {
break;
}
}

// Now try to minimize the exits of the last child, if it is removed
// look at what has become the last child.
for (Node c = n.getLastChild(); c != null; c = n.getLastChild()) {
tryMinimizeExits(c, exitType, labelName);
// If the node is still the last child, we are done.
if (c == n.getLastChild()) {
break;
}
}
}

/**
* Look for exits (returns, breaks, or continues, depending on the context) at
* the end of a block and removes them by moving the if node's siblings,
* if any, into the opposite condition block.
*
* @param srcBlock The block to inspect.
* @param destBlock The block to move sibling nodes into.
* @param ifNode The if node to work with.
* @param exitType The type of exit to look for.
* @param labelName The name associated with the exit, if any.
* @nullable labelName null for anything excepted for named-break associated
* with a label.
*/
private void tryMinimizeIfBlockExits(Node srcBlock, Node destBlock,
Node ifNode, int exitType, String labelName) {
Node exitNodeParent = null;
Node exitNode = null;

// Pick an exit node candidate.
if (srcBlock.isBlock()) {
if (!srcBlock.hasChildren()) {
return;
}
exitNodeParent = srcBlock;
exitNode = exitNodeParent.getLastChild();
} else {
// Just a single statement, if it isn't an exit bail.
exitNodeParent = ifNode;
exitNode = srcBlock;
}

// Verify the candidate.
if (!matchingExitNode(exitNode, exitType, labelName)) {
return;
}

// Take case of the if nodes siblings, if any.
if (ifNode.getNext() != null) {
// Move siblings of the if block into the opposite
// logic block of the exit.
Node newDestBlock = IR.block().srcref(ifNode);
if (destBlock == null) {
// Only possible if this is the false block.
ifNode.addChildToBack(newDestBlock);
} else if (destBlock.isEmpty()) {
// Use the new block.
ifNode.replaceChild(destBlock, newDestBlock);
} else if (destBlock.isBlock()) {
// Reuse the existing block.
newDestBlock = destBlock;
} else {
// Add the existing statement to the new block.
ifNode.replaceChild(destBlock, newDestBlock);
newDestBlock.addChildToBack(destBlock);
}

// Move all the if node's following siblings.
moveAllFollowing(ifNode, ifNode.getParent(), newDestBlock);
compiler.reportCodeChange();
}
}

/**
* Determines if n matches the type and name for the following types of
* "exits":
* - return without values
* - continues and breaks with or without names.
* @param n The node to inspect.
* @param type The Token type to look for.
* @param labelName The name that must be associated with the exit type.
* @nullable labelName non-null only for breaks associated with labels.
* @return Whether the node matches the specified block-exit type.
*/
private static boolean matchingExitNode(Node n, int type, String labelName) {
if (n.getType() == type) {
if (type == Token.RETURN) {
// only returns without expressions.
return !n.hasChildren();
} else {
if (labelName == null) {
return !n.hasChildren();
} else {
return n.hasChildren()
&& labelName.equals(n.getFirstChild().getString());
}
}
}
return false;
}

/**
* Move all the child nodes following start in srcParent to the end of
* destParent's child list.
* @param start The start point in the srcParent child list.
* @param srcParent The parent node of start.
* @param destParent The destination node.
*/
private static void moveAllFollowing(
Node start, Node srcParent, Node destParent) {
for (Node n = start.getNext(); n != null; n = start.getNext()) {
boolean isFunctionDeclaration = NodeUtil.isFunctionDeclaration(n);
srcParent.removeChild(n);
if (isFunctionDeclaration) {
destParent.addChildToFront(n);
} else {
destParent.addChildToBack(n);
}
}
}
}

0 comments on commit ec4c481

Please sign in to comment.