// // 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.Modifier; import java.util.ArrayList; import java.util.function.BiFunction; import cmu.conditional.ChoiceFactory; import cmu.conditional.Conditional; import cmu.conditional.One; import de.fosd.typechef.featureexpr.FeatureExpr; import gov.nasa.jpf.Config; import gov.nasa.jpf.annotation.MJI; import gov.nasa.jpf.util.MethodInfoRegistry; import gov.nasa.jpf.util.RunListener; import gov.nasa.jpf.util.RunRegistry; public class JPF_java_lang_reflect_Method extends NativePeer { static MethodInfoRegistry registry; // class init - this is called automatically from the NativePeer ctor public static boolean init (Config conf) { // this is an example of how to handle cross-initialization between // native peers - this might also get explicitly called by the java.lang.Class // peer, since it creates Method objects. Here we have to make sure // we only reset between JPF runs if (registry == null){ registry = new MethodInfoRegistry(); RunRegistry.getDefaultRegistry().addListener( new RunListener() { public void reset (RunRegistry reg){ registry = null; } }); } return true; } static int createMethodObject (MJIEnv env, ClassInfo ciMth, MethodInfo mi, FeatureExpr ctx){ // note - it is the callers responsibility to ensure Method is properly initialized int regIdx = registry.registerMethodInfo(mi); int eidx = env.newObject( ctx, ciMth); ElementInfo ei = env.getModifiableElementInfo(eidx); ei.setIntField(ctx, "regIdx", new One<>(regIdx)); ei.setBooleanField(ctx, "isAccessible", new One<>(mi.isPublic())); return eidx; } // this is NOT an MJI method, but it is used outside this package, so // we have to add 'final' public static final MethodInfo getMethodInfo (FeatureExpr ctx, MJIEnv env, int objRef){ return registry.getMethodInfo(ctx,env, objRef, "regIdx"); } @MJI public int getName____Ljava_lang_String_2 (MJIEnv env, int objRef, FeatureExpr ctx) { MethodInfo mi = getMethodInfo(ctx, env, objRef); int nameRef = env.getReferenceField( ctx, objRef, "name").getValue(); if (nameRef == MJIEnv.NULL) { nameRef = env.newString(ctx, mi.getName()); env.setReferenceField(ctx, objRef, "name", nameRef); } return nameRef; } @MJI public int getModifiers____I (MJIEnv env, int objRef, FeatureExpr ctx){ MethodInfo mi = getMethodInfo(ctx, env, objRef); return mi.getModifiers(); } static int getParameterTypes( MJIEnv env, MethodInfo mi, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); String[] argTypeNames = mi.getArgumentTypeNames(); int[] ar = new int[argTypeNames.length]; for (int i = 0; i < argTypeNames.length; i++) { ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(argTypeNames[i]); if (!ci.isRegistered()) { ci.registerClass(ctx, ti); } ar[i] = ci.getClassObjectRef(); } int aRef = env.newObjectArray("Ljava/lang/Class;", argTypeNames.length); for (int i = 0; i < argTypeNames.length; i++) { env.setReferenceArrayElement(ctx, aRef, i, new One<>(ar[i])); } return aRef; } @MJI public int getParameterTypes_____3Ljava_lang_Class_2 (MJIEnv env, int objRef, FeatureExpr ctx){ return getParameterTypes(env, getMethodInfo(ctx, env, objRef), ctx); } @MJI public int getGenericParameterTypes_____3Ljava_lang_reflect_Type_2(MJIEnv env, int objRef, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); String[] argTypeNames = getMethodInfo(ctx, env, objRef).getArgumentTypeNames(); int[] ar = new int[argTypeNames.length]; for (int i = 0; i < argTypeNames.length; i++) { ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(argTypeNames[i]); if (!ci.isRegistered()) { ci.registerClass(ctx, ti); } ar[i] = ci.getClassObjectRef(); } int aRef = env.newObjectArray("Ljava/lang/reflect/Type;", argTypeNames.length); for (int i = 0; i < argTypeNames.length; i++) { env.setReferenceArrayElement(ctx, aRef, i, new One<>(ar[i])); } return aRef; } int getExceptionTypes(MJIEnv env, MethodInfo mi, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); String[] exceptionNames = mi.getThrownExceptionClassNames(); if (exceptionNames == null) { exceptionNames = new String[0]; } int[] ar = new int[exceptionNames.length]; for (int i = 0; i < exceptionNames.length; i++) { ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(exceptionNames[i]); if (!ci.isRegistered()) { ci.registerClass(ctx, ti); } ar[i] = ci.getClassObjectRef(); } int aRef = env.newObjectArray("Ljava/lang/Class;", exceptionNames.length); for (int i = 0; i < exceptionNames.length; i++) { env.setReferenceArrayElement(ctx, aRef, i, new One<>(ar[i])); } return aRef; } @MJI public int getExceptionTypes_____3Ljava_lang_Class_2 (MJIEnv env, int objRef, FeatureExpr ctx) { return getExceptionTypes(env, getMethodInfo(ctx, env, objRef), ctx); } @MJI public int getGenericReturnType____Ljava_lang_reflect_Type_2 (MJIEnv env, int objRef, FeatureExpr ctx){ MethodInfo mi = getMethodInfo(ctx, env, objRef); ThreadInfo ti = env.getThreadInfo(); ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(mi.getReturnTypeName()); if (!ci.isRegistered()) { ci.registerClass(ctx, ti); } return ci.getClassObjectRef(); } @MJI public int getReturnType____Ljava_lang_Class_2 (MJIEnv env, int objRef, FeatureExpr ctx){ MethodInfo mi = getMethodInfo(ctx, env, objRef); ThreadInfo ti = env.getThreadInfo(); ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(mi.getReturnTypeName()); if (!ci.isRegistered()) { ci.registerClass(ctx, ti); } return ci.getClassObjectRef(); } @MJI public int getDeclaringClass____Ljava_lang_Class_2 (MJIEnv env, int objRef, FeatureExpr ctx){ MethodInfo mi = getMethodInfo(ctx, env, objRef); ClassInfo ci = mi.getClassInfo(); // it's got to be registered, otherwise we wouldn't be able to acquire the Method object return ci.getClassObjectRef(); } static int createBoxedReturnValueObject (MJIEnv env, MethodInfo mi, DirectCallStackFrame frame, FeatureExpr ctx) { byte rt = mi.getReturnTypeCode(); int ret = MJIEnv.NULL; ElementInfo rei; Object attr = null; if (rt == Types.T_DOUBLE) { attr = frame.getLongResultAttr(); double v = frame.getDoubleResult(); ret = env.newObject(ctx, ClassLoaderInfo.getSystemResolvedClassInfo("java.lang.Double")); rei = env.getModifiableElementInfo(ret); rei.setDoubleField(ctx, "value", new One<>(v)); } else if (rt == Types.T_FLOAT) { attr = frame.getResultAttr(); float v = frame.getFloatResult(); ret = env.newObject(ctx, ClassLoaderInfo.getSystemResolvedClassInfo("java.lang.Float")); rei = env.getModifiableElementInfo(ret); rei.setFloatField(ctx, "value", new One<>(v)); } else if (rt == Types.T_LONG) { attr = frame.getLongResultAttr(); long v = frame.getLongResult(); ret = env.valueOfLong(ctx, v); } else if (rt == Types.T_BYTE) { attr = frame.getResultAttr(); int v = frame.getResult(); ret = env.valueOfByte(ctx, (byte)v); } else if (rt == Types.T_CHAR) { attr = frame.getResultAttr(); int v = frame.getResult(); ret = env.valueOfCharacter(ctx, (char)v); } else if (rt == Types.T_SHORT) { attr = frame.getResultAttr(); int v = frame.getResult(); ret = env.valueOfShort(ctx, (short)v); } else if (rt == Types.T_INT) { attr = frame.getResultAttr(); int v = frame.getResult(); ret = env.valueOfInteger(ctx, v).getValue(); } else if (rt == Types.T_BOOLEAN) { attr = frame.getResultAttr(); int v = frame.getResult(); ret = env.valueOfBoolean((v == 1)? true: false); } else if (mi.isReferenceReturnType()){ attr = frame.getResultAttr(); ret = frame.getReferenceResult(ctx); } env.setReturnAttribute(attr); return ret; } private static Conditional<Integer> argIndex; static boolean pushUnboxedArguments (final MJIEnv env, MethodInfo mi, final DirectCallStackFrame frame, int argIdx, final int argsRef, FeatureExpr ctx) { argIndex = new One<>(argIdx); final String destTypeNames[] = mi.getArgumentTypeNames(); int nArgs, passedCount; final byte destTypes[] = mi.getArgumentTypes(); nArgs = destTypeNames.length; // according to the API docs, passing null instead of an empty array is allowed for no args passedCount = (argsRef != MJIEnv.NULL) ? env.getArrayLength(ctx, argsRef) : 0; if (nArgs != passedCount) { env.throwException(ctx, IllegalArgumentException.class.getName(), "Wrong number of arguments passed. Actual = " + passedCount + ". Expected = " + nArgs); return false; } for (int i = 0; i < nArgs; i++) { final int index = i; Conditional<Integer> sourceRef = env.getReferenceArrayElement(argsRef, i).simplify(ctx); Conditional<Boolean> loopValue = sourceRef.mapf(ctx, new BiFunction<FeatureExpr, Integer, Conditional<Boolean>>() { @Override public Conditional<Boolean> apply(FeatureExpr ctx, Integer sourceRef) { // we have to handle null references explicitly if (sourceRef == MJIEnv.NULL) { if ((destTypes[index] != Types.T_REFERENCE) && (destTypes[index] != Types.T_ARRAY)) { env.throwException(ctx, IllegalArgumentException.class.getName(), "Wrong argument type at index " + index + ". Actual = (null). Expected = " + destTypeNames[index]); return One.FALSE; } frame.pushRef(ctx, MJIEnv.NULL); return One.TRUE; } ElementInfo source = env.getElementInfo(sourceRef); ClassInfo sourceClass = source.getClassInfo(); byte sourceType = getSourceType( sourceClass, destTypes[index], destTypeNames[index]); Object attr = env.getElementInfo(argsRef).getFields().getFieldAttr(index); int pushArg = pushArg( argIndex.simplify(ctx).getValue(), frame, source, sourceType, destTypes[index], attr, ctx); argIndex = ChoiceFactory.create(ctx, new One<>(pushArg), argIndex).simplify(); if (pushArg < 0 ){ env.throwException(ctx, IllegalArgumentException.class.getName(), "Wrong argument type at index " + index + ". Source Class = " + sourceClass.getName() + ". Dest Class = " + destTypeNames[index]); return One.FALSE; } return One.TRUE; } }); if (loopValue.simplify().equals(One.TRUE)) { continue; } else if (loopValue.simplify().equals(One.FALSE)) { return false; } else { System.out.println(loopValue); throw new RuntimeException("More lifting required"); } } return true; } // this returns the primitive type in case we have to unbox, and otherwise checks reference type compatibility private static byte getSourceType (ClassInfo ciArgVal, byte destType, String destTypeName){ switch (destType){ // the primitives case Types.T_BOOLEAN: case Types.T_BYTE: case Types.T_CHAR: case Types.T_SHORT: case Types.T_INT: case Types.T_LONG: case Types.T_FLOAT: case Types.T_DOUBLE: return Types.getUnboxedType(ciArgVal.getName()); case Types.T_ARRAY: case Types.T_REFERENCE: // check if the source type is assignment compatible with the destType if (ciArgVal.isInstanceOf(destTypeName)){ return destType; } } return Types.T_NONE; } // do the proper type conversion - Java is pretty forgiving here and does // not throw exceptions upon value truncation private static int pushArg( int argIdx, DirectCallStackFrame frame, ElementInfo eiArg, byte srcType, byte destType, Object attr, FeatureExpr ctx){ switch (srcType) { case Types.T_DOUBLE: { double v = eiArg.getDoubleField("value").getValue(); if (destType == Types.T_DOUBLE){ return frame.setDoubleArgument( argIdx, v, attr); } return -1; } case Types.T_FLOAT: // covers float, double { float v = eiArg.getFloatField("value").getValue(); switch (destType){ case Types.T_FLOAT: return frame.setFloatArgument( argIdx, v, attr); case Types.T_DOUBLE: return frame.setDoubleArgument( argIdx, v, attr); } return -1; } case Types.T_LONG: { long v = eiArg.getLongField("value").simplify(ctx).getValue(); switch (destType){ case Types.T_LONG: return frame.setLongArgument(argIdx, v, attr); case Types.T_FLOAT: return frame.setFloatArgument(argIdx, (float)v, attr); case Types.T_DOUBLE: return frame.setDoubleArgument( argIdx, (double)v, attr); } return -1; } case Types.T_INT: { int v = eiArg.getIntField("value").simplify(ctx).getValue(); switch (destType){ case Types.T_INT: return frame.setArgument( argIdx, v, attr); case Types.T_LONG: return frame.setLongArgument( argIdx, v, attr); case Types.T_FLOAT: return frame.setFloatArgument(argIdx, (float)v, attr); case Types.T_DOUBLE: return frame.setDoubleArgument( argIdx, (double)v, attr); } return -1; } case Types.T_SHORT: { int v = eiArg.getShortField("value").getValue(); switch (destType){ case Types.T_SHORT: case Types.T_INT: return frame.setArgument( argIdx, v, attr); case Types.T_LONG: return frame.setLongArgument( argIdx, v, attr); case Types.T_FLOAT: return frame.setFloatArgument(argIdx, (float)v, attr); case Types.T_DOUBLE: return frame.setDoubleArgument( argIdx, (double)v, attr); } return -1; } case Types.T_BYTE: { byte v = eiArg.getByteField("value").getValue(); switch (destType){ case Types.T_BYTE: case Types.T_SHORT: case Types.T_INT: return frame.setArgument( argIdx, v, attr); case Types.T_LONG: return frame.setLongArgument( argIdx, v, attr); case Types.T_FLOAT: return frame.setFloatArgument(argIdx, (float)v, attr); case Types.T_DOUBLE: return frame.setDoubleArgument( argIdx, (double)v, attr); } return -1; } case Types.T_CHAR: { char v = eiArg.getCharField("value").getValue(); switch (destType){ case Types.T_CHAR: case Types.T_INT: return frame.setArgument( argIdx, v, attr); case Types.T_LONG: return frame.setLongArgument( argIdx, v, attr); case Types.T_FLOAT: return frame.setFloatArgument(argIdx, (float)v, attr); case Types.T_DOUBLE: return frame.setDoubleArgument( argIdx, (double)v, attr); } return -1; } case Types.T_BOOLEAN: { boolean v = eiArg.getBooleanField("value").getValue(); if (destType == Types.T_BOOLEAN){ return frame.setArgument( argIdx, v ? 1 : 0, attr); } return -1; } case Types.T_ARRAY: { int ref = eiArg.getObjectRef(); if (destType == Types.T_ARRAY){ return frame.setReferenceArgument( ctx, argIdx, ref, attr); } return -1; } case Types.T_REFERENCE: { int ref = eiArg.getObjectRef(); if (destType == Types.T_REFERENCE){ return frame.setReferenceArgument( ctx, argIdx, ref, attr); } return -1; } default: // T_VOID, T_NONE return -1; } } @MJI public int invoke__Ljava_lang_Object_2_3Ljava_lang_Object_2__Ljava_lang_Object_2 (MJIEnv env, int mthRef, Conditional<Integer> objRef, Conditional<Integer> argsRef, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); MethodInfo miCallee = getMethodInfo(ctx, env, mthRef); ClassInfo calleeClass = miCallee.getClassInfo(); DirectCallStackFrame frame = ti.getReturnedDirectCall(); if (frame == null){ // first time //--- check the instance we are calling on if (!miCallee.isStatic()) { if (objRef.getValue().intValue() == MJIEnv.NULL){ env.throwException(ctx, "java.lang.NullPointerException"); return MJIEnv.NULL; } else { ElementInfo eiObj = ti.getElementInfo(objRef.getValue()); ClassInfo objClass = eiObj.getClassInfo(); if (!objClass.isInstanceOf(calleeClass)) { env.throwException(ctx, IllegalArgumentException.class.getName(), "Object is not an instance of declaring class. Actual = " + objClass + ". Expected = " + calleeClass); return MJIEnv.NULL; } } } //--- check accessibility ElementInfo eiMth = ti.getElementInfo(mthRef); Object object = eiMth.getFieldValueObject("isAccessible"); if (object instanceof Conditional) { object = ((Conditional<?>)object).simplify(ctx).getValue(); } if (! (Boolean) object) { StackFrame caller = ti.getTopFrame().getPrevious(); ClassInfo callerClass = caller.getClassInfo(); if (callerClass != calleeClass) { env.throwException(ctx, IllegalAccessException.class.getName(), "Class " + callerClass.getName() + " can not access a member of class " + calleeClass.getName() + " with modifiers \"" + Modifier.toString(miCallee.getModifiers())); return MJIEnv.NULL; } } //--- push the direct call frame = miCallee.createDirectCallStackFrame(ctx, ti, 0); frame.setReflection(); int argOffset = 0; if (!miCallee.isStatic()) { frame.setReferenceArgument( ctx, argOffset++, objRef.getValue(), null); } if (!pushUnboxedArguments( env, miCallee, frame, argOffset, argsRef.getValue(), ctx)) { // we've got a IllegalArgumentException return MJIEnv.NULL; } ti.pushFrame(frame); //--- check for and push required clinits if (miCallee.isStatic()){ calleeClass.pushRequiredClinits(ctx, ti); } return MJIEnv.NULL; // reexecute } else { // we have returned from the direct call return createBoxedReturnValueObject( env, miCallee, frame, ctx); } } // this one has to collect annotations upwards in the inheritance chain static int getAnnotations (MJIEnv env, MethodInfo mi, FeatureExpr ctx){ String mname = mi.getName(); String msig = mi.genericSignature; ArrayList<AnnotationInfo> aiList = new ArrayList<AnnotationInfo>(); // our own annotations ClassInfo ci = mi.getClassInfo(); for (AnnotationInfo ai : mi.getAnnotations()) { aiList.add(ai); } // our superclass annotations for (ci = ci.getSuperClass(); ci != null; ci = ci.getSuperClass()){ mi = ci.getMethod(mname, msig, false); if (mi != null){ for (AnnotationInfo ai: mi.getAnnotations()){ aiList.add(ai); } } } try { return env.newAnnotationProxies(ctx, aiList.toArray(new AnnotationInfo[aiList.size()])); } catch (ClinitRequired x){ env.handleClinitRequest(ctx, x.getRequiredClassInfo()); return MJIEnv.NULL; } } @MJI public int getAnnotations_____3Ljava_lang_annotation_Annotation_2 (MJIEnv env, int mthRef, FeatureExpr ctx){ return getAnnotations( env, getMethodInfo(ctx,env, mthRef), ctx); } // the following ones consist of a package default implementation that is shared with // the constructor peer, and a public model method static int getAnnotation (MJIEnv env, MethodInfo mi, int annotationClsRef, FeatureExpr ctx){ ClassInfo aci = env.getReferredClassInfo(ctx, annotationClsRef); AnnotationInfo ai = mi.getAnnotation(aci.getName()); if (ai != null){ ClassInfo aciProxy = aci.getAnnotationProxy(); try { return env.newAnnotationProxy(ctx, aciProxy, ai); } catch (ClinitRequired x){ env.handleClinitRequest(ctx, x.getRequiredClassInfo()); return MJIEnv.NULL; } } return MJIEnv.NULL; } @MJI public int getAnnotation__Ljava_lang_Class_2__Ljava_lang_annotation_Annotation_2 (MJIEnv env, int mthRef, int annotationClsRef, FeatureExpr ctx) { return getAnnotation(env, getMethodInfo(ctx,env, mthRef), annotationClsRef, ctx); } static int getDeclaredAnnotations (MJIEnv env, MethodInfo mi, FeatureExpr ctx){ AnnotationInfo[] ai = mi.getAnnotations(); try { return env.newAnnotationProxies(ctx, ai); } catch (ClinitRequired x){ env.handleClinitRequest(ctx, x.getRequiredClassInfo()); return MJIEnv.NULL; } } @MJI public int getDeclaredAnnotations_____3Ljava_lang_annotation_Annotation_2 (MJIEnv env, int mthRef, FeatureExpr ctx){ return getDeclaredAnnotations( env, getMethodInfo(ctx,env, mthRef), ctx); } static int getParameterAnnotations (MJIEnv env, MethodInfo mi, FeatureExpr ctx){ AnnotationInfo[][] pa = mi.getParameterAnnotations(); // this should always return an array object, even if the method has no arguments try { int paRef = env.newObjectArray("[Ljava/lang/annotation/Annotation;", pa.length); for (int i=0; i<pa.length; i++){ int eRef = env.newAnnotationProxies(ctx, pa[i]); env.setReferenceArrayElement(ctx, paRef, i, new One<>(eRef)); } return paRef; } catch (ClinitRequired x){ // be prepared that we might have to initialize respective annotation classes env.handleClinitRequest(ctx, x.getRequiredClassInfo()); return MJIEnv.NULL; } } @MJI public int getParameterAnnotations_____3_3Ljava_lang_annotation_Annotation_2 (MJIEnv env, int mthRef, FeatureExpr ctx){ return getParameterAnnotations( env, getMethodInfo(ctx,env, mthRef), ctx); } @MJI public int toString____Ljava_lang_String_2 (MJIEnv env, int objRef, FeatureExpr ctx){ StringBuilder sb = new StringBuilder(); MethodInfo mi = getMethodInfo(ctx, env, objRef); sb.append(Modifier.toString(mi.getModifiers())); sb.append(' '); sb.append(mi.getReturnTypeName()); sb.append(' '); sb.append(mi.getClassName()); sb.append('.'); sb.append(mi.getName()); sb.append('('); String[] at = mi.getArgumentTypeNames(); for (int i=0; i<at.length; i++){ if (i>0) sb.append(','); sb.append(at[i]); } sb.append(')'); int sref = env.newString(ctx, sb.toString()); return sref; } @MJI public boolean equals__Ljava_lang_Object_2__Z (MJIEnv env, int objRef, int mthRef, FeatureExpr ctx){ ElementInfo ei = env.getElementInfo(mthRef); ClassInfo ci = ClassLoaderInfo.getSystemResolvedClassInfo(JPF_java_lang_Class.METHOD_CLASSNAME); if (ei.getClassInfo() == ci){ MethodInfo mi1 = getMethodInfo(ctx, env, objRef); MethodInfo mi2 = getMethodInfo(ctx, env, mthRef); if (mi1.getClassInfo() == mi2.getClassInfo()){ if (mi1.getName().equals(mi2.getName())){ if (mi1.getReturnType().equals(mi2.getReturnType())){ byte[] params1 = mi1.getArgumentTypes(); byte[] params2 = mi2.getArgumentTypes(); if (params1.length == params2.length){ for (int i = 0; i < params1.length; i++){ if (params1[i] != params2[i]){ return false; } } return true; } } } } } return false; } @MJI public int hashCode____I (MJIEnv env, int objRef, FeatureExpr ctx){ MethodInfo mi = getMethodInfo(ctx, env, objRef); return mi.getClassName().hashCode() ^ mi.getName().hashCode(); } }