Package dk.brics.string.java

Source Code of dk.brics.string.java.BuiltinMethodCallTranslator

package dk.brics.string.java;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import soot.ArrayType;
import soot.BooleanType;
import soot.CharType;
import soot.IntType;
import soot.Local;
import soot.RefLikeType;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.Type;
import soot.Value;
import soot.ValueBox;
import soot.jimple.Constant;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.IntConstant;
import soot.jimple.InvokeExpr;
import soot.jimple.StringConstant;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.RegExp;
import dk.brics.string.intermediate.ArrayAddAll;
import dk.brics.string.intermediate.ArrayAssignment;
import dk.brics.string.intermediate.ArrayNew;
import dk.brics.string.intermediate.ArrayWriteArray;
import dk.brics.string.intermediate.ArrayWriteElement;
import dk.brics.string.intermediate.BasicBinaryOp;
import dk.brics.string.intermediate.BasicUnaryOp;
import dk.brics.string.intermediate.Call;
import dk.brics.string.intermediate.Method;
import dk.brics.string.intermediate.PrimitiveInit;
import dk.brics.string.intermediate.StringAssignment;
import dk.brics.string.intermediate.StringBufferAppend;
import dk.brics.string.intermediate.StringBufferAppendChar;
import dk.brics.string.intermediate.StringBufferBinaryOp;
import dk.brics.string.intermediate.StringBufferInit;
import dk.brics.string.intermediate.StringBufferPrepend;
import dk.brics.string.intermediate.StringBufferUnaryOp;
import dk.brics.string.intermediate.StringConcat;
import dk.brics.string.intermediate.StringFromArray;
import dk.brics.string.intermediate.StringFromStringBuffer;
import dk.brics.string.intermediate.StringInit;
import dk.brics.string.intermediate.Variable;
import dk.brics.string.intermediate.VariableType;
import dk.brics.string.stringoperations.Basic;
import dk.brics.string.stringoperations.BinaryOperation;
import dk.brics.string.stringoperations.BooleanToString;
import dk.brics.string.stringoperations.CharAt1;
import dk.brics.string.stringoperations.CharAt2;
import dk.brics.string.stringoperations.Contains;
import dk.brics.string.stringoperations.Delete;
import dk.brics.string.stringoperations.DeleteCharAt;
import dk.brics.string.stringoperations.Insert;
import dk.brics.string.stringoperations.Postfix;
import dk.brics.string.stringoperations.Prefix;
import dk.brics.string.stringoperations.Replace1;
import dk.brics.string.stringoperations.Replace2;
import dk.brics.string.stringoperations.Replace3;
import dk.brics.string.stringoperations.Replace4;
import dk.brics.string.stringoperations.Replace5;
import dk.brics.string.stringoperations.Replace6;
import dk.brics.string.stringoperations.Reverse;
import dk.brics.string.stringoperations.SetCharAt1;
import dk.brics.string.stringoperations.SetCharAt2;
import dk.brics.string.stringoperations.SetLength;
import dk.brics.string.stringoperations.Split;
import dk.brics.string.stringoperations.Substring;
import dk.brics.string.stringoperations.ToLowerCase;
import dk.brics.string.stringoperations.ToUpperCase;
import dk.brics.string.stringoperations.Trim;
import dk.brics.string.stringoperations.UnaryOperation;

/**
* Translates calls to methods declared in {@link String}, {@link StringBuffer}, {@link StringBuilder}, and {@link Object} into
* statements, modelling them as precisely as possible.
*/
public class BuiltinMethodCallTranslator implements MethodCallTranslator {

  private static final Automaton ARRAY_OBJECT_AUTOMATON = new RegExp("\"[Ljava.lang.String;@\"[0-9a-f]+").toAutomaton();
  private static final Automaton UNSIGNED_HEX_AUTOMATON = new RegExp("0|[1-9a-f][0-9a-f]*").toAutomaton();
  private static final Automaton UNSIGNED_OCTAL_AUTOMATON = new RegExp("0|[1-7][0-7]*").toAutomaton();
  private static final Automaton UNSIGNED_BINARY_AUTOMATON = new RegExp("0|1[01]*").toAutomaton();
 
  /**
   * List of collection types we trust. Invoking a constructor on a
   * collection-type not in this list will produce a corrupt collection.
   * <p/>
   * Note that this ONLY affects constructor call! Collections returned by
   * eg. Collections.unmodifiableList() may be considered valid even though
   * its type is not in this list.
   */
  private static final String[] TRUSTED_COLLECTIONS = {
    "java.util.ArrayList",
    "java.util.Vector",
    "java.util.LinkedList",
    "java.util.HashSet",
    "java.util.LinkedHashSet",
    "java.util.TreeSet"
  };
  private static final Set<String> trustedCollections;
  static {
    Set<String> set = new HashSet<String>();
    for (String s : TRUSTED_COLLECTIONS) {
      set.add(s);
    }
    trustedCollections = Collections.unmodifiableSet(set);
  }
 
  /**
   * List of StringBuffer methods that have no side-effects and return uninteresting objects. This technique only works
   * because all overloads of a pure method in StringBuffer are pure.
   * <p/>
   * Does *not* include those declared in Object, but does include those inherited from interfaces.
   */
  private static final String STRING_BUFFER_IGNORED_METHODS =
    "capacity|charAt|codePointAt|codePointBefore|codePointCount|ensureCapacity|getChars|indexOf|"
  "lastIndexOf|length|offsetByCodePoints|subSequence|trimToSize"
  ;
 
  /**
   * Accepts names of StringBuffer methods that have no side-effects and return uninteresting objects.
   */
  private static final Automaton STRING_BUFFER_IGNORED_METHODS_AUTO = new RegExp(STRING_BUFFER_IGNORED_METHODS).toAutomaton();
 
