Add Peer for java.util.concurrent.atomic.AtomicReference (#182)
[jpf-core.git] / src / main / gov / nasa / jpf / vm / NativePeer.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  * 
10  *        http://www.apache.org/licenses/LICENSE-2.0. 
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and 
16  * limitations under the License.
17  */
18 package gov.nasa.jpf.vm;
19
20 import gov.nasa.jpf.Config;
21 import gov.nasa.jpf.JPF;
22 import gov.nasa.jpf.JPFException;
23 import gov.nasa.jpf.annotation.MJI;
24 import gov.nasa.jpf.util.JPFLogger;
25
26 import java.lang.reflect.Constructor;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.logging.Level;
33
34
35 /**
36  * native peer classes are part of MJI and contain the code that is
37  * executed by the host VM (i.e. outside the state-tracked JPF VM). Each
38  * class executed by JPF that has native methods must have a native peer class
39  * (which is looked up and associated at class loadtime)
40  */
41 public class NativePeer implements Cloneable {
42
43   static final String MODEL_PACKAGE = "<model>";
44   static final String DEFAULT_PACKAGE = "<default>";
45
46   static JPFLogger logger = JPF.getLogger("class");
47
48   static ClassLoader loader;
49   static HashMap<ClassInfo, NativePeer> peers;
50   static Config config;
51   static boolean noOrphanMethods;
52
53   static String[] peerPackages;
54
55   ClassInfo ci;
56   Class<?> peerClass;
57   HashMap<String, Method> methods;
58
59
60   public static boolean init (Config conf) {
61     loader = conf.getClassLoader();
62     peers = new HashMap<ClassInfo, NativePeer>();
63
64     peerPackages = getPeerPackages(conf);
65
66     config = conf;
67     noOrphanMethods = conf.getBoolean("vm.no_orphan_methods", false);
68
69     return true;
70   }
71
72   static String[] getPeerPackages (Config conf) {
73     String[] defPeerPackages = { MODEL_PACKAGE, "gov.nasa.jpf.vm", DEFAULT_PACKAGE };
74     String[] packages = conf.getStringArray("peer_packages", defPeerPackages);
75
76     // internalize
77     for (int i=0; i<packages.length; i++) {
78       if (packages[i].equals(MODEL_PACKAGE)) {
79         packages[i] = MODEL_PACKAGE;
80       } else if (packages[i].equals(DEFAULT_PACKAGE)) {
81         packages[i] = DEFAULT_PACKAGE;
82       }
83     }
84
85     return packages;
86   }
87
88   static Class<?> locatePeerCls (String clsName) {
89     String cn = "JPF_" + clsName.replace('.', '_');
90
91     for (int i=0; i<peerPackages.length; i++) {
92       String pcn;
93       String pkg = peerPackages[i];
94
95       if (pkg == MODEL_PACKAGE) {
96         int j = clsName.lastIndexOf('.');
97         pcn = clsName.substring(0, j+1) + cn;
98       } else if (pkg == DEFAULT_PACKAGE) {
99         pcn = cn;
100       } else {
101         pcn = pkg + '.' + cn;
102       }
103      
104       try {
105         Class<?> peerCls = loader.loadClass(pcn);
106         
107         if ((peerCls.getModifiers() & Modifier.PUBLIC) == 0) {
108           logger.warning("non-public peer class: ", pcn);
109           continue; // pointless to use this one, it would just create IllegalAccessExceptions
110         }
111         
112         logger.info("loaded peer class: ", pcn);
113         
114         return peerCls;
115       } catch (ClassNotFoundException cnfx) {
116         // try next one
117       }
118     }
119
120     return null; // nothing found
121   }
122
123   /**
124    * this becomes the factory method to load either a plain (slow)
125    * reflection-based peer (a NativePeer object), or some speed optimized
126    * derived class object.
127    * Watch out - this gets called before the ClassInfo is fully initialized
128    * (we shouldn't rely on more than just its name here)
129    */
130   static NativePeer getNativePeer (ClassInfo ci) {
131     String     clsName = ci.getName();
132     NativePeer peer = peers.get(ci);
133     Class<?>      peerCls = null;
134
135     if (peer == null) {
136       peerCls = locatePeerCls(clsName);
137
138       if (peerCls != null) {
139         initializePeerClass( peerCls);
140                 
141         if (logger.isLoggable(Level.INFO)) {
142           logger.info("load peer: ", peerCls.getName());
143         }
144
145         peer = getInstance(peerCls, NativePeer.class);
146         peer.initialize(peerCls, ci, true);
147
148         peers.put(ci, peer);
149       }
150     }
151
152     return peer;
153   }
154
155   public static <T> T getInstance(Class<?> cls, Class<T> type) throws JPFException {
156     Class<?>[] argTypes = Config.CONFIG_ARGTYPES;
157     Object[] args = config.CONFIG_ARGS;
158
159     return getInstance(cls, type, argTypes, args);
160   }
161
162   public static <T> T getInstance(Class<?> cls, Class<T> type, Class<?>[] argTypes,
163                      Object[] args) throws JPFException {
164     Object o = null;
165     Constructor<?> ctor = null;
166
167     if (cls == null) {
168       return null;
169     }
170
171     while (o == null) {
172       try {
173         ctor = cls.getConstructor(argTypes);
174         o = ctor.newInstance(args);
175       } catch (NoSuchMethodException nmx) {
176          
177         if ((argTypes.length > 1) || ((argTypes.length == 1) && (argTypes[0] != Config.class))) {
178           // fallback 1: try a single Config param
179           argTypes = Config.CONFIG_ARGTYPES;
180           args = config.CONFIG_ARGS;
181         } else if (argTypes.length > 0) {
182           // fallback 2: try the default ctor
183           argTypes = Config.NO_ARGTYPES;
184           args = Config.NO_ARGS;
185
186         } else {
187           // Ok, there is no suitable ctor, bail out
188           throw new JPFException("no suitable ctor found for the peer class " + cls.getName());
189         }
190       } catch (IllegalAccessException iacc) {
191         throw new JPFException("ctor not accessible: "
192             + config.getMethodSignature(ctor));
193       } catch (IllegalArgumentException iarg) {
194         throw new JPFException("illegal constructor arguments: "
195             + config.getMethodSignature(ctor));
196       } catch (InvocationTargetException ix) {
197         Throwable tx = ix.getCause();
198         throw new JPFException("exception " + tx + " occured in " 
199             + config.getMethodSignature(ctor));
200       } catch (InstantiationException ivt) {
201         throw new JPFException("abstract class cannot be instantiated");
202       } catch (ExceptionInInitializerError eie) {
203         throw new JPFException("static initialization failed:\n>> "
204             + eie.getException(), eie.getException());
205       }
206     }
207
208     // check type
209     if (!cls.isInstance(o)) {
210       throw new JPFException("instance not of type: "
211           + cls.getName());
212     }
213
214     return type.cast(o); // safe according to above
215   }
216
217   static String getPeerDispatcherClassName (String clsName) {
218     return (clsName + '$');
219   }
220
221   public Class<?> getPeerClass() {
222     return peerClass;
223   }
224
225   public String getPeerClassName() {
226     return peerClass.getName();
227   }
228
229   protected void initialize (Class<?> peerClass, ClassInfo ci, boolean cacheMethods) {
230     if ((this.ci != null) || (this.peerClass != null)) {
231       throw new RuntimeException("cannot re-initialize NativePeer: " +
232                                  peerClass.getName());
233     }
234
235     this.ci = ci;
236     this.peerClass = peerClass;
237
238     loadMethods(cacheMethods);
239   }
240
241   protected static void initializePeerClass( Class<?> cls) {
242     try {
243       Method m = cls.getDeclaredMethod("init", Config.class );
244       try {
245         m.invoke(null, config);
246       } catch (IllegalArgumentException iax){
247         // can't happen - static method
248       } catch (IllegalAccessException iacx) {
249         throw new RuntimeException("peer initialization method not accessible: "
250                                    + cls.getName());
251       } catch (InvocationTargetException itx){
252         throw new RuntimeException("initialization of peer " +
253                                    cls.getName() + " failed: " + itx.getCause());
254
255       }
256     } catch (NoSuchMethodException nsmx){
257       // nothing to do
258     }
259   }
260
261   private static boolean isMJICandidate (Method mth) {
262
263     // the native peer should be annotated with @MJI
264     if(!mth.isAnnotationPresent(MJI.class)) {
265       return false;
266     }
267
268     // this native peer should be Public
269     if(!Modifier.isPublic(mth.getModifiers())) {
270       return false;
271     }
272
273     // native method always have a MJIEnv and int as the first parameters
274     Class<?>[] argTypes = mth.getParameterTypes();
275     if ((argTypes.length >= 2) && (argTypes[0] == MJIEnv.class) && (argTypes[1] == int.class) ) {
276       return true;
277     } else {
278       return false;
279     }
280   }
281
282
283   private Method getMethod (MethodInfo mi) {
284     return getMethod(null, mi);
285   }
286
287   private Method getMethod (String prefix, MethodInfo mi) {
288     String name = mi.getUniqueName();
289
290     if (prefix != null) {
291       name = prefix + name;
292     }
293
294     return methods.get(name);
295   }
296
297   /**
298    * look at all @MJI annotated  methods in the peer and set their
299    * corresponding model class MethodInfo attributes
300    * <2do> pcm - this is too long, break it down
301    */
302   protected void loadMethods (boolean cacheMethods) {
303     // since we allow native peer class hierarchies, we have to look at all methods
304     //Method[] m = peerClass.getDeclaredMethods();
305     Method[] m = peerClass.getMethods();
306     
307     methods = new HashMap<String, Method>(m.length);
308
309     Map<String,MethodInfo> methodInfos = ci.getDeclaredMethods();
310     MethodInfo[] mis = null;
311
312     for (int i = 0; i < m.length; i++) {
313       Method  mth = m[i];
314
315       if (isMJICandidate(mth)) {
316         // Note that we can't mangle the name automatically, since we loose the
317         // object type info (all mapped to int). This has to be handled
318         // the same way like with overloaded JNI methods - you have to
319         // mangle them manually
320         String mn = mth.getName();
321
322         // JNI doesn't allow <clinit> or <init> to be native, but MJI does
323         // (you should know what you are doing before you use that, really)
324         if (mn.startsWith("$clinit")) {
325           mn = "<clinit>";
326         } else if (mn.startsWith("$init")) {
327           mn = "<init>" + mn.substring(5);
328         }
329
330         String mname = Types.getJNIMethodName(mn);
331         String sig = Types.getJNISignature(mn);
332
333         if (sig != null) {
334           mname += sig;
335         }
336
337         // now try to find a corresponding MethodInfo object and mark it
338         // as 'peer-ed'
339         // <2do> in case of <clinit>, it wouldn't be strictly required to
340         // have a MethodInfo upfront (we could create it). Might be handy
341         // for classes where we intercept just a few methods, but need
342         // to init before
343         MethodInfo mi = methodInfos.get(mname);
344
345         if ((mi == null) && (sig == null)) {
346           // nothing found, we have to do it the hard way - check if there is
347           // a single method with this name (still unsafe, but JNI behavior)
348           // Note there's no point in doing that if we do have a signature
349           if (mis == null) { // cache it for subsequent lookup
350             mis = new MethodInfo[methodInfos.size()];
351             methodInfos.values().toArray(mis);
352           }
353
354           mi = searchMethod(mname, mis);
355         }
356
357         if (mi != null) {
358           logger.info("load MJI method: ", mname);
359
360           NativeMethodInfo miNative = new NativeMethodInfo(mi, mth, this);
361           miNative.replace(mi);
362
363         } else {
364           checkOrphan(mth, mname);
365         }
366       }
367     }
368   }
369
370   protected void checkOrphan (Method mth, String mname){
371     if (!ignoreOrphan(mth)) {
372       // we have an orphan method, i.e. a peer method that does not map into any model method
373       // This is usually a signature typo or an out-of-sync peer, but could also be a
374       // MJI method in a peer superclass which is bound to a MethodInfo in a model superclass
375
376       Class<?> implCls = mth.getDeclaringClass();
377       if (implCls != peerClass) {
378         ClassInfo ciSuper = ci.getSuperClass();
379         if (ciSuper != null){
380           MethodInfo mi = ciSuper.getMethod(mname, true);
381           if (mi != null){
382             if (mi instanceof NativeMethodInfo){
383               NativeMethodInfo nmi = (NativeMethodInfo)mi;
384               if (nmi.getMethod().equals(mth)){
385                 return;
386               }
387             }
388           }
389         }
390       }
391
392       String message = "orphan NativePeer method: " + ci.getName() + '.' + mname;
393
394       if (noOrphanMethods) {
395         throw new JPFException(message);
396       } else {
397         // issue a warning if we have a NativePeer native method w/o a corresponding
398         // method in the model class (this might happen due to compiler optimizations
399         // silently skipping empty methods)
400         logger.warning(message);
401       }
402     }
403   }
404   
405   protected boolean ignoreOrphan (Method m){
406     MJI annotation = m.getAnnotation(MJI.class);
407     return annotation.noOrphanWarning();
408   }
409   
410   private MethodInfo searchMethod (String mname, MethodInfo[] methods) {
411     int idx = -1;
412
413     for (int j = 0; j < methods.length; j++) {
414       if (methods[j].getName().equals(mname)) {
415         // if this is actually a overloaded method, and the first one
416         // isn't the right choice, we would get an IllegalArgumentException,
417         // hence we have to go on and make sure it's not overloaded
418
419         if (idx == -1) {
420           idx = j;
421         } else {
422           throw new JPFException("overloaded native method without signature: " + ci.getName() + '.' + mname);
423         }
424       }
425     }
426
427     if (idx >= 0) {
428       return methods[idx];
429     } else {
430       return null;
431     }
432   }
433 }
434