Code with Finding: |
class CollapseVariableDeclarations.ExploitAssigns {
/**
* Collapse the given assign expression into the expression directly
* following it, if possible.
*
* @param expr The expression that may be moved.
* @param exprParent The parent of {@code expr}.
* @param value The value of this expression, expressed as a node. Each
* expression may have multiple values, so this function may be called
* multiple times for the same expression. For example,
* <code>
* a = true;
* </code>
* is equal to the name "a" and the boolean "true".
* @return Whether the expression was collapsed succesfully.
*/
private boolean collapseAssignEqualTo(Node expr, Node exprParent,
Node value) {
Node assign = expr.getFirstChild();
Node parent = exprParent;
Node next = expr.getNext();
while (next != null) {
switch (next.getType()) {
case Token.AND:
case Token.OR:
case Token.HOOK:
case Token.IF:
case Token.RETURN:
case Token.EXPR_RESULT:
// Dive down the left side
parent = next;
next = next.getFirstChild();
break;
case Token.VAR:
if (next.getFirstChild().hasChildren()) {
parent = next.getFirstChild();
next = parent.getFirstChild();
break;
}
return false;
case Token.GETPROP:
case Token.NAME:
if (next.isQualifiedName()) {
String nextName = next.getQualifiedName();
if (value.isQualifiedName() &&
nextName.equals(value.getQualifiedName())) {
// If the previous expression evaluates to value of a
// qualified name, and that qualified name is used again
// shortly, then we can exploit the assign here.
// Verify the assignment doesn't change its own value.
if (!isSafeReplacement(next, assign)) {
return false;
}
exprParent.removeChild(expr);
expr.removeChild(assign);
parent.replaceChild(next, assign);
return true;
}
}
return false;
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
if (value.getType() == next.getType()) {
if ((next.getType() == Token.STRING ||
next.getType() == Token.NUMBER) &&
!next.isEquivalentTo(value)) {
return false;
}
// If the r-value of the expr assign is an immutable value,
// and the value is used again shortly, then we can exploit
// the assign here.
exprParent.removeChild(expr);
expr.removeChild(assign);
parent.replaceChild(next, assign);
return true;
}
return false;
case Token.ASSIGN:
// Assigns are really tricky. In lots of cases, we want to inline
// into the right side of the assign. But the left side of the
// assign is evaluated first, and it may have convoluted logic:
// a = null;
// (a = b).c = null;
// We don't want to exploit the first assign. Similarly:
// a.b = null;
// a.b.c = null;
// We don't want to exploit the first assign either.
//
// To protect against this, we simply only inline when the left side
// is guaranteed to evaluate to the same L-value no matter what.
Node leftSide = next.getFirstChild();
if (leftSide.getType() == Token.NAME ||
leftSide.getType() == Token.GETPROP &&
leftSide.getFirstChild().getType() == Token.THIS) {
// Dive down the right side of the assign.
parent = next;
next = leftSide.getNext();
break;
} else {
return false;
}
default:
// Return without inlining a thing
return false;
}
}
return false;
}
}
|