package nhandler.conversion.jvm2jpf; import java.util.Iterator; import java.util.function.BiConsumer; import cmu.conditional.Conditional; import cmu.conditional.One; import de.fosd.typechef.featureexpr.FeatureExpr; import gov.nasa.jpf.vm.ArrayFields; import gov.nasa.jpf.vm.ClassInfo; import gov.nasa.jpf.vm.ClassInfoException; import gov.nasa.jpf.vm.ClassLoaderInfo; import gov.nasa.jpf.vm.DynamicElementInfo; import gov.nasa.jpf.vm.ElementInfo; import gov.nasa.jpf.vm.MJIEnv; import gov.nasa.jpf.vm.StaticElementInfo; import nhandler.conversion.ConversionException; import nhandler.conversion.ConverterBase; /** * This class is used to converter objects and classes from JVM to JPF * * @author Nastaran Shafiei */ public abstract class JVM2JPFConverter extends ConverterBase { public static ClassInfo obtainJPFCls(Class<?> JVMCls, MJIEnv env, FeatureExpr ctx) throws ConversionException { if (JVMCls == null) { return null; } JVM2JPFConverter converter = ConverterBase.converterFactory.getJVM2JPFConverter(JVMCls.getName()); return converter.getJPFCls(JVMCls, env, ctx); } public static int obtainJPFObj(Object JVMObj, MJIEnv env, FeatureExpr ctx) throws ConversionException { if (JVMObj == null) { return MJIEnv.NULL; } JVM2JPFConverter converter = ConverterBase.converterFactory.getJVM2JPFConverter(JVMObj.getClass().getName()); return converter.getJPFObj(JVMObj, env, ctx); } public static void updateJPFObj(Object JVMObj, int JPFObj, MJIEnv env, FeatureExpr ctx) throws ConversionException { if (JVMObj == null) { return; } JVM2JPFConverter converter = ConverterBase.converterFactory.getJVM2JPFConverter(JVMObj.getClass().getName()); converter.getUpdatedJPFObj(JVMObj, JPFObj, env, ctx); } /** * Returns a JPF class corresponding to the given JVM Class object. If such * an class exists, it is updated (if it has not been updated) corresponding * to the given JVMObj. Otherwise a new JPF class corresponding to the given * JVM class object is created and added to the list of the JPF loaded * classes. * * @param JVMCls * a JVM Class object * * @return a JPF class corresponding to the given JVM class, JVMCls * * @throws ConversionException * if any incorrect input parameter is observed */ protected ClassInfo getJPFCls(Class<?> JVMCls, MJIEnv env, FeatureExpr ctx) throws ConversionException { ClassInfo JPFCls = null; int JPFClsRef = Integer.MIN_VALUE; if (JVMCls != null) { // retrieving the integer representing the Class in JPF JPFCls = ClassLoaderInfo.getCurrentResolvedClassInfo(JVMCls.getName()); // First check if the class has been already updated if (!ConverterBase.updatedJPFCls.contains(JPFClsRef)) { StaticElementInfo sei = null; /** * If the corresponding ClassInfo does not exist, a new * ClassInfo object is created and will be added to the * loadedClasses. */ if (!JPFCls.isRegistered()) { JPFCls.registerClass(ctx, env.getThreadInfo()); sei = JPFCls.getStaticElementInfo(); JPFClsRef = sei.getObjectRef(); } else { sei = JPFCls.getModifiableStaticElementInfo(); } // This is to avoid JPF to initialized the class JPFCls.setInitialized(); ConverterBase.updatedJPFCls.add(JPFClsRef); setStaticFields(JVMCls, sei, env, ctx); } } return JPFCls; } /** * Sets the static fields of a JPF class corresponding to the given JVM * class * * @param JVMCls * a class object in JVM * @param sei * captures the value of static fields * @throws ConversionException */ protected abstract void setStaticFields(Class<?> JVMCls, StaticElementInfo sei, MJIEnv env, FeatureExpr ctx) throws ConversionException; /** * Returns a JPF object corresponding to the given JVM object. If such an * object exists, it is updated (if it has not been updated) corresponding * to the given JVMObj. Otherwise a new JPF object corresponding to the * given JVM object is created. * * @param JVMObj * a JVM object * * @return a JPF object corresponding to the given JVM object, JVMObj * * @throws ConversionException * if any incorrect input parameter is observed */ protected int getJPFObj(Object JVMObj, MJIEnv env, FeatureExpr ctx) throws ConversionException { int JPFRef = MJIEnv.NULL; if (JVMObj != null) { if (JVMObj.getClass() == Class.class) { JPFRef = (this.getJPFCls((Class<?>) JVMObj, env, ctx)).getClassObjectRef(); } else { JPFRef = this.getExistingJPFRef(JVMObj, true, env, ctx); if (JPFRef == MJIEnv.NULL) { JPFRef = this.getNewJPFRef(JVMObj, env, ctx); } } } return JPFRef; } /** * Updates the given JPF object, according to the given JVM object. For the * case of the non-array object, its JPF class is also updated according to * the class of the given JVM object. * * @param JVMObj * a JVM object * @param JPFObj * a JPF object * * @throws ConversionException * if any incorrect input parameter is observed */ protected void getUpdatedJPFObj(Object JVMObj, int JPFObj, MJIEnv env, FeatureExpr ctx) throws ConversionException { // if (this.isValidJPFRef(JVMObj, JPFObj)){ // Both JVM and JPF objects are null, No need for updating if (JVMObj == null) { return; } if (JVMObj.getClass().isArray()) { this.updateJPFArrObj(JVMObj, JPFObj, env, ctx); } else { this.updateJPFNonArrObj(JVMObj, JPFObj, env, ctx); } // } else{ // throw new ConversionException("The given JPFObj is not valid!"); // } } /** * Checks if the given JPF reference is valid, meaning that either the JPF * reference represents the same object as the given JVM object. This method * should be always invoked before updating the given JPF object * corresponding to the given JVM object. * * @param JVMObj * a JVM object * @param JPFObj * a JPF object * @return true if the given JPFObj represents the same object as JPFObj, OW * false is returned. * * @throws ConversionException * if any incorrect input parameter is observed */ // todo - investigate this using Socket.jpf example protected boolean isValidJPFRef(Object JVMObj, int JPFObj, MJIEnv env, FeatureExpr ctx) throws ConversionException { boolean isValid; if (!ConverterBase.isResetState()) { return true; } if (JPFObj == MJIEnv.NULL || JVMObj == null) { isValid = (JPFObj == MJIEnv.NULL && JVMObj == null); } else if (JVMObj.getClass() == Class.class) { isValid = true; } else { int existingJPFObj = this.getExistingJPFRef(JVMObj, false, env, ctx); isValid = (existingJPFObj == JPFObj);// || existingJPFObj == // MJIEnv.NULL); } return isValid; } /** * Updates the given non-array JPF object according to the given non-array * JVM object. The class of the object is also updated according to the * class of the given JVM object. * * @param JVMObj * a non-array JVM object * @param JPFObj * a non-array JPF object * * @throws ConversionException * if any incorrect input parameter is observed */ protected void updateJPFNonArrObj(Object JVMObj, int JPFObj, MJIEnv env, FeatureExpr ctx) throws ConversionException { if (JVMObj != null) { // First check if the JPF object has been already updated if (!ConverterBase.updatedJPFObj.containsKey(JPFObj)) { ConverterBase.updatedJPFObj.put(JPFObj, JVMObj); ConverterBase.objMapJPF2JVM.put(JPFObj, JVMObj); // Why do we need that? Because JPF might have not leaded the // class // before! JPF classloader does not recognize them! // I don't now why exactly! // INVESTIGATE: Why not arrays? if (JVMObj.getClass() == Class.class) { try { Class<?> temp = (Class<?>) JVMObj; if (!temp.isArray() && !temp.isPrimitive()) env.getConfig().getClassLoader().loadClass(temp.getName()); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } } DynamicElementInfo dei = (DynamicElementInfo) env.getHeap().getModifiable(JPFObj); if (dei == null) { System.err.println("ElemetInfo is null for ref: " + JPFObj + " JVM:" + JVMObj + " class:" + JVMObj.getClass()); return; } setInstanceFields(JVMObj, dei, env, ctx); } } } protected abstract void setInstanceFields(Object JVMObj, DynamicElementInfo dei, MJIEnv env, FeatureExpr ctx) throws ConversionException; /** * Updates the given JPF array according to the given JVM array. * * @param JVMArr * a JVM array * @param JPFArr * a JPF array * * @throws ConversionException * if any incorrect input parameter is observed */ protected void updateJPFArrObj(Object JVMArr, int JPFArr, final MJIEnv env, FeatureExpr ctx) throws ConversionException { if (JVMArr != null) { // First check if the JPF array has been already updated if (!ConverterBase.updatedJPFObj.containsKey(JPFArr)) { ConverterBase.updatedJPFObj.put(JPFArr, JVMArr); ConverterBase.objMapJPF2JVM.put(JPFArr, JVMArr); final DynamicElementInfo dei = (DynamicElementInfo) env.getHeap().getModifiable(JPFArr); ArrayFields fields = (ArrayFields) dei.getFields(); // Array of primitive type if (dei.getClassInfo().getComponentClassInfo().isPrimitive()) { Utilities.setJPFPrimitiveArr(ctx, dei, JVMArr, env); } // Array of Non-primitives else { int arrSize = fields.arrayLength().getValue(); final Object[] arrObj = (Object[]) JVMArr; for (int i = 0; i < arrSize; i++) { Conditional<Integer> conditionalElementValueRef = dei.getReferenceElement(i).simplify(ctx); final int currentI = i; conditionalElementValueRef.mapf(ctx, new BiConsumer<FeatureExpr, Integer>() { @Override public void accept(FeatureExpr ctx, Integer elementValueRef) { try { if (arrObj[currentI] == null) { elementValueRef = MJIEnv.NULL; } else if (elementValueRef == MJIEnv.NULL || ConverterBase.objMapJPF2JVM.get(elementValueRef) != arrObj[currentI]) { elementValueRef = obtainJPFObj(arrObj[currentI], env, ctx); } else if (ConverterBase.objMapJPF2JVM.get(elementValueRef) == arrObj[currentI]) { updateJPFObj(arrObj[currentI], elementValueRef, env, ctx); } else { throw new ConversionException("Unconsidered case observed! - JVM2JPF.updateArr()"); } dei.setReferenceElement(ctx, currentI, new One<>(elementValueRef)); } catch (ConversionException e) { e.printStackTrace(); } } }); } } } } } /** * Looks info the existing hash tables, declared in Converter, to see if the * JPF object corresponding to the given JVM object has been already * created. * * @param JVMObj * a JVM object * * @return a non-null JPF object if there already exists a JPF object * corresponding to the given JVM object. OW null JPF object is * returned. * * @throws ConversionException * if any incorrect input parameter is observed */ protected int getExistingJPFRef(Object JVMObj, boolean update, MJIEnv env, FeatureExpr ctx) throws ConversionException { int JPFRef = MJIEnv.NULL; boolean found = false; if (ConverterBase.updatedJPFObj.containsValue(JVMObj)) { Iterator<Integer> iterator = (ConverterBase.updatedJPFObj.keySet()).iterator(); Integer key; while (!found && iterator.hasNext()) { key = iterator.next(); Object value = ConverterBase.updatedJPFObj.get(key); if (value == JVMObj) { found = true; JPFRef = key; } } } if (!found && ConverterBase.objMapJPF2JVM.containsValue(JVMObj)) { Iterator<Integer> iterator = (ConverterBase.objMapJPF2JVM.keySet()).iterator(); Integer key; while (!found && iterator.hasNext()) { key = iterator.next(); Object value = ConverterBase.objMapJPF2JVM.get(key); if (value == JVMObj) { found = true; JPFRef = key; if (update == true) { getUpdatedJPFObj(JVMObj, JPFRef, env, ctx); } } } } return JPFRef; } /** * Creates a new JPF object corresponding to the given JVM object. It also * updates the class of the JPF object according to the class of the given * JVM object. * * @param JVMObj * a JVM object * @return a new JPF object corresponding to the given JVM object. * * @throws ConversionException * if any incorrect input parameter is observed * */ protected int getNewJPFRef(Object JVMObj, MJIEnv env, FeatureExpr ctx) throws ConversionException { int JPFRef = MJIEnv.NULL; Class<?> JVMCls = JVMObj.getClass(); if (!JVMCls.isArray()) { // we treat Strings differently, until we immigrate to JDK7 if (JVMObj.getClass() == String.class) { JPFRef = env.newString(ctx, new One<>(JVMObj.toString())); } else { ClassInfo fci = null; try { fci = this.getJPFCls(JVMCls, env, ctx); } catch (ClassInfoException e) { System.out.println("WARNING: the class " + JVMCls + " is ignored!"); return MJIEnv.NULL; } ElementInfo ei = env.getHeap().newObject(ctx, fci, env.getThreadInfo()); JPFRef = ei.getObjectRef(); this.updateJPFNonArrObj(JVMObj, JPFRef, env, ctx); } } else { JPFRef = Utilities.createNewJPFArray(JVMObj, env, ctx); this.updateJPFArrObj(JVMObj, JPFRef, env, ctx); } return JPFRef; } }