/*
* Copyright (c) 2008-2013, Matthias Mann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Matthias Mann nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package nginx.clojure.wave;
import nginx.clojure.asm.Opcodes;
import nginx.clojure.asm.Type;
import nginx.clojure.asm.tree.AbstractInsnNode;
import nginx.clojure.asm.tree.TypeInsnNode;
import nginx.clojure.asm.tree.analysis.AnalyzerException;
import nginx.clojure.asm.tree.analysis.BasicInterpreter;
import nginx.clojure.asm.tree.analysis.BasicValue;
import nginx.clojure.asm.tree.analysis.SimpleVerifier;
/**
* An extension to {@link BasicInterpreter} which collects the type of
* objects and arrays.
*
* @author Matthias Mann
*/
public class TypeInterpreter extends SimpleVerifier {
private final MethodDatabase db;
public TypeInterpreter(MethodDatabase db) {
this.db = db;
}
@Override
public BasicValue newValue(Type type) {
if(type == null) {
return BasicValue.UNINITIALIZED_VALUE;
}
if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return new BasicValue(type);
}
return super.newValue(type);
}
@Override
public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
if(insn.getOpcode() == Opcodes.NEW) {
return new NewValue(Type.getObjectType(((TypeInsnNode) insn).desc), false, insn);
}
return super.newOperation(insn);
}
@Override
public BasicValue copyOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException {
if(insn.getOpcode() == Opcodes.DUP) {
if(value instanceof NewValue) {
NewValue newValue = (NewValue)value;
if(!newValue.isDupped) {
return new NewValue(newValue.getType(), true, insn);
}
}
}
return super.copyOperation(insn, value);
}
@Override
public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException {
if(insn.getOpcode() == Opcodes.AALOAD) {
Type t1 = value1.getType();
if(t1 == null || t1.getSort() != Type.ARRAY) {
throw new AnalyzerException(insn, "AALOAD needs an array as first parameter");
}
Type resultType = Type.getType(t1.getDescriptor().substring(1));
return new BasicValue(resultType);
}
return super.binaryOperation(insn, value1, value2);
}
// @Override
// public BasicValue merge(BasicValue v, BasicValue w) {
// if (!v.equals(w)) {
// if(v.isReference() && w.isReference()) {
// int dimensions = 0;
// Type typeV = v.getType();
// Type typeW = w.getType();
// if(typeV.getSort() != typeW.getSort()) {
// db.trace("Array and none array type can't be merged: %s %s", v, w);
// return BasicValue.UNINITIALIZED_VALUE;
// }
// if(typeW.getSort() == Type.ARRAY) {
// dimensions = typeV.getDimensions();
// if(dimensions != typeW.getDimensions()) {
// db.trace("Arrays with different dimensions can't be merged: %s %s", v, w);
// return BasicValue.UNINITIALIZED_VALUE;
// }
// typeV = typeV.getElementType();
// typeW = typeW.getElementType();
// if(typeV.getSort() != Type.OBJECT || typeW.getSort() != Type.OBJECT) {
// db.trace("Arrays of different primitive type can't be merged: %s %s", v, w);
// return BasicValue.UNINITIALIZED_VALUE;
// }
// }
// String internalV = typeV.getInternalName();
// String internalW = typeW.getInternalName();
// if("null".equals(internalV)) {
// return w;
// }
// if("null".equals(internalW)) {
// return v;
// }
// String superClass = db.getCommonSuperClass(internalV, internalW);
// if(superClass == null) {
// if(db.isException(internalW)) {
// db.warn("Could not determine super class for v=%s w=%s - decided to use exception %s", v, w, w);
// return w;
// }
// if(db.isException(internalV)) {
// db.warn("Could not determine super class for v=%s w=%s - decided to use exception %s", v, w, v);
// return v;
// }
// db.warn("Could not determine super class for v=%s w=%s - decided to use java/lang/Object", v, w);
// superClass = "java/lang/Object";
// }
// String typeDescriptor = makeTypeDescriptor(superClass, dimensions);
// db.trace("Common super class for v=%s w=%s is %s", v, w, typeDescriptor);
// return new BasicValue(Type.getType(typeDescriptor));
// }
// return BasicValue.UNINITIALIZED_VALUE;
// }
// return v;
// }
private static String makeTypeDescriptor(String className, int dimensions) {
int len = className.length();
char[] tmp = new char[len + 2 + dimensions];
for(int i=0 ; i<dimensions ; i++) {
tmp[i] = '[';
}
tmp[dimensions] = 'L';
className.getChars(0, len, tmp, dimensions+1);
tmp[dimensions+1+len] = ';';
return new String(tmp);
}
@Override
protected Type getSuperClass(Type t) {
return Type.getObjectType(db.getDirectSuperClass(t.getInternalName()));
}
@Override
protected boolean isInterface(Type t) {
return db.isInterface(t.getInternalName());
}
@Override
protected boolean isAssignableFrom(Type t, Type u) {
try {
int us = u.getSort();
int ts = t.getSort();
switch (ts) {
case Type.BOOLEAN:
return us == Type.BOOLEAN;
case Type.BYTE:
return us == Type.BYTE;
case Type.CHAR:
return us == Type.BYTE || us == Type.CHAR;
case Type.SHORT:
return us == Type.BYTE || us == Type.CHAR || us == Type.SHORT;
case Type.DOUBLE:
return us == Type.BYTE || us == Type.CHAR
|| us == Type.INT || us == Type.FLOAT || us == Type.DOUBLE;
case Type.FLOAT:
return us == Type.BYTE || us == Type.CHAR
|| us == Type.INT || us == Type.FLOAT;
case Type.INT:
return us == Type.BYTE || us == Type.CHAR
|| us == Type.INT;
case Type.LONG:
return us == Type.BYTE || us == Type.CHAR
|| us == Type.INT || us == Type.LONG;
}
switch (us) {
case Type.BOOLEAN:
case Type.BYTE:
case Type.CHAR:
case Type.SHORT:
case Type.DOUBLE:
case Type.FLOAT:
case Type.INT:
case Type.LONG:
return false;
}
String tn = t.getInternalName();
if (tn != null && tn.equals("java/lang/Object")) {
return true;
}
if (us == Type.ARRAY) {
if ( ts == Type.ARRAY ) {
if (t.getDimensions() == u.getDimensions() && isAssignableFrom(t.getElementType(), u.getElementType())) {
return true;
}else if (t.getDimensions() < u.getDimensions()) {
Type ti = t.getElementType();
String tin = ti.getInternalName();
if (ti.getSort() == Type.OBJECT && ("java/lang/Object".equals(tin) || ("java/lang/Cloneable".equals(tin) || "java/io/Serializable".equals(tin) ))) {
return true;
}
}
return false;
}else if (ts == Type.OBJECT && ("java/lang/Cloneable".equals(tn) || "java/io/Serializable".equals(tn) )){
return true;
}
}
// System.out.println("isAssignableFrom: " + t.getInternalName() +
// "/" + u.getInternalName());
boolean b = t.getInternalName().equals(u.getInternalName())
|| db.getCommonSuperClass(u.getInternalName(),
t.getInternalName()).equals(t.getInternalName());
// boolean ob = getClass(t).isAssignableFrom(getClass(u));
// if (b != ob) {
// throw new RuntimeException("isAssignableFrom : " + b + "!=" + ob
// + ", getCommonSuperClass=" +
// db.getCommonSuperClass(u.getInternalName(), t.getInternalName())
// + " t :" + t.getInternalName() + ", u:" + u.getInternalName());
// }
return b;
} catch (Throwable e) {
db.error("isAssignableFrom error t=" + t + ", u=" + u, e);
return false;
}
}
public boolean checkAssignableFrom(Type t, Type u) {
return isAssignableFrom(t, u);
}
public Type fetchSuperClass(Type t) {
return getSuperClass(t);
}
}