package dk.brics.jspointers.display;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import dk.brics.jscontrolflow.Block;
import dk.brics.jscontrolflow.Function;
import dk.brics.jscontrolflow.Statement;
import dk.brics.jscontrolflow.analysis.privatevars.PrivateVariables;
import dk.brics.jscontrolflow.analysis.reachdef.ReachingDefinitions;
import dk.brics.jscontrolflow.analysis.reachdef.StatementVariableDefinition;
import dk.brics.jscontrolflow.analysis.reachdef.VariableDefinition;
import dk.brics.jscontrolflow.statements.AbstractStatementVisitor;
import dk.brics.jscontrolflow.statements.CreateFunction;
import dk.brics.jscontrolflow.statements.DeclareVariable;
import dk.brics.jscontrolflow.statements.WriteDynamicProperty;
import dk.brics.jspointers.dataflow.DataflowGraph;
import dk.brics.jspointers.dataflow.FlowNode;
import dk.brics.jspointers.dataflow.IInvocation;
import dk.brics.jspointers.lattice.contexts.NullContext;
import dk.brics.jspointers.lattice.keys.FunctionInstanceKey;
import dk.brics.jspointers.lattice.keys.Key;
import dk.brics.jspointers.lattice.keys.NamedPropertyKey;
import dk.brics.jspointers.lattice.keys.VariableKey;
import dk.brics.jspointers.lattice.values.ApplyFunctionValue;
import dk.brics.jspointers.lattice.values.BooleanValue;
import dk.brics.jspointers.lattice.values.CallFunctionValue;
import dk.brics.jspointers.lattice.values.FunctionPrototypeValue;
import dk.brics.jspointers.lattice.values.FunctionValue;
import dk.brics.jspointers.lattice.values.GlobalObjectValue;
import dk.brics.jspointers.lattice.values.NativeFunctionValue;
import dk.brics.jspointers.lattice.values.NumberValue;
import dk.brics.jspointers.lattice.values.ObjectValue;
import dk.brics.jspointers.lattice.values.StringValue;
import dk.brics.jspointers.lattice.values.UserFunctionValue;
import dk.brics.jspointers.lattice.values.Value;
import dk.brics.jspointers.solver.AnalysisResult;
import dk.brics.jsutil.MultiMap;
public class PrintTypeInfo {
public static void printTypeInfo(final DataflowGraph dataflow, AnalysisResult<Key,Set<Value>> result) {
final MultiMap<Key,Value> map = DisplayUtil.makeContextInsensitive(result);
final Set<Function> calledAsFunction = new HashSet<Function>();
final Set<Function> calledAsConstructor = new HashSet<Function>();
DisplayUtil util = new DisplayUtil(map, dataflow);
for (FlowNode node : dataflow.getNodes()) {
if (node instanceof IInvocation) {
IInvocation invoke = (IInvocation) node;
for (Value funcval : util.getFunctionArgs(invoke)) {
if (funcval instanceof UserFunctionValue) {
UserFunctionValue func = (UserFunctionValue) funcval;
if (invoke.isConstructor()) {
calledAsConstructor.add(func.getFunction());
} else {
calledAsFunction.add(func.getFunction());
}
}
}
}
}
for (Value value : map.getView(CallFunctionValue.Instance.getThisArg(NullContext.Instance))) {
if (value instanceof UserFunctionValue) {
UserFunctionValue func = (UserFunctionValue) value;
calledAsFunction.add(func.getFunction());
}
}
for (Value value : map.getView(ApplyFunctionValue.Instance.getThisArg(NullContext.Instance))) {
if (value instanceof UserFunctionValue) {
UserFunctionValue func = (UserFunctionValue) value;
calledAsFunction.add(func.getFunction());
}
}
for (Function toplevel : dataflow.getTopLevels()) {
PrivateVariables privateVariables = new PrivateVariables(toplevel);
for (final Function func : toplevel.getTransitiveInnerFunctions(true)) {
if (map.getView(new FunctionInstanceKey(func, NullContext.Instance)).isEmpty())
continue; // function is not used - everything is undefined
if (dataflow.getHarnessFunctions().contains(func))
continue;
final boolean isGlobalScope = func.getOuterFunction() == null;
//Set<String> privateVars = privateVariables.getPrivateVariables(func);
//final Liveness liveness = new Liveness(fgd, func, privateVars);
//final ReachingDefs defs = new ReachingDefs(fgd, func, liveness, privateVars);
final ReachingDefinitions defs = new ReachingDefinitions(func, privateVariables, func != toplevel);
for (Block block : func.getBlocks()) {
for (final Statement node : block.getStatements()) {
node.apply(new AbstractStatementVisitor() {
@Override
public void caseDeclareVariable(DeclareVariable n) {
Key key;
if (isGlobalScope) {
key = new NamedPropertyKey(GlobalObjectValue.Instance, n.getVarName());
} else {
key = new VariableKey(n.getVarName(), func, NullContext.Instance);
}
printInfo(n.getVarName(), map.getView(key));
}
// @Override
// public void visit(DeclareFunctionNode n, Void a) {
// if (n.getFunction().getName() != null) {
// printInfo(n.getFunction().getName(), n.getFunction());
// }
// }
@Override
public void caseWriteDynamicProperty(WriteDynamicProperty n) {
boolean isPrototypeAssign = false;
Set<VariableDefinition> sources = defs.getReachingDefinitions(n, n.getBaseVar());
for (VariableDefinition asn : sources) {
if (asn instanceof StatementVariableDefinition) {
StatementVariableDefinition tasn = (StatementVariableDefinition)asn;
if (tasn.getStatement() instanceof ReadProperty) {
ReadProperty read = (ReadProperty) tasn.getStatement();
if (read.getProperty().equals("prototype")) {
isPrototypeAssign = true;
break;
}
}
}
}
if (!isPrototypeAssign)
return;
for (VariableDefinition asn : defs.getReachingDefinitions(n, n.getValueVar())) {
if (!(asn instanceof StatementVariableDefinition))
continue;
StatementVariableDefinition tasn = (StatementVariableDefinition)asn;
Function func;
if (tasn.getStatement() instanceof CreateFunction) {
CreateFunction decl = (CreateFunction) tasn.getStatement();
func = decl.getFunction();
} else {
func = null;
}
if (func != null) {
printInfo(n.getProperty(), func);
}
}
}
private void printInfo(String name, Function function) {
if (dataflow.getHarnessFunctions().contains(function))
return; // skip harness functions
String filename = function.getLocation().getFile().getName();
System.out.printf("%s:%d %s: %s\n", filename, function.getLocation().getLineNumber(), name, computeFunctionSignature(function));
}
private void printInfo(String name, Set<Value> values) {
// String filename = new File(node.getSourceLocation().getFileName()).getName();
// System.out.printf("%s:%d %s: %s\n", filename, node.getSourceLocation().getLineNumber(), name, getArgumentTypeString(values));
}
private Set<Function> active = new HashSet<Function>();
private String computeFunctionSignature(Function function) {
if (!active.add(function))
return "cyclic-function-type";
boolean func = calledAsFunction.contains(function);
boolean constructor = calledAsConstructor.contains(function);
if (!func && !constructor) {
return "unused function";
}
// format: <return type> function(<arg1>, <arg2>, ...)
StringBuilder b = new StringBuilder();
if (func) { // do not print return type for functions only used as constructors
b.append(getReturnTypeString(map.getView(dataflow.getNormalReturns().get(function).getValue().getKey(NullContext.Instance))));
b.append(" ");
}
if (func && constructor) {
b.append("function&constructor");
} else if (func) {
b.append("function");
} else {
b.append("constructor");
}
b.append("(");
for (int i=0; i<function.getParameterNames().size(); i++) {
String pname = function.getParameterNames().get(i);
if (i > 0)
b.append(", ");
b.append(getArgumentTypeString(map.getView(new VariableKey(pname, function, NullContext.Instance))));
}
b.append(")");
// print types of this argument
b.append(" [thisarg=");
b.append(getArgumentTypeString(map.getView(new VariableKey("this", function, NullContext.Instance))));
b.append("]");
active.remove(function);
return b.toString();
}
private String set2string(Set<String> strings) {
if (strings.isEmpty())
return "undefined";
else if (strings.size() == 1)
return strings.iterator().next();
else {
StringBuilder b = new StringBuilder("<");
boolean first=true;
for (String s : strings) {
if (!first)
b.append("|");
else
first=false;
b.append(s);
}
b.append(">");
return b.toString();
}
}
private String getArgumentTypeString(Set<Value> values) {
Set<String> typenames = getTypeNames(values);
return set2string(typenames);
}
private String getReturnTypeString(Set<Value> values) {
Set<String> typenames = getTypeNames(values);
if (typenames.isEmpty())
return "void";
else
return set2string(typenames);
}
private Set<String> getTypeNames(Set<Value> values) {
Set<String> typenames = new TreeSet<String>();
for (Value value : values) {
if (value instanceof NumberValue)
typenames.add("number");
else if (value instanceof BooleanValue)
typenames.add("boolean");
else if (value instanceof StringValue)
typenames.add("string");
// else if (value instanceof NullValue)
// typenames.add("null");
else if (value instanceof UserFunctionValue) {
UserFunctionValue uf = (UserFunctionValue)value;
Function func = uf.getFunction();
typenames.add(computeFunctionSignature(func));
} else if (value instanceof NativeFunctionValue) {
typenames.add("function"); // TODO
} else if (value instanceof ObjectValue) {
typenames.add(getObjectClassName((ObjectValue)value));
}
}
return typenames;
}
private String getObjectClassName(ObjectValue obj) {
if (obj instanceof FunctionValue) {
return "function";
}
Set<String> names = new TreeSet<String>();
for (Value protoval : map.getView(obj.getPrototypeProperty())) {
String name = null;
if (protoval instanceof FunctionPrototypeValue) {
FunctionPrototypeValue proto = (FunctionPrototypeValue) protoval;
if (proto.getFunction() instanceof UserFunctionValue) {
UserFunctionValue uf = (UserFunctionValue) proto.getFunction();
if (uf.getFunction().getName() != null) {
name = uf.getFunction().getName();
} else {
name = "object";
}
}
}
if (name != null)
names.add(name);
}
if (names.isEmpty())
return "object";
else if (names.size() == 1)
return names.iterator().next();
else
return names.toString();
}
});
}
}
}
}
}
}