  public Variable translateMethodCall(InstanceInvokeExpr expr, SootMethod target, Variable callee, List<Variable> arguments, IntermediateFactory factory) {
    String methodName = target.getName();
    int numArgs = arguments.size();
    SootClass declaringClass = target.getDeclaringClass();
   
    //
    //  STRING
    //
    if (isString(declaringClass)) {
      // String.toString()
            if (methodName.equals("toString") && numArgs == 0) {
                return callee;
               
            // String.intern()
            } else if (methodName.equals("intern") && numArgs == 0) {
                return callee;
               
            // String.concat(String)  [explicit, not invoked by + operator]
            } else if (methodName.equals("concat") && numArgs == 1 && isString(target.getParameterType(0))) {
                // translate the argument
                Variable rvar = arguments.get(0);
                Variable result = factory.createVariable(VariableType.STRING);
                factory.addStatement(new StringConcat(result, callee, rvar));
                return result;
               
            // String.replace(char,char)
            } else if (methodName.equals("replace") && numArgs == 2 &&
                    isChar(target.getParameterType(0)) &&
                    isChar(target.getParameterType(1))) {
                Integer arg1 = trackInteger(expr.getArg(0));
                Integer arg2 = trackInteger(expr.getArg(1));
                UnaryOperation op;
                if (arg1 != null) {
                    if (arg2 != null) {
                        op = new Replace1((char) arg1.intValue(), (char) arg2.intValue());
                    } else {
                        op = new Replace2((char) arg1.intValue());
                    }
                } else {
                    if (arg2 != null) {
                        op = new Replace3((char) arg2.intValue());
                    } else {
                        op = new Replace4();
                    }
                }
               
                Variable temp = factory.createVariable(VariableType.STRINGBUFFER);
                Variable result = factory.createVariable(VariableType.STRING);
                factory.addStatement(new StringBufferInit(temp, callee));
                factory.addStatement(new StringBufferUnaryOp(temp, op));
                factory.addStatement(new StringFromStringBuffer(result, temp));
                return result;
               
            // String.replace(CharSequence, CharSequence)
            } else if (methodName.equals("replace") && numArgs == 2 &&
                    isCharSequence(target.getParameterType(0)) &&
                    isCharSequence(target.getParameterType(1))) {
                String arg1 = trackString(expr.getArg(0));
                String arg2 = trackString(expr.getArg(1));
                Variable result = factory.createVariable(VariableType.STRING);
               
                // if either argument is unknown, give up.  [TODO make this better]
                if (arg1 == null || arg2 == null) {
                  factory.addStatement(new StringInit(result, Basic.makeAnyString()));
                  return result;
                }
               
                Replace6 rep = new Replace6(arg1, arg2);
                Variable temp = factory.createVariable(VariableType.STRINGBUFFER);
                factory.addStatement(new StringBufferInit(temp, callee));
                factory.addStatement(new StringBufferUnaryOp(temp, rep));
                factory.addStatement(new StringFromStringBuffer(result, temp));
                return result;
               
            // String.trim()
            } else if (methodName.equals("trim") && numArgs == 0) {
                UnaryOperation op = new Trim();
                Variable temp = factory.createVariable(VariableType.STRINGBUFFER);
                Variable result = factory.createVariable(VariableType.STRING);
                factory.addStatement(new StringBufferInit(temp, callee));
                factory.addStatement(new StringBufferUnaryOp(temp, op));
                factory.addStatement(new StringFromStringBuffer(result, temp));
                return result;
           
            // String.substring(int)    [this method returns a suffix of the string, starting at the specified index]
            } else if (methodName.equals("substring") && numArgs == 1) {
                UnaryOperation op = new Postfix();
                Variable temp = factory.createVariable(VariableType.STRINGBUFFER);
                Variable result = factory.createVariable(VariableType.STRING);
                factory.addStatement(new StringBufferInit(temp, callee));
                factory.addStatement(new StringBufferUnaryOp(temp, op));
                factory.addStatement(new StringFromStringBuffer(result, temp));
                return result;
           
            // String.substring(int,int)
            } else if (methodName.equals("substring") && numArgs == 2) {
                UnaryOperation op;
                Integer arg1 = trackInteger(expr.getArg(0));
                if (arg1 != null && arg1.intValue() == 0) {
                    op = new Prefix();
                } else {
                    op = new Substring();
                }
                Variable temp = factory.createVariable(VariableType.STRINGBUFFER);
                Variable result = factory.createVariable(VariableType.STRING);
                factory.addStatement(new StringBufferInit(temp, callee));
                factory.addStatement(new StringBufferUnaryOp(temp, op));
                factory.addStatement(new StringFromStringBuffer(result, temp));
                return result;
           
            // String.toLowerCase()
            } else if (methodName.equals("toLowerCase") && numArgs == 0) {
                UnaryOperation op = new ToLowerCase();
                Variable temp = factory.createVariable(VariableType.STRINGBUFFER);
                Variable result = factory.createVariable(VariableType.STRING);
                factory.addStatement(new StringBufferInit(temp, callee));
                factory.addStatement(new StringBufferUnaryOp(temp, op));
                factory.addStatement(new StringFromStringBuffer(result, temp));
                return result;
           
            // String.toUpperCase()
            } else if (methodName.equals("toUpperCase") && numArgs == 0) {
                UnaryOperation op = new ToUpperCase();
                Variable temp = factory.createVariable(VariableType.STRINGBUFFER);
                Variable result = factory.createVariable(VariableType.STRING);
                factory.addStatement(new StringBufferInit(temp, callee));
                factory.addStatement(new StringBufferUnaryOp(temp, op));
                factory.addStatement(new StringFromStringBuffer(result, temp));
                return result;
           
            // String.split(String)
            } else if (methodName.equals("split") && numArgs == 1) {
                Variable result = factory.createVariable(VariableType.ARRAY);
                factory.addStatement(new BasicUnaryOp(result, callee, new Split()));
                return result;
           
            // String.charAt(int)
            } else if (methodName.equals("charAt") && numArgs == 1) {
                UnaryOperation op;
                Integer arg = trackInteger(expr.getArg(0));
                if (arg != null) {
                    op = new CharAt1(arg);
                } else {
                    op = new CharAt2();
                }
                Variable result = factory.createVariable(VariableType.PRIMITIVE);
                factory.addStatement(new BasicUnaryOp(result, callee, op));
                return result;
               
            // String.contains(CharSequence)
            } else if (methodName.equals("contains") && numArgs == 1) {
                // this is experimental stuff. it is working, but probably not that useful
                Variable result = factory.createVariable(VariableType.PRIMITIVE);
                factory.addStatement(new BasicBinaryOp(result, callee, arguments.get(0), new Contains()));
                return result;
           
           
            // String.contentEquals(CharSequence) and String.contentEquals(StringBuffer)
            } else if (methodName.equals("contentEquals") && numArgs == 1) {
                // we can't say anything meaningful except the argument is NOT corrupted
                // (the argument will be considered corrupted if we do not handle it here)
                Variable result = factory.createVariable(VariableType.PRIMITIVE);
                factory.addStatement(new PrimitiveInit(result, Basic.getBinaryBooleans()));
                return result;
            }
           
            // String.toCharArray()
            else if (methodName.equals("toCharArray") && numArgs == 0) {
                Variable result = factory.createVariable(VariableType.ARRAY);
                factory.addStatement(new ArrayNew(result));
                Variable charAt = factory.createVariable(VariableType.PRIMITIVE);
                factory.addStatement(new BasicUnaryOp(charAt, callee, new CharAt2()));
                factory.addStatement(new ArrayWriteElement(result, charAt));
                return result;
            }
           
     
          return null;
     
    }
   
    //
    //    STRINGBUFFER
    //
    else if (isBufferOrBuilder(declaringClass)) {
      if (methodName.equals("toString") && numArgs == 0) {
        Variable result = factory.createVariable(VariableType.STRING);
                factory.addStatement(new StringFromStringBuffer(result, callee));
                return result;
            }
            // StringBuffer.append(<any type>)
            else if (methodName.equals("append") && numArgs == 1) {
                Variable rvar = valueOf(expr.getArgBox(0), arguments.get(0), 10, target.getParameterType(0), factory);
                factory.addStatement(new StringBufferAppend(callee, rvar));
                return callee;
            }
            // StringBuffer.insert(int, <any type>)
            else if (methodName.equals("insert") && numArgs == 2 &&
                    isInt(target.getParameterType(0))) {
                Integer pos = trackInteger(expr.getArg(0));
                Variable rvar = valueOf(expr.getArgBox(1), arguments.get(1), 10, target.getParameterType(0), factory);
                if (pos != null && pos.intValue() == 0) {
                  factory.addStatement(new StringBufferPrepend(callee, rvar));
                } else {
                  factory.addStatement(new StringBufferBinaryOp(callee, new Insert(), rvar));
                }
                return callee;
            }
            // StringBuffer.delete(int,int)
            else if (methodName.equals("delete") && numArgs == 2) {
                UnaryOperation op = new Delete();
                factory.addStatement(new StringBufferUnaryOp(callee, op));
                return callee;
            }
            // StringBuffer.deleteCharAt(int)
            else if (methodName.equals("deleteCharAt") && numArgs == 1) {
                UnaryOperation op = new DeleteCharAt();
                factory.addStatement(new StringBufferUnaryOp(callee, op));
                return callee;
            }
            // StringBuffer.replace(int start, int end, String replacement)
            else if (methodName.equals("replace") && numArgs == 3) {
                BinaryOperation op = new Replace5();
                Variable rvar = valueOf(expr.getArgBox(2), arguments.get(2), 10, target.getParameterType(2), factory);
                factory.addStatement(new StringBufferBinaryOp(callee, op, rvar));
                return callee;
            }
            // StringBuffer.reverse()
            else if (methodName.equals("reverse") && numArgs == 0) {
                UnaryOperation op = new Reverse();
                factory.addStatement(new StringBufferUnaryOp(callee, op));
                return callee;
            }
            // StringBuffer.setCharAt(int, char)  [NOTE: This method returns void]
            else if (methodName.equals("setCharAt") && numArgs == 2) {
                Integer c = trackInteger(expr.getArg(1));
                if (c == null) {
                    UnaryOperation op = new SetCharAt2();
                    factory.addStatement(new StringBufferUnaryOp(callee, op));
                } else {
                    UnaryOperation op = new SetCharAt1((char) c.intValue());
                    factory.addStatement(new StringBufferUnaryOp(callee, op));
                }
                return factory.getNothing();
            }
            // StringBuffer.setLength(int)      [NOTE: This method returns void]
            else if (methodName.equals("setLength") && numArgs == 1) {
                UnaryOperation op = new SetLength();
                factory.addStatement(new StringBufferUnaryOp(callee, op));
                return factory.getNothing();// returns void
            }
            // StringBuffer.substring(int)      [NOTE: Returns a string]
            else if (methodName.equals("substring") && numArgs == 1) {
                UnaryOperation op = new Postfix();

                Variable result = factory.createVariable(VariableType.STRING);
               
                // clone the stringbuffer
                Variable clone = makeStringBufferClone(callee, factory);
               
                // perform the substring operation on the clone
                factory.addStatement(new StringBufferUnaryOp(clone, op));
               
                // now put the clone's value back into the result variable
                factory.addStatement(new StringFromStringBuffer(result, clone));
               
                return result;
           
            // StringBuffer.substring(int,int)    [NOTE: Returns a string]
            else if (methodName.equals("substring") && numArgs == 2) {
                UnaryOperation op;
                Integer arg1 = trackInteger(expr.getArg(0));
                if (arg1 != null && arg1.intValue() == 0) {
                    op = new Prefix();
                } else {
                    op = new Substring();
                }
               
                Variable result = factory.createVariable(VariableType.STRING);
               
                // clone the stringbuffer
                Variable clone = makeStringBufferClone(callee, factory);
               
                // perform the substring operation on the clone
                factory.addStatement(new StringBufferUnaryOp(clone, op));
               
                // now put the clone's value back into the result variable
                factory.addStatement(new StringFromStringBuffer(result, clone));
               
                return result;
            }
     
            else if (STRING_BUFFER_IGNORED_METHODS_AUTO.run(methodName)) {
              // A method without side-effects. Just return something.
              return factory.createVariable(factory.fromSootType(target.getReturnType()));
            }
     
      System.err.println("Unknown StringBuffer method: " + target.getSignature());
    }
   
    //
    //  WRAPPERS
    //
    else if (isWrapperClass(declaringClass)) {
      if (methodName.equals("toString") && numArgs == 0) {
        Automaton typeAuto = Automatons.fromType(declaringClass.getName());
        if (typeAuto == null)
          throw new RuntimeException("Unknown wrapper class " + declaringClass.getName());
       
        Variable result = factory.createVariable(VariableType.STRING);
        factory.addStatement(new StringInit(result, typeAuto));
        return result;
      }
     
      //System.err.println("Unknown wrapper method: " + target.getSignature());
    }
   
    //
    //  OBJECT
    //
    else if (isObjectClass(declaringClass)) {
      if (methodName.equals("equals") || methodName.equals("hashCode") || methodName.equals("getClass")) {
        // no side-effects
        return factory.createVariable(factory.fromSootType(target.getReturnType()));
      }
    }
   
    return null;
  }
 
  public Variable translateAbstractMethodCall(InstanceInvokeExpr expr,
      SootMethod target, Variable callee, List<Variable> arguments,
      IntermediateFactory factory) {
    SootClass declaringClass = target.getDeclaringClass();
    String methodName = target.getName();
    int numArgs = expr.getArgCount();
   
    //
    //  COLLECTIONS
    //
    if (factory.isSubtypeOf(declaringClass, Scene.v().getSootClass("java.util.Collection"))) {
      // add(String)
      if (methodName.equals("add") && numArgs == 1 && isString(expr.getArg(0).getType())) {
        factory.addStatement(new ArrayWriteElement(callee, arguments.get(0)));
        return anybool(factory);
        // note: if a non-string object was inserted, this method returns null
        // so the collection gets corrupted as it should
      }
      // add(int,String)
      else if (methodName.equals("add") && numArgs == 2 && isInt(expr.getArg(0).getType()) && isString(expr.getArg(1).getType())) {
        factory.addStatement(new ArrayWriteElement(callee, arguments.get(1)));
        return anybool(factory);
      }
      // addAll(<known collection>)
      else if (methodName.equals("addAll") && numArgs == 1 && factory.fromSootType(expr.getArg(0).getType()) == VariableType.ARRAY) {
        factory.addStatement(new ArrayWriteArray(callee, arguments.get(0)));
        return anybool(factory);
      }
      // addAll(int,<known collection>)
      else if (methodName.equals("addAll") && numArgs == 2 && isInt(expr.getArg(0).getType()) && factory.fromSootType(expr.getArg(1).getType()) == VariableType.ARRAY) {
        factory.addStatement(new ArrayWriteArray(callee, arguments.get(1)));
        return anybool(factory);
      }
      // clear()
      else if (methodName.equals("clear") && numArgs == 0) {
        // TODO Collection.clear()
        // for now just say no side-effects. this is sound.
        return factory.getNothing();
      }
      // set(int,String)
      else if (methodName.equals("set") && numArgs == 2 && isInt(expr.getArg(0).getType()) && isString(expr.getArg(1).getType())) {
        // note: List.set returns the element previously at the position
       
        // get the old element
        Variable old = factory.createVariable(VariableType.STRING);
        factory.addStatement(new StringFromArray(old, callee));
       
        // insert the new element
        factory.addStatement(new ArrayWriteElement(callee, arguments.get(1)));
       
        return old;
      }
      // get(int)
      else if (methodName.equals("get") && numArgs == 1 && isInt(expr.getArg(0).getType())) {
        Variable result = factory.createVariable(VariableType.STRING);
        factory.addStatement(new StringFromArray(result, callee));
        return result;
      }
      // iterator()
      else if (methodName.equals("iterator") && numArgs == 0) {
        // changes to the collection might reflect on the iterator
        // and vice versa (eg using ListIterator.add)
        return callee;
      }
      // listIterator()
      else if (methodName.equals("listIterator") && numArgs == 0) {
        // changes to the collection might reflect on the iterator
        // and vice versa (eg using ListIterator.add)
        return callee;
      }
      // methods without side-effects returning booleans
      else if (methodName.equals("contains")
          || methodName.equals("containsAll")
          || methodName.equals("isEmpty")) {
        return anybool(factory);
      }
      // remove() and removeAll() are just identity operations for now
      else if (methodName.equals("remove")
          || methodName.equals("removeAll")) {
        return anybool(factory);
      }
      // size()
      else if (methodName.equals("size")) {
        return factory.getNothing();
      }
      else if (methodName.equals("toArray") && numArgs == 0) {
        Variable result = factory.createVariable(VariableType.ARRAY);
        factory.addStatement(new ArrayNew(result));
        factory.addStatement(new ArrayAddAll(result, callee));
        return result;
      }
      else if (methodName.equals("toArray") && numArgs == 1) {
        Variable result = factory.createVariable(VariableType.ARRAY);
       
        // the elements MIGHT be stored into the argument
        factory.startBranch();
        // 1) not stored in argument
        {
          factory.addStatement(new ArrayNew(result));
          factory.useBranch();
        }
        // 2) stored in argument
        {
          // note: existing elements in the array may remain
          // in particular if the array is larger than the collection,
          // the exceeding elements are unchanged, so do not clear the array here
          factory.addStatement(new ArrayAssignment(result, arguments.get(0)));
          factory.useBranch();
        }
        factory.endBranch();
       
        factory.addStatement(new ArrayAddAll(result, callee));
       
        return result;
      }
    }
   
    //
    // ITERATORS
    //
    else if (factory.isSubtypeOf(declaringClass, Scene.v().getSootClass("java.util.Iterator"))) {
      if (methodName.equals("hasNext") && numArgs == 0) {
        return anybool(factory); // no side-effects
      }
      else if (methodName.equals("hasPrevious") && numArgs == 0) {
        return anybool(factory); // no side-effects
      }
      else if ((methodName.equals("next") || methodName.equals("previous")) && numArgs == 0) {
        Variable result = factory.createVariable(VariableType.STRING);
        factory.addStatement(new StringFromArray(result, callee));
        return result;
      }
      else if (methodName.equals("remove") && numArgs == 1) {
        return anybool(factory); // just prevent corruption
      }
      else if (methodName.equals("add") && numArgs == 1 && isString(expr.getArg(0).getType())) {
        factory.addStatement(new ArrayWriteElement(callee, arguments.get(0)));
        return factory.getNothing();
      }
      else if (methodName.equals("set") && numArgs == 1 && isString(expr.getArg(0).getType())) {
        factory.addStatement(new ArrayWriteElement(callee, arguments.get(0)));
        return factory.getNothing();
      }
      else if (methodName.equals("nextIndex") && numArgs == 0) {
        return factory.getNothing();
      }
      else if (methodName.equals("previousIndex") && numArgs == 0) {
        return factory.getNothing();
      }
    }
   
    return null;
  }
 
  private Variable anybool(IntermediateFactory factory) {
    Variable var = factory.createVariable(VariableType.PRIMITIVE);
    factory.addStatement(new PrimitiveInit(var, Basic.getBinaryBooleans()));
    return var;
  }
 
  public Variable translateStaticMethodCall(InvokeExpr expr, List<Variable> arguments, IntermediateFactory factory) {
    SootMethod method = expr.getMethod();
    SootClass declaringClass = method.getDeclaringClass();
    String methodName = method.getName();
    int numArgs = arguments.size();
    String className = declaringClass.getName();
   
    if (isString(declaringClass)) {
      if (methodName.equals("valueOf") && numArgs == 1) {
        return valueOf(expr.getArgBox(0), arguments.get(0), 10, method.getParameterType(0), factory);
      }
    }
    else if (isWrapperClass(declaringClass) && methodName.equals("toString") && numArgs == 1) {
      return valueOf(expr.getArgBox(0), arguments.get(0), 10, method.getParameterType(0), factory);
      // make sure wrapper classes try the remaining else-ifs
      // do not nest methodName.equals("toString") in an inner if here
    }
   
    else if (className.equals("java.lang.Integer")) {
      if (methodName.equals("toHexString") && numArgs == 1) {
        Integer arg = trackInteger(expr.getArg(0));
        if (arg != null) {
          return makeStringConstant(Integer.toHexString(arg), factory);
        }
        return makeStringVariable(UNSIGNED_HEX_AUTOMATON, factory);
      }
     
      else if (methodName.equals("toOctalString") && numArgs == 1) {
        Integer arg = trackInteger(expr.getArg(0));
        if (arg != null) {
          return makeStringConstant(Integer.toOctalString(arg), factory);
        }
        return makeStringVariable(UNSIGNED_OCTAL_AUTOMATON, factory);
      }
     
      else if (methodName.equals("toBinaryString") && numArgs == 1) {
        Integer arg = trackInteger(expr.getArg(0));
        if (arg != null) {
          return makeStringConstant(Integer.toBinaryString(arg), factory);
        }
        return makeStringVariable(UNSIGNED_BINARY_AUTOMATON, factory);
      }
    }
   
    else if (className.equals("java.util.Arrays")) {
      if (methodName.equals("asList")) {
        return arguments.get(0);
      }
      else if (methodName.equals("equals")) {
        return anybool(factory);
      }
      else if (methodName.equals("binarySearch")
          || methodName.equals("deepHashCode")
          || methodName.equals("hashCode")
          || methodName.equals("sort")) {
        return factory.getNothing();
      }
    }
   
    else if (className.equals("java.util.Collections")) {
      if (methodName.equals("unmodifiableList") && numArgs == 1) {
        // the returned collection is immutable but right now it can still get corrupt
        // TODO add immunity to protect immutable collections
        return arguments.get(0);
      }
      else if (methodName.equals("unmodifiableSet") && numArgs == 1) {
        return arguments.get(0);
      }
      else if (methodName.equals("unmodifiableCollection") && numArgs == 1) {
        return arguments.get(0);
      }
      else if (methodName.equals("unmodifiableSortedSet") && numArgs == 1) {
        return arguments.get(0);
      }
      else if (methodName.equals("synchronizedList") && numArgs == 1) {
        return arguments.get(0);
      }
      else if (methodName.equals("synchronizedSet") && numArgs == 1) {
        return arguments.get(0);
      }
      else if (methodName.equals("synchronizedCollection") && numArgs == 1) {
        return arguments.get(0);
      }
      else if (methodName.equals("synchronizedSortedSet") && numArgs == 1) {
        return arguments.get(0);
      }
      else if (methodName.equals("swap") && numArgs == 2) {
        return factory.getNothing(); // language is unaffected
      }
      else if (methodName.equals("sort")) {
        return factory.getNothing();
      }
      else if (methodName.equals("shuffle")) {
        return factory.getNothing();
      }
      else if (methodName.equals("rotate")) {
        return factory.getNothing();
      }
      else if (methodName.equals("reverse")) {
        return factory.getNothing();
      }
      else if (methodName.equals("singleton") && numArgs == 1 && isString(expr.getArg(0).getType())) {
        Variable v = factory.createVariable(VariableType.ARRAY);
        factory.addStatement(new ArrayNew(v));
        factory.addStatement(new ArrayWriteElement(v, arguments.get(0)));
        return v;
      }
      else if (methodName.equals("singletonList") && numArgs == 1 && isString(expr.getArg(0).getType())) {
        Variable v = factory.createVariable(VariableType.ARRAY);
        factory.addStatement(new ArrayNew(v));
        factory.addStatement(new ArrayWriteElement(v, arguments.get(0)));
        return v;
      }
    }
   
    return null;
  }
 
  public boolean translateConstructorCall(InstanceInvokeExpr expr, Variable callee, List<Variable> arguments, IntermediateFactory factory) {
    SootMethod method = expr.getMethod();
    SootClass declaringClass = method.getDeclaringClass();
    int numArgs = arguments.size();
   
    if (isString(declaringClass)) {
      // new String()
      if (numArgs == 0) {
        factory.addStatement(new StringInit(callee, Basic.makeEmptyString()));
        return true;
      }
     
      // new String(String)
      if (numArgs == 1 && isString(method.getParameterType(0))) {
                factory.addStatement(new StringAssignment(callee, arguments.get(0)));
                return true;
      }  
     
      // new String(StringBuffer); new String(StringBuilder)
            if (numArgs == 1 && isBufferOrBuilder(method.getParameterType(0))) {
        factory.addStatement(new StringFromStringBuffer(callee, arguments.get(0)));
                return true;
            }
     
          // unsupported constructor
          // make any string. this is slightly better than returning false,
          // because here we can guarantee that the arguments are not corrupted.
            factory.addStatement(new StringInit(callee, Basic.makeAnyString()));
    }
    else if (isBufferOrBuilder(declaringClass)) {
      // new StringBuffer(); new StringBuffer(int capacity)
      if (numArgs == 0 || (numArgs == 1 && isInt(method.getParameterType(0)))) {
        Variable empty = factory.createVariable(VariableType.STRING);
        factory.addStatement(new StringInit(empty, Basic.makeEmptyString()));
        factory.addStatement(new StringBufferInit(callee, empty));
        return true;
      }
     
      // new StringBuffer(String)
      if (numArgs == 1 && isString(method.getParameterType(0))) {
        factory.addStatement(new StringBufferInit(callee, arguments.get(0)));
        return true;
      }
     
      // new StringBuffer(CharSequence)
      if (numArgs == 1 && isCharSequence(method.getParameterType(0))) {
        // use the valueOf of the CharSequence, which will be its ToString-method, or
        // in case we know the argument is a StringBuffer/String/Array, we can use its language
        // directly
        Variable value = valueOf(expr.getArgBox(0), arguments.get(0), 10, method.getParameterType(0), factory);
        factory.addStatement(new StringBufferInit(callee, value));
        return true;
      }
    }
    // known collection type
    else if (trustedCollections.contains(declaringClass.getName())) {
      if (expr.getArgCount() == 0) {
        factory.addStatement(new ArrayNew(callee));
        return true;
      }
      //TODO more constructors
    }
   
   
    return false;
  }
 
  /**
   * Returns a variable with the possible strings returned by <tt>valueOf</tt> with the specified argument.
   * @param box the jimple-expression containing the argument.
   * @param argument variable holding the argument (it has already been evaluated).
   * @param radix for integers, this is the base of the number system. Unused for other types.
   * @param type type of the formal parameter. This identifies which overload of <tt>valueOf</tt> is being called.
   * @return a variable holding the possible return values
   */
  private Variable valueOf(ValueBox box, Variable argument, int radix, Type type, IntermediateFactory factory) {
        // determine whether the value might be null
        boolean canBeNull = false;
       
        Value val = box.getValue();
       
        // only referene-types can be null
        // we only consider locals, because only locals and constants may occur,
        //     and null-constants are treated by valueOfNonNull (ironically)
        // do not consider strings here, since we assign them to the string "null" instead of null
        // note: use RefLikeType to include arrays -- RefType only includes declared types
        if (val instanceof Local && val.getType() instanceof RefLikeType && !(isString(val.getType()))) {
          canBeNull = factory.canBeNull((Local)val);
        }
       
        if (canBeNull) {
          Variable resultVar = factory.createVariable(VariableType.STRING);
         
          // if the variable might be null, split the graph in two, one for the case
          // where it is non-null, and one where it is null.
          // both branches should assign to 'resultVar' so we can return a variable
          factory.startBranch();
         
          // not null
          Variable notNull = valueOfNonNull(box, argument, radix, type, factory);
          factory.addStatement(new StringAssignment(resultVar, notNull));
          factory.useBranch();
         
          // null
          factory.addStatement(new StringInit(resultVar, Automatons.getNull()));
          factory.useBranch();
         
          factory.endBranch();
         
          return resultVar;
        } else {
          return valueOfNonNull(box, argument, radix, type, factory);
        }
  }
 
  /**
   * Like {@link #valueOf}, except this assumes the argument is not a variable with value <tt>null</tt>, at runtime.
   * The argument may, however, be a <tt>null</tt>-constant.
   */
    private Variable valueOfNonNull(ValueBox box, Variable argument, int radix, Type type, IntermediateFactory factory) {
        // don't create a new variable if the type is STRING
        if (argument.getType() == VariableType.STRING)
          return argument;
       
        Value val = box.getValue();
        Variable result = factory.createVariable(VariableType.STRING);
        switch (argument.getType()) {
        case STRINGBUFFER:
          if (isStringBuffer(val.getType()) || isStringBuilder(val.getType())) {
            factory.addStatement(new StringFromStringBuffer(result, argument));
          } else {
            // if the type implements Appendable, we can't really know its value
            factory.addStatement(new StringInit(result, Basic.makeAnyString()));
          }
          break;
         
        case ARRAY:
          if (val.getType() instanceof ArrayType) {  // the argument's type only says it *might* be an array
            // TODO because of covariant arrays, we might not actually know it is a String array??
            factory.addStatement(new StringInit(result, ARRAY_OBJECT_AUTOMATON));
          } else {
            factory.addStatement(new StringInit(result, Basic.makeAnyString()));
          }
          break;
         
        case OBJECT:
          Automaton auto = Basic.makeAnyString();
          if (val.getType() instanceof RefType) {
            Automaton resolved = factory.resolveToStringMethod(((RefType)val.getType()).getSootClass());
            if (resolved != null)
              auto = resolved;
          }
          factory.addStatement(new StringInit(result, auto));
          break;
         
        case NULL:
          factory.addStatement(new StringInit(result, Automatons.getNull()));
          break;
         
        case NONE:
            if (val instanceof Constant) {
              String s = constantToString((Constant) val, radix, type);
              if (s != null)
                factory.addStatement(new StringInit(result, Basic.makeConstString(s)));
              else
                factory.addStatement(new StringInit(result, Basic.makeAnyString()));
               
            } else if (radix == 10 && Automatons.fromType(type.toString()) != null) { // TODO: handle radix!=10
             
              factory.addStatement(new StringInit(result, Automatons.fromType(type.toString())));
             
            } else if (val.getType() instanceof RefType) {
                // Call the corresponding toString method
                Method tostring_method = factory.getToStringMethod(((RefType) val.getType()).getSootClass());
                if (tostring_method != null && !isInterface(val.getType())) {
                  factory.addStatement(new Call(result, tostring_method, new Variable[0]));
                } else {
                  factory.addStatement(new StringInit(result, Basic.makeAnyString()));
                }
            } else { // If all else fails, give any string
              factory.addStatement(new StringInit(result, Basic.makeAnyString())); // not currently reachable, but good to have here
            }
           
            break;
           
        case PRIMITIVE:
            // TODO: If type is another primitive type, add a UnaryOp to extract this primitive type as a boolean, int, etc.
            if (val instanceof Constant) {
                // NOTE: This for char-variables, this does the same as the if-clause below, but is a bit more efficient
                factory.addStatement(new StringInit(result, Basic.makeConstString(constantToString((Constant) val, radix, type))));
               
            } else if (type.equals(CharType.v())) {
                // create a string buffer, append the char, convert it to a string and return that
                Variable tmp = factory.createVariable(VariableType.STRINGBUFFER);
                factory.addStatement(new StringBufferInit(tmp, makeStringConstant("", factory)));
                factory.addStatement(new StringBufferAppendChar(tmp, argument));
                factory.addStatement(new StringFromStringBuffer(result, tmp));
           
            } else if (type.equals(BooleanType.v())) {
                // use a BooleanToString operation
                factory.addStatement(new BasicUnaryOp(result, argument, new BooleanToString()));
               
            } else if (radix == 10 && Automatons.fromType(type.toString()) != null) { // TODO: handle radix!=10
                // unknown primitive type. use the known automaton for this type
                factory.addStatement(new StringInit(result, Automatons.fromType(type.toString())));
               
            } else {
                factory.addStatement(new StringInit(result, Basic.makeAnyString())); // not currently reachable, but good to have here
            }
            break;
           
        default:
          throw new RuntimeException("Unknown variable type: " + argument.getType());
        } // end switch
       
        return result;
    }
 
    /**
     * Returns whether the specified type is an interface type.
     */
    private boolean isInterface(Type type) {
      if (type instanceof RefType) {
        return ((RefType)type).getSootClass().isInterface();
      }
    return false;
  }
   
    /**
     * Converts a constant to a string, according to the <tt>toString</tt> method of
     * the type's wrapper object. For example, <tt>Integer.toString</tt> is used to convert
     * integers.
     * @param c the constant to convert to a string
     * @param radix for integers, this is the base of the number system. Unused for other types.
     * @param type the static type of the constant. This may differ from <tt>c</tt>'s type;
     *       for example because booleans are treated as integer constants.
     * @return result of <tt>toString</tt> of the specified constant.
     */
  private String constantToString(Constant c, int radix, Type type) {
      String s;
      if (c instanceof soot.jimple.NullConstant) {
            s = "null";
           
        } else if (c instanceof soot.jimple.IntConstant) {
            int value = ((soot.jimple.IntConstant) c).value;
           
            // booleans and chars are treated as integer constants, so we must check
            // which overload of valueOf was invoked, to see how it is converted to a string
            if (type instanceof BooleanType)
              s = (value == 0) ? "false":"true";
            else if (type instanceof CharType)
              s = "" + (char)value;
            else
              s = Integer.toString(value, radix);
           
        } else if (c instanceof soot.jimple.LongConstant) {
            long value = ((soot.jimple.LongConstant) c).value;
            s = Long.toString(value, radix);
           
        } else if (c instanceof soot.jimple.DoubleConstant) {
            double value = ((soot.jimple.DoubleConstant) c).value;
            s = Double.toString(value);
           
        } else if (c instanceof soot.jimple.FloatConstant) {
            float value = ((soot.jimple.FloatConstant) c).value;
            s = Float.toString(value);
           
        } else if (c instanceof soot.jimple.StringConstant) {
            s = ((soot.jimple.StringConstant) c).value;
           
        } else if (c instanceof soot.jimple.ClassConstant) {
          return null; // simulating this takes a lot of code
          // this currently works, but gives the <any string> language
          // TODO class constants
        } else {
            throw new RuntimeException("unexpected Soot constant kind");
        }
        return s;
    }

  /**
     * Checks whether the given type is <code>java.lang.CharSequence</code>.
     */
    private boolean isCharSequence(Type t) {
        return t.equals(RefType.v("java.lang.CharSequence"));
    }
   
//  /**
//     * Checks whether the given class is <code>java.lang.CharSequence</code>.
//     */
//    private boolean isCharSequence(SootClass c) {
//        return c.getName().equals("java.lang.CharSequence");
//    }

    /**
     * Checks whether the given type is <code>String</code>.
     */
    private boolean isString(Type t) {
        return t.equals(RefType.v("java.lang.String"));
    }

    /**
     * Checks whether the given class is <code>String</code>.
     */
    private boolean isString(SootClass c) {
        return c.getName().equals("java.lang.String");
    }
   
    private boolean isBufferOrBuilder(Type t) {
      return isStringBuffer(t) || isStringBuilder(t);
    }

    private boolean isBufferOrBuilder(SootClass t) {
      return isStringBuffer(t) || isStringBuilder(t);
    }
   
//    private boolean isObjectClass(Type t) {
//      return t.equals(RefType.v("java.lang.Object"));
//    }

    private boolean isObjectClass(SootClass c) {
      return c.getName().equals("java.lang.Object");
    }
   
//    /**
//     * Checks whether the given type is <code>Collection</code>.
//     */
//    private boolean isCollection(Type t) {
//        return t.equals(RefType.v("java.util.Collection"));
//    }
//   
//    /**
//     * Checks whether the given type is <code>List</code>.
//     */
//    private boolean isList(Type t) {
//        return t.equals(RefType.v("java.util.List"));
//    }
//   
//    /**
//     * Checks whether the given type is <code>Set</code>.
//     */
//    private boolean isSet(Type t) {
//        return t.equals(RefType.v("java.util.Set"));
//    }
   
    /**
     * Checks whether the given type is <code>StringBuffer</code>.
     */
    private boolean isStringBuffer(Type t) {
        return t.equals(RefType.v("java.lang.StringBuffer"));
    }

    /**
     * Checks whether the given class is <code>StringBuffer</code>.
     */
    private boolean isStringBuffer(SootClass c) {
        return c.getName().equals("java.lang.StringBuffer");
    }

    /**
     * Checks whether the given type is <code>StringBuilder</code>.
     */
    private boolean isStringBuilder(Type t) {
        return t.equals(RefType.v("java.lang.StringBuilder"));
    }

    /**
     * Checks whether the given class is <code>StringBuilder</code>.
     */
    private boolean isStringBuilder(SootClass c) {
        return c.getName().equals("java.lang.StringBuilder");
    }
   
//    /**
//     * Checks whether the given class is <code>Appendable</code>.
//     */
//    private boolean isAppendable(SootClass c) {
//      return c.getName().equals("java.lang.Appendable");
//    }

    /**
     * Checks whether the given type is <code>int</code>.
     */
    private boolean isInt(Type t) {
        return t.equals(IntType.v());
    }

    /**
     * Checks whether the given type is <code>char</code>.
     */
    private boolean isChar(Type t) {
        return t.equals(CharType.v());
    }

    /**
     * Checks whether the given class is a standard wrapper class.
     */
    private boolean isWrapperClass(SootClass c) {
        return c.getName().equals("java.lang.Boolean")
                || c.getName().equals("java.lang.Byte")
                || c.getName().equals("java.lang.Character")
                || c.getName().equals("java.lang.Double")
                || c.getName().equals("java.lang.Float")
                || c.getName().equals("java.lang.Integer")
                || c.getName().equals("java.lang.Long")
                || c.getName().equals("java.lang.Short");
    }
 

    /**
     * Attempts to find a constant string value. Returns null if unable to determine constant
     */
    private String trackString(Value val) {
        if (val instanceof StringConstant) {
            return ((StringConstant) val).value;
        }
        return null;
    }

    /**
     * Attempts to find constant integer (or char) value.
     * Returns null if unable to determine constant.
     */
    private Integer trackInteger(Value val) {
        if (val instanceof IntConstant) {
            return ((IntConstant) val).value;
        }
        // TODO: make some more intelligent tracking of integers
        return null;
    }
 

    /**
     * Creates a clone of the stringbuffer in the specified variable, such that the
     * two variables are <i>not</i> aliases, and the clone may be modified without
     * polluting the original stringbuffer.
     * @param buffer variable holding the stringbuffer to clone
     * @return a new variable holding a new stringbuffer
     */
    private Variable makeStringBufferClone(Variable buffer, IntermediateFactory factory) {
      if (buffer == null || buffer.getType() != VariableType.STRINGBUFFER) {
        throw new IllegalArgumentException("Can only clone STRINGBUFFER variables");
      }
      // get the current possible values of the buffer
      Variable currentValue = factory.createVariable(VariableType.STRING);
      factory.addStatement(new StringFromStringBuffer(currentValue, buffer));
     
      // create a new string buffer with this initial value
      Variable clone = factory.createVariable(VariableType.STRINGBUFFER);
      factory.addStatement(new StringBufferInit(clone, currentValue));
     
      return clone;
    }

    private Variable makeStringVariable(Automaton auto, IntermediateFactory factory) {
      Variable var = factory.createVariable(VariableType.STRING);
      factory.addStatement(new StringInit(var, auto));
      return var;
    }
    private Variable makeStringConstant(String value, IntermediateFactory factory) {
      Variable var = factory.createVariable(VariableType.STRING);
      factory.addStatement(new StringInit(var, Automaton.makeString(value)));
      return var;
    }
   
}
TOP

Related Classes of dk.brics.string.java.BuiltinMethodCallTranslator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.