/******************************************************************************
* Copyright (c) 2009 - 2015 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*****************************************************************************/
/**
*
*/
package com.ibm.wala.memsat.viz;
import static com.ibm.wala.memsat.util.Graphs.root;
import static com.ibm.wala.memsat.util.Programs.instructions;
import static com.ibm.wala.memsat.util.Programs.relevantMethods;
import static com.ibm.wala.memsat.util.Strings.nodeNames;
import static com.ibm.wala.util.graph.traverse.DFS.iterateDiscoverTime;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.ibm.wala.cast.loader.AstMethod;
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.classLoader.IField;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.ShrikeBTMethod;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.memsat.concurrent.Execution;
import com.ibm.wala.memsat.concurrent.Justification;
import com.ibm.wala.memsat.concurrent.Program;
import com.ibm.wala.memsat.frontEnd.IRType;
import com.ibm.wala.memsat.frontEnd.InlinedInstruction;
import com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action;
import com.ibm.wala.memsat.frontEnd.WalaConcurrentInformation;
import com.ibm.wala.memsat.frontEnd.WalaInformation;
import com.ibm.wala.memsat.representation.ConstantFactory;
import com.ibm.wala.memsat.representation.Interpreter;
import com.ibm.wala.memsat.translation.concurrent.ConcurrentTranslation;
import com.ibm.wala.shrikeCT.InvalidClassFileException;
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
import com.ibm.wala.ssa.SSAArrayReferenceInstruction;
import com.ibm.wala.ssa.SSAFieldAccessInstruction;
import com.ibm.wala.types.TypeReference;
import kodkod.ast.Expression;
import kodkod.engine.Solution;
import kodkod.instance.Tuple;
import kodkod.instance.TupleSet;
/**
* Visualizes the results of a concurrent translation.
*
* @author etorlak
*/
final class ConcurrentStringVisualizer extends StringVisualizer<ConcurrentTranslation> {
private final Justification just;
private final Program prog;
private final WalaInformation info;
/**
* Constructs a new string visualizer from the given translation and solution.
* @requires solution.instance!=null
* @requires solution.formula = translation.formula && solution.bounds = translation.bounds
*/
ConcurrentStringVisualizer(ConcurrentTranslation translation, Solution solution) {
super(translation, solution);
this.just = translation.context();
this.prog = just.program();
this.info = prog.info();
}
/**
* {@inheritDoc}
* @see com.ibm.wala.memsat.viz.StringVisualizer#visualize()
*/
@Override
public String visualize() {
final StringBuilder s = new StringBuilder();
final Map<CGNode,String> methodNames = nodeNames(relevantMethods(instructions(info)));
warnings(s, translation.warnings());
s.append("solution interpretation:\n");
s.append(" main execution");
execution(s, just.execution(), methodNames);
if (!just.speculations().isEmpty()) {
final List<? extends Execution> speculations = just.speculations();
final List<? extends Expression> commits = just.commits();
for(int i = 0, max = speculations.size(); i < max; i++) {
s.append("\n\n speculative execution " + i + ":");
execution(s, speculations.get(i), methodNames);
s.append("\n committed " + i + ": ");
commit(s, commits.get(i));
}
}
s.append("\n");
return s.toString();
}
/**
* Appends to the specified buffer a string representation of the given commit set.
*/
private void commit(StringBuilder s, Expression commit) {
s.append("{");
final TupleSet commitValue = eval.evaluate(commit);
assert commitValue.arity()==1;
if (!commitValue.isEmpty()) {
final Iterator<Tuple> itr = commitValue.iterator();
s.append(itr.next().atom(0));
while(itr.hasNext()) {
s.append(", " + itr.next().atom(0));
}
}
s.append("}");
}
/**
* Appends to the specified buffer a string representation of the given execution.
*/
private void execution(StringBuilder s, Execution exec, Map<CGNode,String> methodNames) {
final Map<InlinedInstruction,String> execed = executedInstructions(exec, methodNames);
for(Iterator<CGNode> threadItr = iterateDiscoverTime(info.threads(), root(info.threads())); threadItr.hasNext(); ) {
final CGNode n = threadItr.next();
final WalaConcurrentInformation cinfo = info.concurrentInformation(n);
s.append("\n actions of thread " + methodNames.get(n) + ":");
for(Iterator<InlinedInstruction> instItr = iterateDiscoverTime(cinfo.threadOrder(), cinfo.start()); instItr.hasNext(); ) {
final InlinedInstruction inst = instItr.next();
if (execed.containsKey(inst)) {
s.append("\n " + atom(eval.evaluate(exec.action(inst))) + " = " + execed.get(inst));
}
}
}
writeSeen(s, exec);
orderings(s, exec);
}
/**
* Appends a string representation of the write-seen relation for the given execution to the given string builder.
*/
private void writeSeen(StringBuilder s, Execution exec) {
s.append("\n write seen: {");
final TupleSet w = eval.evaluate(exec.w());
assert w.arity()==2;
if (!w.isEmpty()) {
final Iterator<Tuple> itr = w.iterator();
s.append(pair(itr.next()));
while(itr.hasNext()) {
s.append(", " + pair(itr.next()));
}
}
s.append("}");
}
/**
* Appends a string representation of the orderings returned by exec.viz() to the given string builder.
*/
private void orderings(StringBuilder s, Execution exec) {
for(Map.Entry<Expression, String> entry : exec.viz().entrySet()) {
final TupleSet vizOrd = eval.evaluate(entry.getKey());
assert vizOrd.arity() == 2;
s.append("\n " + entry.getValue() + ": {");
if (vizOrd.isEmpty()) {
s.append(" }");
} else {
final Iterator<Tuple> itr = vizOrd.iterator();
s.append("\n " + pair(itr.next()));
for(int i = 1; itr.hasNext(); i=(i+1)%5) {
if (i==0) { s.append(",\n "); }
else { s.append(", "); }
s.append(pair(itr.next()));
}
s.append("\n }");
}
}
}
/**
* Returns a map from each instruction in this.just.program.info that is executed by
* the given Execution to a readable string.
* @return a map from each instruction in this.just.program.info that is executed by
* the given Execution to a readable string.
*/
private final Map<InlinedInstruction, String> executedInstructions(Execution exec, Map<CGNode,String> methodNames) {
final Map<InlinedInstruction, String> ret = new LinkedHashMap<InlinedInstruction, String>();
for(CGNode n : info.threads()) {
final WalaConcurrentInformation cinfo = info.concurrentInformation(n);
for(InlinedInstruction inst : cinfo.actions()) {
if (eval.evaluate(exec.action(inst).some())) {
final String methodString = methodNames.get(inst.cgNode());
final String instString = instruction(exec, inst);
final String lineString = line(inst.cgNode().getMethod(), inst.instructionIndex());
ret.put(inst, methodString + "[" + lineString + "]::" + instString);
}
}
}
return ret;
}
/**
* Returns a String representation of the given executed instruction.
* @requires this.eval.evaluate(this.prog.executes(exec,inst))
* @return a String representation of the given executed instruction.
*/
private final String instruction(Execution exec, InlinedInstruction inst) {
final Action act = inst.action();
switch(act) {
case START : case END :
return act.name().toLowerCase();
case SPECIAL :
return ((SSAAbstractInvokeInstruction)inst.instruction()).getDeclaredTarget().getName().toString();
case LOCK : case UNLOCK :
return act.name().toLowerCase() + "(" + eval.evaluate(exec.action(inst).join(exec.monitor())) + ")";
case NORMAL_READ : case VOLATILE_READ :
final String readLoc = locationOf(exec, inst);
final Interpreter<Object> reader = valueInterpreter(inst);
final Object readVal = reader.evaluate(reader.fromObj(exec.action(inst).join(exec.w()).join(exec.v())), eval);
return "read(" + readLoc + ", " + readVal + ")";
case NORMAL_WRITE : case VOLATILE_WRITE :
final String writeLoc = locationOf(exec, inst);
final Interpreter<Object> writer = valueInterpreter(inst);
final Object writeVal = writer.evaluate(writer.fromObj(exec.action(inst).join(exec.v())), eval);
return "write(" + writeLoc + ", " + writeVal + ")";
default :
throw new AssertionError("unreachable");
}
}
/**
* Returns an interpreter for the value read or written by the given inlined instruction.
* @requires inst.action in NORMAL_READ + VOLATILE_READ + NORMAL_WRITE + VOLATILE_WRITE
* @return an interpreter for the value read or written by the given inlined instruction.
*/
private final Interpreter<Object> valueInterpreter(InlinedInstruction inst) {
final ConstantFactory consts = translation.factory().constants();
final TypeReference type;
if (inst.instruction() instanceof SSAFieldAccessInstruction) {
type = ((SSAFieldAccessInstruction)inst.instruction()).getDeclaredFieldType();
} else {
type = ((SSAArrayReferenceInstruction)inst.instruction()).getElementType();
}
return consts.interpreter(IRType.convert(type));
}
/**
* Returns a String representation of the location read or written by the given inlined instruction.
* @requires inst.action in NORMAL_READ + VOLATILE_READ + NORMAL_WRITE + VOLATILE_WRITE
* @requires this.eval.evaluate(this.prog.executes(exec,inst))
* @return a String representation of the location read or written by the given inlined instruction.
*/
private final String locationOf(Execution exec, InlinedInstruction inst) {
if (inst.instruction() instanceof SSAFieldAccessInstruction) {
final SSAFieldAccessInstruction obj = (SSAFieldAccessInstruction)inst.instruction();
final IField field = info.callGraph().getClassHierarchy().resolveField(obj.getDeclaredField());
final Expression locExpr = exec.action(inst).join(exec.location());
if (field.isStatic()) {
return atom(eval.evaluate(locExpr)).toString();
} else {
final Expression type = translation.factory().constants().valueOf(field.getDeclaringClass().getReference());
final TupleSet fieldAtom = eval.evaluate(locExpr.difference(type));
final TupleSet objectAtom = eval.evaluate(locExpr.intersection(type));
return atom(objectAtom) + "." + atom(fieldAtom);
}
} else {
assert inst.instruction() instanceof SSAArrayReferenceInstruction;
final Expression location = exec.action(inst).join(exec.location());
final int idx = eval.evaluate(location.sum());
final TupleSet instance = eval.evaluate(location.difference(Expression.INTS));
return atom(instance) + "[" + idx + "]";
}
}
/**
* Given a singleton unary relation, returns the underlying atom.
* @requires s.size()==1 && s.arity()==1;
* @return s.iterator().next().atom(0)
*/
private static final Object atom(TupleSet s) {
assert s.arity()==1 : "Expected set of arity 1 but got " + s;
assert s.size()==1 : "Expected set of size 1 but got " + s;
return s.iterator().next().atom(0);
}
/**
* Returns a String representation of the first two atoms of the given tuple, separated by ->.
* @requires t.arity >= 2
* @return t.atom(0) + "->" + t.atom(1)
**/
private static String pair(Tuple t) { return t.atom(0) + "->" + t.atom(1); }
/**
* Returns a String representation of the position in the source of the given method corresponding
* to the instruction at the specified index, or the empty string if the line is unknown.
* @return a String representation of the position in the source of the given method corresponding
* to the instruction at the specified index, or the empty string if the line is unknown.
*/
private static final String line(IMethod method, int instructionIndex) {
if (instructionIndex!=Integer.MAX_VALUE && instructionIndex!=Integer.MIN_VALUE) {
if (method instanceof ShrikeBTMethod) {
try {
return String.valueOf(method.getLineNumber(((ShrikeBTMethod)method).getBytecodeIndex(instructionIndex)));
} catch (InvalidClassFileException e) { } // ignore
} else if (method instanceof AstMethod) {
final Position pos = ((AstMethod)method).getSourcePosition(instructionIndex);
if (pos!=null)
return String.valueOf(pos.getFirstLine());
}
}
return "";
}
}