/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com)
* Copyright (C) 2010, Stefan Hepp (stefan@stefant.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.common.code;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.MethodCode;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.graphutils.Pair;
import com.jopdesign.common.misc.MethodNotFoundException;
import com.jopdesign.common.type.MemberID;
import org.apache.bcel.generic.InstructionHandle;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Callstrings for a context sensitive analysis.
* Note that callstrings are immutable objects.
*
* @author Benedikt Huber <benedikt.huber@gmail.com>
* @author Stefan Hepp <stefan@stefant.org>
*/
public class CallString implements CallStringProvider, Iterable<InvokeSite> {
public static class CallStringSerialization implements Serializable {
private static final long serialVersionUID = 1L;
private List<Pair<String,Integer>> sites = new ArrayList<Pair<String,Integer>>();
public CallStringSerialization(CallString cs) {
for(InvokeSite site : cs.getInvokeSiteList()) {
String method = site.getInvoker().getFQMethodName();
int pos = site.getInstructionHandle().getPosition();
this.sites.add(new Pair<String,Integer>(method,pos));
}
}
public CallString getCallString(AppInfo appInfo) throws MethodNotFoundException {
List<InvokeSite> invokeSiteList = new ArrayList<InvokeSite>();
for(Pair<String,Integer> invokeSiteSpec : sites) {
String methodID = invokeSiteSpec.first();
Integer pos = invokeSiteSpec.second();
MethodInfo method = appInfo.getMethodInfo(MemberID.parse(methodID));
InstructionHandle ih = method.getCode().getInstructionList().findHandle(pos);
InvokeSite site = method.getCode().getInvokeSite(ih);
invokeSiteList.add(site);
}
return CallString.fromInvokeSiteList(invokeSiteList);
}
public String toString() {
return this.sites.toString();
}
}
private final InvokeSite[] callString;
private final int hash;
public static final CallString EMPTY = new CallString() {
@SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"})
public boolean equals(Object obj) { return obj == this; }
public int hashCode() { return 1; }
};
/**
* Create a new callstring with one entry.
* If you want a new, empty CallString, use {@link #EMPTY}.
*
* @param site the invokesite to push to the callstring.
*/
public CallString(InvokeSite site) {
callString = new InvokeSite[]{site};
hash = site.hashCode();
}
public CallString(List<InvokeSite> sites) {
this(sites.toArray(new InvokeSite[sites.size()]));
}
private CallString() {
callString = new InvokeSite[]{};
hash = 1;
}
private CallString(InvokeSite[] cs) {
assert cs != null;
callString = cs;
int hash = 0;
for (InvokeSite invokeSite : callString) {
hash = hash * 31 + invokeSite.hashCode();
}
this.hash = hash;
}
public CallString getCallString() {
return this;
}
@Override
public Iterator<InvokeSite> iterator() {
return new Iterator<InvokeSite>() {
private int pos = 0;
@Override
public boolean hasNext() {
return pos < callString.length;
}
@Override
public InvokeSite next() {
return callString[pos++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public boolean contains(MethodInfo invoker) {
for (InvokeSite site : callString) {
if (site.getInvoker().equals(invoker)) return true;
}
return false;
}
public boolean contains(InvokeSite invokeSite) {
for (InvokeSite site : callString) {
if (site.equals(invokeSite)) return true;
}
return false;
}
public CallString push(InvokeSite invokeSite) {
return push(invokeSite, length()+1);
}
/**
* Return a new callstring, extended by the given invoke site.
* <p>Let {@code n(1)} be the id of the given invoke site, and
* {@code n(2),...,n(k)} be the callstring represented by {@code this}
* <ol><li/>If k <= maxDepth, the resulting callstring is {@code n(1),n(2),...,n(k)}
* <li/>If k >= maxDepth, the resulting callstring is {@code n(1),n(2),...,n(maxDepth)}
* </ol>
*
* @param invokeNode a CFG InvokeNode representing the invocation
* @param maxLen the maximum length of the callstring
* @return a new callstring with the given invocation as the last element.
*/
public CallString push(ControlFlowGraph.InvokeNode invokeNode , int maxLen) {
return push(invokeNode.getBasicBlock().getMethodInfo(),invokeNode.getInstructionHandle(),maxLen);
}
/**
* Return a new callstring, extended by the given invoke site.
* <p>Let {@code n(1)} be the id of the given invoke site, and
* {@code n(2),...,n(k)} be the callstring represented by {@code this}
* <ol><li/>If k <= maxDepth, the resulting callstring is {@code n(1),n(2),...,n(k)}
* <li/>If k >= maxDepth, the resulting callstring is {@code n(1),n(2),...,n(maxDepth)}
* </ol>
*
* TODO: Code duplication with DFA/LoopBounds.java ?
*
* @param method the method containing the invocation
* @param invoke the invocation instruction
* @param maxLen the maximum length of the callstring
* @return a new callstring with the given invocation as the last element.
*/
public CallString push(MethodInfo method, InstructionHandle invoke, int maxLen) {
return push(method.getCode().getInvokeSite(invoke), maxLen);
}
/**
* Return a new callstring, extended by the given invoke site.
* <p>Let {@code n(1)} be the id of the given invoke site, and
* {@code n(2),...,n(k)} be the callstring represented by {@code this}
* <ol><li/>If k <= maxDepth, the resulting callstring is {@code n(1),n(2),...,n(k)}
* <li/>If k >= maxDepth, the resulting callstring is {@code n(1),n(2),...,n(maxDepth)}
* </ol>
*
* @see MethodCode#getInvokeSite(InstructionHandle)
* @param is the invokesite to be pushed at the end of the string.
* @param maxLen the maximum length of the callstring
* @return a new callstring with the given invocation as the last element.
*/
public CallString push(InvokeSite is, int maxLen) {
if (maxLen <= 0) return EMPTY;
// Workaround for hanging copyOfRange() if this is the empty callstring
if (callString.length == 0) {
// We know that maxLen is > 0 here
return new CallString(is);
}
// shallow clone, new length is k
int k = Math.min(callString.length + 1, maxLen);
int end = callString.length + 1;
InvokeSite[] cs = Arrays.copyOfRange(callString, end - k, end);
cs[k - 1] = is;
return new CallString(cs);
}
public CallString pop() {
if (callString.length < 2) {
return EMPTY;
}
InvokeSite[] cs = Arrays.copyOfRange(callString, 0, callString.length-1);
return new CallString(cs);
}
/**
* @return the execution context containing the top invoke
*/
public ExecutionContext getExecutionContext() {
if (callString.length == 0) return null;
return new ExecutionContext(top().getInvoker(), pop());
}
public int length() {
return callString.length;
}
/**
* @return the first invokesite in this callstring.
*/
public InvokeSite first() {
if (callString.length > 0) {
return callString[0];
} else {
return null;
}
}
/**
* Return the most previously pushed InvokeSite.
* @return the last InvokeSite in this string, or null if this is the empty callstring.
*/
public InvokeSite top() {
if (callString.length > 0) {
return callString[callString.length-1];
} else {
return null;
}
}
public InvokeSite get(int pos) {
return callString[pos];
}
/**
* @param length must be between 0 and {@link #length()} inclusive.
* @return return a callstring with the given length containing the {@code length} most recently pushed items.
*/
public CallString getSuffix(int length) {
if (length == 0) return EMPTY;
if (length > callString.length) {
throw new IllegalArgumentException("Trying to get suffix with length "+length+
" greater than callstring length "+callString.length);
}
if (length == callString.length) return this;
InvokeSite[] cs = Arrays.copyOfRange(callString, callString.length - length, callString.length);
return new CallString(cs);
}
/**
* Check if either this callstring or the given callstring is a suffix of the other one.
* @param cs callstring to compare to.
* @return true if they are equal or one of them is a suffix of the other.
*/
public boolean matches(CallString cs) {
// empty callstring always matches
if (length() == 0 || cs.length() == 0) return true;
return hasSuffix(cs) || cs.hasSuffix(this);
}
@Override
public int hashCode() {
return hash;
}
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
InvokeSite[] other = ((CallString) obj).callString;
// This is slightly faster than Arrays.equals()
int len = callString.length;
if (len != other.length) return false;
for (int i = 0; i < len; i++) {
if (!callString[i].equals(other[i])) return false;
}
return true;
}
/**
* Return true if {@code cs} is a suffix of callstring, that is,
* {@code this = prefix + cs} for some {@code prefix}.
*
* @param cs the suffix to check
* @return true if this callstring ends with the given callstring
*/
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
public boolean hasSuffix(CallString cs) {
if (this.equals(cs) || cs.isEmpty()) {
return true;
}
if (callString.length < cs.callString.length) {
return false;
}
int suffixStart = callString.length - cs.callString.length;
for (int i = 0; i < cs.callString.length; i++) {
if (!callString[suffixStart + i].equals(cs.callString[i])) {
return false;
}
}
return true;
}
public boolean isEmpty() {
return callString.length == 0;
}
@Override
public String toString() {
if (this.isEmpty()) return "CallString.EMPTY";
long hash = hashCode();
if (hash < 0) hash += Integer.MAX_VALUE;
//if (callString.length == 1) {
// return String.format("CallString[%s|%x]", callString[0].toString().substring(arg0, arg1), hash);
//} else {
return String.format("CallString[|%d|%x]", callString.length, hash);
//}
}
public String toStringVerbose(boolean newlines) {
if (this.isEmpty()) return "CallString.Empty";
StringBuffer sb = new StringBuffer("CallString[");
boolean first = true;
for (int i = callString.length-1; i >= 0; i--) {
if (first) first = false;
else if (newlines) sb.append("\n ");
else sb.append(";");
sb.append(callString[i].toString());
}
sb.append("]");
return sb.toString();
}
/**
* Create a list of all invoke sites as strings.
* Note that this representation can change, or it might even be incorrect!
*
* @return a list of invoke site string representations.
* @see #toString()
*/
public List<String> toStringList() {
LinkedList<String> cs = new LinkedList<String>();
for (InvokeSite cse : callString) {
cs.add(cse.toString());
}
return cs;
}
/* for serialization */
private InvokeSite[] getInvokeSiteList() {
return this.callString;
}
public List<InvokeSite> getInvokeSites() {
return Collections.unmodifiableList(Arrays.asList(callString));
}
public static CallString fromInvokeSiteList(List<InvokeSite> invokeSiteList) {
InvokeSite[] sites = new InvokeSite[invokeSiteList.size()];
invokeSiteList.toArray(sites);
return new CallString(Arrays.copyOf(sites, sites.length));
}
}