//
// Copyright (C) 2006 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.vm;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import cmu.conditional.Conditional;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.JPFException;
import gov.nasa.jpf.annotation.MJI;
import gov.nasa.jpf.util.JPFLogger;
/**
* native peer classes are part of MJI and contain the code that is
* executed by the host VM (i.e. outside the state-tracked JPF VM). Each
* class executed by JPF that has native mehods must have a native peer class
* (which is looked up and associated at class loadtime)
*/
public class NativePeer implements Cloneable {
static final String MODEL_PACKAGE = "<model>";
static final String DEFAULT_PACKAGE = "<default>";
static JPFLogger logger = JPF.getLogger("class");
static ClassLoader loader;
static HashMap<ClassInfo, NativePeer> peers;
static Config config;
static boolean noOrphanMethods;
static String[] peerPackages;
ClassInfo ci;
Class<?> peerClass;
HashMap<String, Method> methods;
public static boolean init (Config conf) {
loader = conf.getClassLoader();
peers = new HashMap<>();
peerPackages = getPeerPackages(conf);
config = conf;
noOrphanMethods = conf.getBoolean("vm.no_orphan_methods", false);
return true;
}
static String[] getPeerPackages (Config conf) {
String[] defPeerPackages = { MODEL_PACKAGE, "gov.nasa.jpf.vm", DEFAULT_PACKAGE };
String[] packages = conf.getStringArray("peer_packages", defPeerPackages);
// internalize
for (int i=0; i<packages.length; i++) {
if (packages[i].equals(MODEL_PACKAGE)) {
packages[i] = MODEL_PACKAGE;
} else if (packages[i].equals(DEFAULT_PACKAGE)) {
packages[i] = DEFAULT_PACKAGE;
}
}
return packages;
}
static Class<?> locatePeerCls (String clsName) {
String cn = "JPF_" + clsName.replace('.', '_');
for (int i=0; i<peerPackages.length; i++) {
String pcn;
String pkg = peerPackages[i];
if (pkg == MODEL_PACKAGE) {
int j = clsName.lastIndexOf('.');
pcn = clsName.substring(0, j+1) + cn;
} else if (pkg == DEFAULT_PACKAGE) {
pcn = cn;
} else {
pcn = pkg + '.' + cn;
}
try {
Class<?> peerCls = loader.loadClass(pcn);
if ((peerCls.getModifiers() & Modifier.PUBLIC) == 0) {
logger.warning("non-public peer class: ", pcn);
continue; // pointless to use this one, it would just create IllegalAccessExceptions
}
logger.info("loaded peer class: ", pcn);
return peerCls;
} catch (ClassNotFoundException cnfx) {
// try next one
}
}
return null; // nothing found
}
/**
* this becomes the factory method to load either a plain (slow)
* reflection-based peer (a NativePeer object), or some speed optimized
* derived class object.
* Watch out - this gets called before the ClassInfo is fully initialized
* (we shouldn't rely on more than just its name here)
*/
static NativePeer getNativePeer (ClassInfo ci) {
String clsName = ci.getName();
NativePeer peer = peers.get(ci);
Class<?> peerCls = null;
if (peer == null) {
peerCls = locatePeerCls(clsName);
if (peerCls != null) {
initializePeerClass( peerCls);
if (logger.isLoggable(Level.INFO)) {
logger.info("load peer: ", peerCls.getName());
}
peer = getInstance(peerCls, NativePeer.class);
peer.initialize(peerCls, ci, true);
peers.put(ci, peer);
}
}
return peer;
}
public static <T> T getInstance(Class<?> cls, Class<T> type) throws JPFException {
Class<?>[] argTypes = Config.CONFIG_ARGTYPES;
Object[] args = config.CONFIG_ARGS;
return getInstance(cls, type, argTypes, args);
}
public static <T> T getInstance(Class<?> cls, Class<T> type, Class<?>[] argTypes,
Object[] args) throws JPFException {
Object o = null;
Constructor<?> ctor = null;
if (cls == null) {
return null;
}
while (o == null) {
try {
ctor = cls.getConstructor(argTypes);
o = ctor.newInstance(args);
} catch (NoSuchMethodException nmx) {
if ((argTypes.length > 1) || ((argTypes.length == 1) && (argTypes[0] != Config.class))) {
// fallback 1: try a single Config param
argTypes = Config.CONFIG_ARGTYPES;
args = config.CONFIG_ARGS;
} else if (argTypes.length > 0) {
// fallback 2: try the default ctor
argTypes = Config.NO_ARGTYPES;
args = Config.NO_ARGS;
} else {
// Ok, there is no suitable ctor, bail out
throw new JPFException("no suitable ctor found for the peer class " + cls.getName());
}
} catch (IllegalAccessException iacc) {
throw new JPFException("ctor not accessible: "
+ config.getMethodSignature(ctor));
} catch (IllegalArgumentException iarg) {
throw new JPFException("illegal constructor arguments: "
+ config.getMethodSignature(ctor));
} catch (InvocationTargetException ix) {
Throwable tx = ix.getCause();
throw new JPFException("exception " + tx + " occured in "
+ config.getMethodSignature(ctor));
} catch (InstantiationException ivt) {
throw new JPFException("abstract class cannot be instantiated");
} catch (ExceptionInInitializerError eie) {
throw new JPFException("static initialization failed:\n>> "
+ eie.getException(), eie.getException());
}
}
// check type
if (!cls.isInstance(o)) {
throw new JPFException("instance not of type: "
+ cls.getName());
}
return type.cast(o); // safe according to above
}
static String getPeerDispatcherClassName (String clsName) {
return (clsName + '$');
}
public Class<?> getPeerClass() {
return peerClass;
}
public String getPeerClassName() {
return peerClass.getName();
}
//@TODO: protected -> public
public void initialize (Class<?> peerClass, ClassInfo ci, boolean cacheMethods) {
if ((this.ci != null) || (this.peerClass != null)) {
throw new RuntimeException("cannot re-initialize NativePeer: " +
peerClass.getName());
}
this.ci = ci;
this.peerClass = peerClass;
loadMethods(cacheMethods);
}
protected static void initializePeerClass( Class<?> cls) {
try {
Method m = cls.getDeclaredMethod("init", Config.class );
try {
m.invoke(null, config);
} catch (IllegalArgumentException iax){
// can't happen - static method
} catch (IllegalAccessException iacx) {
throw new RuntimeException("peer initialization method not accessible: "
+ cls.getName());
} catch (InvocationTargetException itx){
throw new RuntimeException("initialization of peer " +
cls.getName() + " failed: " + itx.getCause());
}
} catch (NoSuchMethodException nsmx){
// nothing to do
}
}
private static boolean isMJICandidate (Method mth) {
// the native peer should be annotated with @MJI
if(!mth.isAnnotationPresent(MJI.class)) {
return false;
}
// this native peer should be Public
if(!Modifier.isPublic(mth.getModifiers())) {
return false;
}
// native method always have a MJIEnv and int as the first parameters
Class<?>[] argTypes = mth.getParameterTypes();
if ((argTypes.length >= 2) && (argTypes[0] == MJIEnv.class) && (argTypes[1] == int.class || argTypes[1] == Conditional.class) ) {
return true;
} else {
return false;
}
}
@SuppressWarnings("unused")
private Method getMethod (MethodInfo mi) {
return getMethod(null, mi);
}
private Method getMethod (String prefix, MethodInfo mi) {
String name = mi.getUniqueName();
if (prefix != null) {
name = prefix + name;
}
return methods.get(name);
}
/**
* look at all public static methods in the peer and set their
* corresponding model class MethodInfo attributes
* <2do> pcm - this is too long, break it down
*/
private void loadMethods (boolean cacheMethods) {
Method[] m = peerClass.getDeclaredMethods();
methods = new HashMap<String, Method>(m.length);
Map<String,MethodInfo> methodInfos = ci.getDeclaredMethods();
MethodInfo[] mis = null;
for (int i = 0; i < m.length; i++) {
Method mth = m[i];
if (isMJICandidate(mth)) {
// Note that we can't mangle the name automatically, since we loose the
// object type info (all mapped to int). This has to be handled
// the same way like with overloaded JNI methods - you have to
// mangle them manually
String mn = mth.getName();
// JNI doesn't allow <clinit> or <init> to be native, but MJI does
// (you should know what you are doing before you use that, really)
if (mn.startsWith("$clinit")) {
mn = "<clinit>";
} else if (mn.startsWith("$init")) {
mn = "<init>" + mn.substring(5);
}
String mname = Types.getJNIMethodName(mn);
String sig = Types.getJNISignature(mn);
if (sig != null) {
mname += sig;
}
// now try to find a corresponding MethodInfo object and mark it
// as 'peer-ed'
// <2do> in case of <clinit>, it wouldn't be strictly required to
// have a MethodInfo upfront (we could create it). Might be handy
// for classes where we intercept just a few methods, but need
// to init before
MethodInfo mi = methodInfos.get(mname);
if ((mi == null) && (sig == null)) {
// nothing found, we have to do it the hard way - check if there is
// a single method with this name (still unsafe, but JNI behavior)
// Note there's no point in doing that if we do have a signature
if (mis == null) { // cache it for subsequent lookup
mis = new MethodInfo[methodInfos.size()];
methodInfos.values().toArray(mis);
}
mi = searchMethod(mname, mis);
}
if (mi != null) {
logger.info("load MJI method: ", mname);
NativeMethodInfo miNative = new NativeMethodInfo(mi, mth, this);
miNative.replace(mi);
} else {
// we have an orphan method, i.e. a peer method that does not map into any model method
// (this is usually a signature typo or an out-of-sync peer)
String message = "orphan NativePeer method: " + ci.getName() + '.' + mname;
if (noOrphanMethods){
throw new JPFException(message);
}
// issue a warning if we have a NativePeer native method w/o a corresponding
// method in the model class (this might happen due to compiler optimizations
// silently skipping empty methods)
logger.warning(message);
}
}
}
}
private static MethodInfo searchMethod (String mname, MethodInfo[] methods) {
int idx = -1;
for (int j = 0; j < methods.length; j++) {
if (methods[j].getName().equals(mname)) {
// if this is actually a overloaded method, and the first one
// isn't the right choice, we would get an IllegalArgumentException,
// hence we have to go on and make sure it's not overloaded
if (idx == -1) {
idx = j;
} else {
throw new JPFException("overloaded native method without signature: " + mname);
}
}
}
if (idx >= 0) {
return methods[idx];
} else {
return null;
}
}
}