comparison src/main/gov/nasa/jpf/vm/NativePeer.java @ 0:61d41facf527

initial v8 import (history reset)
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Fri, 23 Jan 2015 10:14:01 -0800
parents
children 3517702bd768
comparison
equal deleted inserted replaced
-1:000000000000 0:61d41facf527
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 mehods 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 static 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: " + mname);
423 }
424 }
425 }
426
427 if (idx >= 0) {
428 return methods[idx];
429 } else {
430 return null;
431 }
432 }
433 }
434