package dk.brics.xact.analysis.soot;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import soot.Hierarchy;
import soot.Immediate;
import soot.Local;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.ClassConstant;
import soot.jimple.Constant;
import soot.jimple.NullConstant;
import soot.jimple.StringConstant;
import soot.jimple.toolkits.annotation.nullcheck.NullnessAnalysis;
import soot.tagkit.AnnotationStringElem;
import soot.tagkit.AnnotationTag;
import soot.tagkit.VisibilityAnnotationTag;
import soot.tagkit.VisibilityParameterAnnotationTag;
import soot.util.StringTools;
import dk.brics.misc.Origin;
import dk.brics.xact.analysis.ErrorHandler;
import dk.brics.xact.analysis.XMLAnalysisException;
import dk.brics.xact.analysis.flowgraph.FlowGraph;
import dk.brics.xact.analysis.flowgraph.Method;
import dk.brics.xact.analysis.flowgraph.SchemaType;
import dk.brics.xact.analysis.flowgraph.Statement;
import dk.brics.xact.analysis.flowgraph.Variable;
import dk.brics.xact.analysis.flowgraph.statements.AnalyzeStm;
import dk.brics.xact.operations.XMLValidator;
/**
* Context for producing flow graph statements.
* This is simple storage with no actual functionality.
*/
public class TranslatorContext {
/**
* Current class.
*/
private SootClass current_class;
/**
* Current method.
*/
private SootMethod current_method;
/**
* Current source line.
*/
private int current_line;
/**
* Current source origin.
*/
private Origin current_origin;
/**
* Map from Soot field/local/dummy variable descriptor strings to flow graph variables.
*/
private Map<String,Variable> var_map = new HashMap<String,Variable>();
/**
* Class hierarchy.
*/
private Hierarchy hierarchy;
/**
* Serial number for created variables.
*/
private int var_id = 0;
private Variable nothing;
/**
* Namespace map.
*/
private Map<String,String> namespaces = new HashMap<String,String>();
private Map<String,Variable> field_map = new HashMap<String,Variable>();
private FlowGraph graph;
private Map<SootMethod, Method> methods = new HashMap<SootMethod, Method>();
private Map<SootField, SchemaType> fieldSchemaTypes = new HashMap<SootField, SchemaType>();
private NullnessAnalysis nullness;
private StringConstantAnalysis stringConstants;
private ArrayConstantAnalysis arrayConstants;
private Unit currentUnit;
private Map<Statement,ValueBox> stringHotspots1 = new HashMap<Statement, ValueBox>();;
private Map<Statement,ValueBox> stringHotspots2 = new HashMap<Statement, ValueBox>();;
private Map<Statement,String> nodeStmNamespaces = new HashMap<Statement, String>();
private ErrorHandler errors;
private Map<ValueBox,AnalyzeStm> expr2hotspot = new HashMap<ValueBox,AnalyzeStm>();
/**
* Returns the map from soot expression hotspots to
* their corresponding statements in the flow graph.
* @return modifiable map backed by this object
*/
public Map<ValueBox,AnalyzeStm> getHotspotMap() {
return expr2hotspot;
}
/**
* Marks that the specified soot expression hotspot is being
* modelled by the specified analyze statement.
* @param expr a hotspot expression
* @param stm the corresponding statement in the flow graph
*/
public void putHotspot(ValueBox expr, AnalyzeStm stm) {
expr2hotspot.put(expr, stm);
}
public ValueBox getStringHotspot1(Statement s) {
return stringHotspots1.get(s);
}
public ValueBox getStringHotspot2(Statement s) {
return stringHotspots2.get(s);
}
public String getNamespace(Statement s) {
return nodeStmNamespaces.get(s);
}
public void setStringHotspot(Statement stm, ValueBox box) {
stringHotspots1.put(stm, box);
}
public void setStringHotspot(Statement stm, ValueBox box1, ValueBox box2) {
stringHotspots1.put(stm, box1);
stringHotspots2.put(stm, box2);
}
public void setStringHotspot(Statement stm, ValueBox box1, ValueBox box2, String namespace) {
stringHotspots1.put(stm, box1);
stringHotspots2.put(stm, box2);
nodeStmNamespaces.put(stm, namespace);
}
/**
* Constructs a new translator context.
*/
public TranslatorContext(FlowGraph g, ErrorHandler errors) {
this.graph = g;
this.errors = errors;
hierarchy = new Hierarchy();
nothing = new Variable(getNextVarID(), true);
this.namespaces = g.getNamespaces();
}
public ErrorHandler getErrors() {
return errors;
}
public void setCurrentUnit(Unit unit) {
this.currentUnit = unit;
}
public void setNullness(NullnessAnalysis nullness) {
this.nullness = nullness;
}
public void setStringConstants(StringConstantAnalysis stringConstants) {
this.stringConstants = stringConstants;
}
public void setArrayConstants(ArrayConstantAnalysis arrayConstants) {
this.arrayConstants = arrayConstants;
}
public boolean maybeNull(Value v) {
if (v instanceof NullConstant)
return true;
if (v instanceof Constant)
return false;
return !nullness.isAlwaysNonNullBefore(currentUnit, (Immediate)v);
}
public boolean maybeNonNull(Value v) {
if (v instanceof NullConstant)
return false;
if (v instanceof Constant)
return true;
return !nullness.isAlwaysNullBefore(currentUnit, (Immediate)v);
}
public boolean definitelyNull(Value v) {
if (v instanceof NullConstant)
return true;
if (v instanceof Constant)
return false;
return nullness.isAlwaysNullBefore(currentUnit, (Immediate)v);
}
public boolean definitelyNonNull(Value v) {
if (v instanceof NullConstant)
return false;
if (v instanceof Constant)
return true;
return nullness.isAlwaysNonNullBefore(currentUnit, (Immediate)v);
}
public Variable getNothing() {
return nothing;
}
public Method getMethod(SootMethod method) {
return methods.get(method);
}
public void setMethod(SootMethod sm, Method method) {
methods.put(sm, method);
}
/**
* Returns the next fresh variable ID.
*/
public int getNextVarID() {
return var_id++;
}
/**
* Sets the current class.
*/
public void setCurrentClass(SootClass c) {
current_origin = null;
current_class = c;
}
/**
* Returns the current class.
*/
public SootClass getCurrentClass() {
return current_class;
}
/**
* Sets the current method.
*/
public void setCurrentSootMethod(SootMethod m) {
current_origin = null;
current_method = m;
var_map.clear();
}
/**
* Returns the current method.
*/
public SootMethod getCurrentSootMethod() {
return current_method;
}
public Method getCurrentMethod() {
return getMethod(current_method);
}
/**
* Returns the current origin.
*/
public Origin getCurrentOrigin() {
if (current_origin == null) {
if (current_class == null || current_method == null)
return new Origin("???", -1, 0);
current_origin = new Origin(current_class.getName() + "." + current_method.getName(), current_line, 0);
}
return current_origin;
}
/**
* Returns the current line number.
*/
public int getCurrentLine() {
return current_line;
}
/**
* Sets the current line number.
*/
public void setCurrentLine(int line) {
current_origin = null;
current_line = line;
}
/**
* Returns the class hierarchy.
*/
public Hierarchy getHierarchy() {
return hierarchy;
}
public Variable getLocal(String name) {
Variable v = var_map.get(name);
if (v == null) {
v = new Variable(getNextVarID(), false);
var_map.put(name, v);
}
return v;
}
public Variable getField(SootField field) {
Variable v = field_map.get(field.getSignature());
if (v == null) {
v = new Variable(getNextVarID(), true);
field_map.put(field.getSignature(), v);
}
return v;
}
/**
* Returns true if the specified type can have @Type annotations.
*/
public boolean isAnnotatableType(Type t) {
return isSubtypeOf(t, "dk.brics.xact.Node");
}
/**
* Expands a QName according to the current namespace declarations.
*/
public String expandQName(String qname) {
return XMLValidator.expandQName(qname, getNamespaces(), null, getCurrentOrigin());
}
public SchemaType getFieldSchemaType(SootField sf) {
return fieldSchemaTypes.get(sf);
}
public void setFieldSchemaType(SootField sf, SchemaType type) {
fieldSchemaTypes.put(sf, type);
}
/**
* Returns the Type annotation for a field, or null if not present.
*/
public String getTypeAnnotation(SootField sf) {
String type = null;
if (isAnnotatableType(sf.getType()) && sf.hasTag("VisibilityAnnotationTag"))
type = getTypeAnnotation((VisibilityAnnotationTag)sf.getTag("VisibilityAnnotationTag"));
return type;
}
/**
* Returns the Type annotation for a method return, or null if not present.
*/
public String getTypeAnnotation(SootMethod m) {
String type = null;
if (isAnnotatableType(m.getReturnType()) && m.hasTag("VisibilityAnnotationTag"))
type = getTypeAnnotation((VisibilityAnnotationTag)m.getTag("VisibilityAnnotationTag"));
//System.out.println("Found @Type("+type+") at return from "+m);
return type;
}
/**
* Returns the Type annotation for a method parameter, or null if not present.
*/
public String getTypeAnnotationForParameter(SootMethod m, int parameter) {
if (!isAnnotatableType(m.getParameterType(parameter)))
return null;
VisibilityParameterAnnotationTag tag = (VisibilityParameterAnnotationTag)m.getTag("VisibilityParameterAnnotationTag");
if (tag == null)
return null;
return getTypeAnnotation(tag.getVisibilityAnnotations().get(parameter));
}
private String getTypeAnnotation(VisibilityAnnotationTag vat) {
if (vat == null)
return null;
for (AnnotationTag at : vat.getAnnotations())
if (at.getType().equals("Ldk/brics/xact/Type;"))
return ((AnnotationStringElem)at.getElemAt(0)).getValue();
return null;
}
/*public void putField(String name, Variable var) {
field_map.put(name, var);
}*/
/**
* Returns the namespace declaration map (from prefix to URI).
*/
public Map<String,String> getNamespaces() {
return namespaces;
}
public FlowGraph getFlowGraph() {
return graph;
}
public List<SootClass> getSubclassesOfIncluding(RefType t) {
SootClass cl = t.getSootClass();
if (cl.isInterface())
return hierarchy.getImplementersOf(cl);
else
return hierarchy.getSubclassesOfIncluding(cl);
}
public boolean implementsToXMLable(SootClass cl) {
return !cl.isInterface() && hierarchy.getImplementersOf(Scene.v().getSootClass("dk.brics.xact.ToXMLable")).contains(cl);
}
private String unescapeStringConstant(StringConstant sc) {
String unesc = StringTools.getUnEscapedStringOf(sc.toString());
return unesc.substring(1, unesc.length()-1);
}
public boolean hasConstantString(Value v) {
if (v instanceof StringConstant)
return true;
if (v instanceof Local)
return stringConstants.getFlowBefore(currentUnit).containsKey(v);
if (v instanceof NullConstant)
return true;
return false;
}
/**
* Returns the string constant value for the given Soot value or null if it is a null constant.
* @throws XMLAnalysisException if the value does not appear to be a constant string
*/
public String getConstantString(Value v) {
if (v==null)
throw new RuntimeException("null value");
if (v instanceof StringConstant) {
StringConstant sc = (StringConstant)v;
return unescapeStringConstant(sc);
} else if (v instanceof Local) {
StringConstant sc = stringConstants.getFlowBefore(currentUnit).get(v);
if (sc != null) {
return unescapeStringConstant(sc);
}
} else if (v instanceof NullConstant) {
return null;
}
if (System.getProperty("dk.brics.xact.analysis.ignore-non-constant-string") != null) {
return "NONCONSTANT";
}
throw new XMLAnalysisException("Constant string expected", getCurrentOrigin());
}
/**
* Returns the information known about the specified array variable, if any.
* @param v any value (UNKNOWN is simply returned for non-array variables)
* @return an instance of ArrayConstantInfo (note that its contents may be null)
* @see ArrayConstantInfo
* @see ArrayConstantAnalysis
*/
public ArrayConstantInfo getConstantArray(Value v) {
if (!(v instanceof Local))
return ArrayConstantInfo.UNKNOWN;
Local local = (Local)v;
ArrayConstantInfo info = arrayConstants.getFlowBefore(currentUnit).get(local);
if (info != null)
return info;
else {
// technically this is BOTTOM, but it doesn't really happen in practice so
// just return TOP so the caller doesn't have to bother with null return values
return ArrayConstantInfo.UNKNOWN;
}
}
/**
* Returns the origin constant value for the given Soot value.
* @throws XMLAnalysisException if the value does not appear to be a constant origin
*/
public Origin getConstantOrigin(Value v) {
//return getCurrentOrigin();
return new Origin("???", 0, 0); // TODO: getConstantOrigin
}
public String getModifiedPackageNameFromClassConstant(Value v) {
if (v instanceof ClassConstant) {
String s = ((ClassConstant)v).getValue();
int lastSlash = s.lastIndexOf('/');
if (lastSlash==-1)
return "";
else
return s.substring(0, lastSlash);
} else {
throw new XMLAnalysisException("Class constant expected", getCurrentOrigin());
}
}
/**
* Retrieves a resource string using the current class loader.
* Assumes UTF-8.
*/
public String getResourceString(String name) {
InputStream in = TranslatorContext.class.getClassLoader().getResourceAsStream(name);
if (in == null)
throw new XMLAnalysisException("Resource not found: " + name, getCurrentOrigin());
BufferedReader reader = null;
try {
StringBuilder sb = new StringBuilder(1000);
reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
char[] cb = new char[1024];
int len=0;
while((len = reader.read(cb)) != -1)
sb.append(cb, 0, len);
return sb.toString();
} catch (IOException e) {
throw new XMLAnalysisException(e, getCurrentOrigin());
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
throw new XMLAnalysisException(e, getCurrentOrigin());
}
}
}
/**
* Returns true if the first argument is a subtype of the class or interface named by the second argument.
*/
public boolean isSubtypeOf(SootClass a, String b) {
return isSubtypeOf(a, Scene.v().getSootClass(b));
}
/**
* Returns true if the first argument is a class or interface that is a subtype of the
* class or interface named by the second argument.
*/
public boolean isSubtypeOf(Type a, String b) {
if (a instanceof RefType)
return isSubtypeOf(((RefType)a).getSootClass(), Scene.v().getSootClass(b));
else
return false;
}
/**
* Returns true if the first argument is a subtype of the second argument.
*/
public boolean isSubtypeOf(SootClass a, SootClass b) {
if (a.equals(b))
return true;
if (b.getType().equals(RefType.v("java.lang.Object")))
return true;
if (a.isInterface()) {
return b.isInterface() && getHierarchy().isInterfaceSubinterfaceOf(a, b);
}
if (b.isInterface()) {
return getHierarchy().getImplementersOf(b).contains(a);
}
return getHierarchy().isClassSubclassOf(a, b);
}
/**
* Expands a QName map according to the current namespace declarations.
*/
public Map<String,String> expandQNames(Map<String,String> qnames) {
if (qnames == null)
return null;
Map<String,String> expanded = new HashMap<String,String>();
for (Map.Entry<String,String> me : qnames.entrySet())
expanded.put(me.getKey(), expandQName(me.getValue()));
return expanded;
}
/**
* Returns an automaton representing the possible string values of the given Soot string expression.
*/
/*public Automaton getAutomatonFromStringExp(ValueBox b) {
if (!isStringType(b))
throw new XMLValidationException("String expression expected " + b, getCurrentOrigin());
return getStringAnalysis().getAutomaton(b);
}*/
public static boolean isStringType(ValueBox b) {
return b.getValue().getType().toString().indexOf("java.lang.String") == 0; // matches (arrays of) String
}
public SchemaType parseSchemaType(String annotation, Origin origin) {
if (annotation == null)
return null;
XMLAnalysisException ex = new XMLAnalysisException("Malformed @Type argument '" + annotation + "'", getCurrentOrigin());
annotation = annotation.trim();
// make schema
SchemaType schema;
int leftbrack = annotation.indexOf('[');
int rightbrack = annotation.lastIndexOf(']');
if (leftbrack<0 != rightbrack<0)
throw ex;
if (leftbrack<0 || rightbrack<0) {
// no gap annotations
schema = new SchemaType(expandQName(annotation), origin);
} else {
// parse gap annotations
String start = annotation.substring(0, leftbrack).trim();
String gaps = annotation.substring(leftbrack+1, rightbrack).trim();
String[] ga = gaps.split(",");
if (ga.length==0)
ga = new String[]{ gaps };
// build gap info
Collection<String> tgaps = new LinkedHashSet<String>();
Collection<String> agaps = new LinkedHashSet<String>();
Map<String,String> gap_types = new LinkedHashMap<String,String>();
for (String g : ga) {
g = g.trim();
int space = g.indexOf(' ');
if (space<0)
throw ex;
String gaptype = g.substring(0, space).trim();
String gapname = g.substring(space+1).trim();
Collection<String> set;
if (gapname.startsWith("@")) { // attribute gap?
gapname = gapname.substring(1);
set = agaps;
} else
set = tgaps;
set.add(gapname);
gap_types.put(gapname, gaptype);
}
schema = new SchemaType(expandQName(start), tgaps, agaps, expandQNames(gap_types), origin);
}
return schema;
}
/**
* Returns list of all classes extending or implementing the specified class or interface.
* @param clazz a class or interface
* @return an unmodifiable list
*/
public List<SootClass> getSubtypesOfIncluding(SootClass clazz) {
if (clazz.isInterface()) {
return getHierarchy().getImplementersOf(clazz);
} else {
return getHierarchy().getSubclassesOfIncluding(clazz);
}
}
/**
* Returns list of all classes extending or implementing the specified class or interface.
* @param type a class or interface
* @return an unmodifiable list
*/
public List<SootClass> getSubtypesOfIncluding(RefType type) {
return getSubtypesOfIncluding(type.getSootClass());
}
}