/*
* Copyright 2011 Jon S Akhtar (Sylvanaar)
*
* 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.sylvanaar.idea.Lua.lang.psi.controlFlow.impl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.sylvanaar.idea.Lua.lang.parser.LuaElementTypes;
import com.sylvanaar.idea.Lua.lang.psi.LuaFunctionDefinition;
import com.sylvanaar.idea.Lua.lang.psi.LuaPsiElement;
import com.sylvanaar.idea.Lua.lang.psi.LuaPsiFile;
import com.sylvanaar.idea.Lua.lang.psi.LuaReferenceElement;
import com.sylvanaar.idea.Lua.lang.psi.controlFlow.AfterCallInstruction;
import com.sylvanaar.idea.Lua.lang.psi.controlFlow.CallEnvironment;
import com.sylvanaar.idea.Lua.lang.psi.controlFlow.CallInstruction;
import com.sylvanaar.idea.Lua.lang.psi.controlFlow.Instruction;
import com.sylvanaar.idea.Lua.lang.psi.expressions.*;
import com.sylvanaar.idea.Lua.lang.psi.impl.symbols.LuaCompoundReferenceElementImpl;
import com.sylvanaar.idea.Lua.lang.psi.statements.*;
import com.sylvanaar.idea.Lua.lang.psi.symbols.LuaParameter;
import com.sylvanaar.idea.Lua.lang.psi.symbols.LuaSymbol;
import com.sylvanaar.idea.Lua.lang.psi.types.LuaType;
import com.sylvanaar.idea.Lua.lang.psi.util.LuaPsiUtils;
import com.sylvanaar.idea.Lua.lang.psi.visitor.LuaRecursiveElementVisitor;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
/**
* @author ven
*/
public class ControlFlowBuilder extends LuaRecursiveElementVisitor {
private static final Logger log = Logger.getInstance("Lua.ControlFlowBuilder");
private List<InstructionImpl> myInstructions;
private Stack<InstructionImpl> myProcessingStack;
//private final PsiConstantEvaluationHelper myConstantEvaluator;
public ControlFlowBuilder(Project project) {
// myConstantEvaluator = JavaPsiFacade.getInstance(project).getConstantEvaluationHelper();
}
private InstructionImpl myHead;
private boolean myNegate;
private boolean myAssertionsOnly;
private LuaPsiElement myLastInScope;
private List<Pair<InstructionImpl, LuaPsiElement>> myPending;
private int myInstructionNumber;
public void visitBlock(LuaBlock block) {
final PsiElement parent = block.getParent();
if (parent instanceof LuaFunctionDefinition) {
final LuaParameter[] parameters = ((LuaFunctionDefinition) parent).getParameters().getLuaParameters();
for (LuaParameter parameter : parameters) {
addNode(new ReadWriteVariableInstructionImpl(parameter, myInstructionNumber++));
}
}
super.visitBlock(block);
handlePossibleReturn(block);
}
private void handlePossibleReturn(LuaBlock block) {
final LuaStatementElement[] statements = block.getStatements();
for(int i=statements.length; i<0; i--)
handlePossibleReturn(statements[i]);
}
private void handlePossibleReturn(LuaStatementElement last) {
if (PsiTreeUtil.isAncestor(myLastInScope, last, false)) {
final MaybeReturnInstruction instruction = new MaybeReturnInstruction((LuaExpression) last, myInstructionNumber++);
checkPending(instruction);
addNode(instruction);
}
}
final Object lock = new Object();
public Instruction[] buildControlFlow(LuaPsiElement scope) {
myInstructions = new ArrayList<InstructionImpl>();
myProcessingStack = new Stack<InstructionImpl>();
myPending = new ArrayList<Pair<InstructionImpl, LuaPsiElement>>();
myInstructionNumber = 0;
myLastInScope = null;
if (scope instanceof LuaPsiFile) {
LuaStatementElement[] statements = ((LuaPsiFile) scope).getStatements();
if (statements.length > 0) {
myLastInScope = statements[statements.length - 1];
}
} else if (scope instanceof LuaBlock) {
LuaStatementElement[] statements = ((LuaBlock) scope).getStatements();
if (statements.length > 0) {
myLastInScope = statements[statements.length - 1];
}
}
log.info("Scope: " + scope + " parent: " + scope.getParent());
startNode(null);
// if (scope instanceof LuaClosableBlock) {
// buildFlowForClosure((LuaClosableBlock)scope);
// }
scope.accept(this);
final InstructionImpl end = startNode(null);
checkPending(end); //collect return edges
synchronized (lock) {
for(Instruction i : myInstructions)
log.info(i.toString());
}
return myInstructions.toArray(new Instruction[myInstructions.size()]);
}
// private void buildFlowForClosure(final LuaClosableBlock closure) {
// for (LuaParameter parameter : closure.getParameters()) {
// addNode(new ReadWriteVariableInstructionImpl(parameter, myInstructionNumber++));
// }
//
// final Set<String> names = new LinkedHashSet<String>();
//
// closure.accept(new LuaRecursiveElementVisitor() {
// public void visitReferenceExpression(LuaReferenceExpression refExpr) {
// super.visitReferenceExpression(refExpr);
// if (refExpr.getQualifierExpression() == null && !PsiUtil.isLValue(refExpr)) {
// if (!(refExpr.getParent() instanceof LuaCall)) {
// final String refName = refExpr.getReferenceName();
// if (!hasDeclaredVariable(refName, closure, refExpr)) {
// names.add(refName);
// }
// }
// }
// }
// });
//
// names.add("owner");
//
// for (String name : names) {
// addNode(new ReadWriteVariableInstructionImpl(name, closure.getLBrace(), myInstructionNumber++, true));
// }
//
// PsiElement child = closure.getFirstChild();
// while (child != null) {
// if (child instanceof LuaPsiElement) {
// ((LuaPsiElement)child).accept(this);
// }
// child = child.getNextSibling();
// }
//
// final LuaStatement[] statements = closure.getStatements();
// if (statements.length > 0) {
// handlePossibleReturn(statements[statements.length - 1]);
// }
// }
private void addNode(InstructionImpl instruction) {
myInstructions.add(instruction);
if (myHead != null) {
addEdge(myHead, instruction);
}
myHead = instruction;
}
static void addEdge(InstructionImpl beg, InstructionImpl end) {
if (!beg.mySucc.contains(end)) {
beg.mySucc.add(end);
}
if (!end.myPred.contains(beg)) {
end.myPred.add(beg);
}
}
public void visitFunctionDef(LuaFunctionDefinitionStatement e) {
//do not go into functions
e.getIdentifier().accept(this);
addNode(new ReadWriteVariableInstructionImpl(e.getIdentifier(), myInstructionNumber++));
}
@Override
public void visitDeclarationStatement(LuaDeclarationStatement e) {
super.visitDeclarationStatement(e);
for (LuaSymbol s : e.getDefinedSymbols())
addNode(new ReadWriteVariableInstructionImpl(s, myInstructionNumber++));
}
// @Override
// public void visitDeclarationStatement(LuaDeclarationStatement e) {
// e.getDefinedSymbols().accept(this);
// }
//
// @Override
// public void visitDeclarationExpression(LuaDeclarationExpression e) {
// addNode(new ReadWriteVariableInstructionImpl(e, myInstructionNumber++));
// }
@Override
public void visitFile(PsiFile file) {
visitBlock((LuaBlock) file);
}
@Override
public void visitDoStatement(LuaDoStatement e) {
final InstructionImpl instruction = startNode(e);
final LuaBlock body = e.getBlock();
if (body != null) {
body.accept(this);
}
finishNode(instruction);
}
//
// public void visitBreakStatement(LuaBreakStatement breakStatement) {
// super.visitBreakStatement(breakStatement);
// final LuaStatementElement target = breakStatement.findTargetStatement();
// if (target != null && myHead != null) {
// addPendingEdge(target, myHead);
// }
//
// flowAbrupted();
// }
//
public void visitReturnStatement(LuaReturnStatement returnStatement) {
boolean isNodeNeeded = myHead == null || myHead.getElement() != returnStatement;
final LuaExpression value = returnStatement.getReturnValue();
if (value != null) value.accept(this);
if (isNodeNeeded) {
InstructionImpl retInsn = startNode(returnStatement);
addPendingEdge(null, myHead);
finishNode(retInsn);
}
else {
addPendingEdge(null, myHead);
}
flowAbrupted();
}
//
// public void visitAssertStatement(LuaAssertStatement assertStatement) {
// final LuaExpression assertion = assertStatement.getAssertion();
// if (assertion != null) {
// assertion.accept(this);
// final InstructionImpl assertInstruction = startNode(assertStatement);
// final PsiType type = TypesUtil.createTypeByFQClassName("java.lang.AssertionError", assertStatement);
// ExceptionInfo info = findCatch(type);
// if (info != null) {
// info.myThrowers.add(assertInstruction);
// }
// else {
// addPendingEdge(null, assertInstruction);
// }
// finishNode(assertInstruction);
// }
// }
//
//
private void flowAbrupted() {
myHead = null;
}
public void visitAssignment(LuaAssignmentStatement expression) {
LuaIdentifierList lValues = expression.getLeftExprs();
LuaExpressionList rValues = expression.getRightExprs();
if (rValues != null) {
rValues.accept(this);
lValues.accept(this);
}
}
// @Override
// public void visitParenthesizedExpression(LuaParenthesizedExpression expression) {
// final LuaExpression operand = expression.getOperand();
// if (operand != null) operand.accept(this);
// }
//
@Override
public void visitUnaryExpression(LuaUnaryExpression expression) {
final LuaExpression operand = expression.getOperand();
if (operand != null) {
final boolean negation = expression.getOperationTokenType() == LuaElementTypes.NOT;
if (negation) {
myNegate = !myNegate;
}
operand.accept(this);
if (negation) {
myNegate = !myNegate;
}
}
}
@Override
public void visitCompoundReference(LuaCompoundReferenceElementImpl e) {
visitReferenceElement(e);
}
public void visitReferenceElement(LuaReferenceElement referenceExpression) {
super.visitReferenceElement(referenceExpression);
final ReadWriteVariableInstructionImpl i =
new ReadWriteVariableInstructionImpl(referenceExpression, myInstructionNumber++, !myAssertionsOnly && LuaPsiUtils.isLValue(referenceExpression));
addNode(i);
checkPending(i);
}
public void visitIfThenStatement(LuaIfThenStatement ifStatement) {
InstructionImpl ifInstruction = startNode(ifStatement);
final LuaExpression condition = ifStatement.getIfCondition();
final InstructionImpl head = myHead;
final LuaBlock thenBranch = ifStatement.getIfBlock();
InstructionImpl thenEnd = null;
if (thenBranch != null) {
if (condition != null) {
condition.accept(this);
}
thenBranch.accept(this);
handlePossibleReturn(thenBranch);
thenEnd = myHead;
}
myHead = head;
final LuaBlock elseBranch = ifStatement.getElseBlock();
InstructionImpl elseEnd = null;
if (elseBranch != null) {
if (condition != null) {
myNegate = !myNegate;
final boolean old = myAssertionsOnly;
myAssertionsOnly = true;
condition.accept(this);
myNegate = !myNegate;
myAssertionsOnly = old;
}
elseBranch.accept(this);
handlePossibleReturn(elseBranch);
elseEnd = myHead;
}
if (thenBranch != null || elseBranch != null) {
final InstructionImpl end = new IfEndInstruction(ifStatement, myInstructionNumber++);
addNode(end);
if (thenEnd != null) addEdge(thenEnd, end);
if (elseEnd != null) addEdge(elseEnd, end);
}
finishNode(ifInstruction);
/*InstructionImpl ifInstruction = startNode(ifStatement);
final LuaCondition condition = ifStatement.getCondition();
final InstructionImpl head = myHead;
final LuaStatement thenBranch = ifStatement.getThenBranch();
if (thenBranch != null) {
if (condition != null) {
condition.accept(this);
}
thenBranch.accept(this);
handlePossibleReturn(thenBranch);
addPendingEdge(ifStatement, myHead);
}
myHead = head;
if (condition != null) {
myNegate = !myNegate;
final boolean old = myAssertionsOnly;
myAssertionsOnly = true;
condition.accept(this);
myNegate = !myNegate;
myAssertionsOnly = old;
}
final LuaStatement elseBranch = ifStatement.getElseBranch();
if (elseBranch != null) {
elseBranch.accept(this);
handlePossibleReturn(elseBranch);
addPendingEdge(ifStatement, myHead);
}
finishNode(ifInstruction);*/
}
//
// public void visitForStatement(LuaForStatement forStatement) {
// final LuaForClause clause = forStatement.getClause();
// if (clause instanceof LuaTraditionalForClause) {
// for (LuaCondition initializer : ((LuaTraditionalForClause)clause).getInitialization()) {
// initializer.accept(this);
// }
// }
// else if (clause instanceof LuaForInClause) {
// final LuaExpression expression = ((LuaForInClause)clause).getIteratedExpression();
// if (expression != null) {
// expression.accept(this);
// }
// for (LuaVariable variable : clause.getDeclaredVariables()) {
// ReadWriteVariableInstructionImpl writeInsn = new ReadWriteVariableInstructionImpl(variable, myInstructionNumber++);
// checkPending(writeInsn);
// addNode(writeInsn);
// }
// }
//
// InstructionImpl instruction = startNode(forStatement);
// if (clause instanceof LuaTraditionalForClause) {
// final LuaExpression condition = ((LuaTraditionalForClause)clause).getCondition();
// if (condition != null) {
// condition.accept(this);
// if (!alwaysTrue(condition)) {
// addPendingEdge(forStatement, myHead); //break cycle
// }
// }
// } else {
// addPendingEdge(forStatement, myHead); //break cycle
// }
//
// final LuaStatement body = forStatement.getBody();
// if (body != null) {
// InstructionImpl bodyInstruction = startNode(body);
// body.accept(this);
// finishNode(bodyInstruction);
// }
// checkPending(instruction); //check for breaks targeted here
//
// if (clause instanceof LuaTraditionalForClause) {
// for (LuaExpression expression : ((LuaTraditionalForClause)clause).getUpdate()) {
// expression.accept(this);
// }
// }
// if (myHead != null) addEdge(myHead, instruction); //loop
// flowAbrupted();
//
// finishNode(instruction);
// }
//
private void checkPending(InstructionImpl instruction) {
final PsiElement element = instruction.getElement();
if (element == null) {
//add all
for (Pair<InstructionImpl, LuaPsiElement> pair : myPending) {
addEdge(pair.getFirst(), instruction);
}
myPending.clear();
}
else {
for (int i = myPending.size() - 1; i >= 0; i--) {
final Pair<InstructionImpl, LuaPsiElement> pair = myPending.get(i);
final PsiElement scopeWhenToAdd = pair.getSecond();
if (scopeWhenToAdd == null) continue;
if (!PsiTreeUtil.isAncestor(scopeWhenToAdd, element, false)) {
addEdge(pair.getFirst(), instruction);
myPending.remove(i);
}
else {
break;
}
}
}
}
//add edge when instruction.getElement() is not contained in scopeWhenAdded
private void addPendingEdge(LuaPsiElement scopeWhenAdded, InstructionImpl instruction) {
if (instruction == null) return;
int i = 0;
if (scopeWhenAdded != null) {
for (; i < myPending.size(); i++) {
Pair<InstructionImpl, LuaPsiElement> pair = myPending.get(i);
final LuaPsiElement currScope = pair.getSecond();
if (currScope == null) continue;
if (!PsiTreeUtil.isAncestor(currScope, scopeWhenAdded, true)) break;
}
}
myPending.add(i, new Pair<InstructionImpl, LuaPsiElement>(instruction, scopeWhenAdded));
}
public void visitWhileStatement(LuaWhileStatement whileStatement) {
final InstructionImpl instruction = startNode(whileStatement);
final LuaConditionalExpression condition = whileStatement.getCondition();
if (condition != null) {
condition.accept(this);
}
if (!alwaysTrue(condition)) {
addPendingEdge(whileStatement, myHead); //break
}
final LuaBlock body = whileStatement.getBody();
if (body != null) {
body.accept(this);
}
checkPending(instruction); //check for breaks targeted here
if (myHead != null) addEdge(myHead, instruction); //loop
flowAbrupted();
finishNode(instruction);
}
private boolean alwaysTrue(LuaExpression condition) {
LuaType type = condition.getLuaType();
if (type != LuaType.NIL && type != LuaType.BOOLEAN && type != LuaType.ANY)
return true;
return false;
}
private InstructionImpl startNode(@Nullable LuaPsiElement element) {
return startNode(element, true);
}
private InstructionImpl startNode(LuaPsiElement element, boolean checkPending) {
final InstructionImpl instruction = new InstructionImpl(element, myInstructionNumber++);
addNode(instruction);
if (checkPending) checkPending(instruction);
return myProcessingStack.push(instruction);
}
private void finishNode(InstructionImpl instruction) {
assert instruction.equals(myProcessingStack.pop());
/* myHead = myProcessingStack.peek();*/
}
//
// public void visitField(LuaField field) {
// }
//
// public void visitParameter(LuaParameter parameter) {
// }
//
// public void visitMethod(LuaMethod method) {
// }
//
// public void visitTypeDefinition(LuaTypeDefinition typeDefinition) {
// if (typeDefinition instanceof LuaAnonymousClassDefinition) {
// super.visitTypeDefinition(typeDefinition);
// }
// }
//
// public void visitVariable(LuaVariable variable) {
// super.visitVariable(variable);
// if (variable.getInitializerLua() != null ||
// variable.getParent() instanceof LuaTupleDeclaration && ((LuaTupleDeclaration)variable.getParent()).getInitializerLua() != null) {
// ReadWriteVariableInstructionImpl writeInsn = new ReadWriteVariableInstructionImpl(variable, myInstructionNumber++);
// checkPending(writeInsn);
// addNode(writeInsn);
// }
// }
//
@Nullable
private InstructionImpl findInstruction(PsiElement element) {
for (int i = myProcessingStack.size() - 1; i >= 0; i--) {
InstructionImpl instruction = myProcessingStack.get(i);
if (element.equals(instruction.getElement())) return instruction;
}
return null;
}
static class CallInstructionImpl extends InstructionImpl implements CallInstruction {
private final InstructionImpl myCallee;
public String toString() {
return super.toString() + " CALL " + myCallee.num();
}
public Iterable<? extends Instruction> succ(CallEnvironment env) {
getStack(env, myCallee).push(this);
return Collections.singletonList(myCallee);
}
public Iterable<? extends Instruction> allSucc() {
return Collections.singletonList(myCallee);
}
protected String getElementPresentation() {
return "";
}
CallInstructionImpl(int num, InstructionImpl callee) {
super(null, num);
myCallee = callee;
}
}
static class PostCallInstructionImpl extends InstructionImpl implements AfterCallInstruction {
private final CallInstructionImpl myCall;
private RetInstruction myReturnInsn;
public String toString() {
return super.toString() + "AFTER CALL " + myCall.num();
}
public Iterable<? extends Instruction> allPred() {
return Collections.singletonList(myReturnInsn);
}
public Iterable<? extends Instruction> pred(CallEnvironment env) {
getStack(env, myReturnInsn).push(myCall);
return Collections.singletonList(myReturnInsn);
}
protected String getElementPresentation() {
return "";
}
PostCallInstructionImpl(int num, CallInstructionImpl call) {
super(null, num);
myCall = call;
}
public void setReturnInstruction(RetInstruction retInstruction) {
myReturnInsn = retInstruction;
}
}
static class RetInstruction extends InstructionImpl {
RetInstruction(int num) {
super(null, num);
}
public String toString() {
return super.toString() + " RETURN";
}
protected String getElementPresentation() {
return "";
}
public Iterable<? extends Instruction> succ(CallEnvironment env) {
final Stack<CallInstruction> callStack = getStack(env, this);
if (callStack.isEmpty()) return Collections.emptyList(); //can be true in case env was not populated (e.g. by DFA)
final CallInstruction callInstruction = callStack.peek();
final List<InstructionImpl> succ = ((CallInstructionImpl)callInstruction).mySucc;
final Stack<CallInstruction> copy = (Stack<CallInstruction>)callStack.clone();
copy.pop();
for (InstructionImpl instruction : succ) {
env.update(copy, instruction);
}
return succ;
}
}
private static boolean hasDeclaredVariable(String name, LuaBlock scope, PsiElement place) {
PsiElement prev = null;
while (place != null) {
if (place instanceof LuaBlock) {
LuaStatementElement[] statements = ((LuaBlock)place).getStatements();
for (LuaStatementElement statement : statements) {
if (statement == prev) break;
if (statement instanceof LuaDeclarationStatement) {
LuaSymbol[] variables = ((LuaDeclarationStatement)statement).getDefinedSymbols();
for (LuaSymbol variable : variables) {
if (name.equals(variable.getName())) return true;
}
}
}
}
if (place == scope) {
break;
}
else {
prev = place;
place = place.getParent();
}
}
return false;
}
}