comparison src/main/gov/nasa/jpf/jvm/JVMClassInfo.java @ 24:6774e2e08d37

the fix I would have liked to avoid - apparently hotspot internally does nested locking during class init, which can lead to deadlocks such as described in http://ternarysearch.blogspot.ru/2013/07/static-initialization-deadlock.html. Actually, it's not a regular deadlock since core dumps still list the threads as runnable, althouth it doesn't seem to be a livelock either. In any case, it can be simulated by nested locking and clinit execution, and it is such a serious defect that we want to be able to catch it. The general mechanism is to replace the disparate (but properly ordered) direct clinit calls of the generic ClassInfo.initializeClass() with a single sythetic method that includes all required locking (bottom up), clinit calls / class status change (top down), and unlocking (top down). We also need to add a synthetic insn to defer changing the class status of classes that don't have clinits(), or otherwise the correct lock/unlock order will not amount to anything if the hierarchy is entered through one of the clinit-absent classes. Now we get proper deadlocks if there are concurrent cyclic dependencies during class resolution. However, this can be such a state exploder that we certainly don't want this as the default behavior, especially since it probably is hotspot specific. Nested class init locking is therefore controlled by jvm.nested_init and respective jvm.nested_init.include/exclude options. Added a NestedInitTest to demonstrate use. Thanks to Lilia Abdulina for bringing this long forgotten issue up In the wake of nested locks, there were a number of cases to fix that implicitly relied on absent clinits because clients were not properly checking for re-execution (most notably java.util.Exchanger). This mostly came in through MJIEnv.newObject/ElementInfo. We might turn ClinitRequired into a handled exception at some point, to catch such cases during compilation. Added a UnknownJPFClass exception (in analogy to ClinitRequired), to make clients aware of failed class load attempts/reasons. fixed Exchanger peer, which was not giving up the lock when timing out. This is an example of a lockfree wait op that can time out. Basically, ThreadInfo.isWaiting() needs to be complemented by a isWaitingOrTimedOut(), and ElementInfo.notifies0() has to be aware of it fixed NPE when setting report.probe_interval in tests, which was missing that it had to create a stat object
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Tue, 21 Apr 2015 00:34:15 -0700
parents b822e7665585
children 7be90179bb3b
comparison
equal deleted inserted replaced
23:db918c531e6d 24:6774e2e08d37
16 * limitations under the License. 16 * limitations under the License.
17 */ 17 */
18 18
19 package gov.nasa.jpf.jvm; 19 package gov.nasa.jpf.jvm;
20 20
21 import gov.nasa.jpf.Config;
21 import gov.nasa.jpf.util.Misc; 22 import gov.nasa.jpf.util.Misc;
22 import gov.nasa.jpf.vm.AbstractTypeAnnotationInfo; 23 import gov.nasa.jpf.util.StringSetMatcher;
23 import gov.nasa.jpf.vm.AnnotationInfo; 24 import gov.nasa.jpf.vm.*;
24 import gov.nasa.jpf.vm.BootstrapMethodInfo; 25
25 import gov.nasa.jpf.vm.BytecodeAnnotationInfo;
26 import gov.nasa.jpf.vm.BytecodeTypeParameterAnnotationInfo;
27 import gov.nasa.jpf.vm.ClassInfo;
28 import gov.nasa.jpf.vm.ClassLoaderInfo;
29 import gov.nasa.jpf.vm.ClassParseException;
30 import gov.nasa.jpf.vm.DirectCallStackFrame;
31 import gov.nasa.jpf.vm.ExceptionHandler;
32 import gov.nasa.jpf.vm.ExceptionParameterAnnotationInfo;
33 import gov.nasa.jpf.vm.FieldInfo;
34 import gov.nasa.jpf.vm.FormalParameterAnnotationInfo;
35 import gov.nasa.jpf.vm.GenericSignatureHolder;
36 import gov.nasa.jpf.vm.InfoObject;
37 import gov.nasa.jpf.vm.LocalVarInfo;
38 import gov.nasa.jpf.vm.MethodInfo;
39 import gov.nasa.jpf.vm.NativeMethodInfo;
40 import gov.nasa.jpf.vm.StackFrame;
41 import gov.nasa.jpf.vm.SuperTypeAnnotationInfo;
42 import gov.nasa.jpf.vm.ThreadInfo;
43 import gov.nasa.jpf.vm.ThrowsAnnotationInfo;
44 import gov.nasa.jpf.vm.TypeAnnotationInfo;
45 import gov.nasa.jpf.vm.TypeParameterAnnotationInfo;
46 import gov.nasa.jpf.vm.TypeParameterBoundAnnotationInfo;
47 import gov.nasa.jpf.vm.Types;
48 import gov.nasa.jpf.vm.VariableAnnotationInfo;
49 import java.lang.reflect.Modifier; 26 import java.lang.reflect.Modifier;
50 import java.util.HashMap; 27 import java.util.HashMap;
51 import java.util.LinkedHashMap; 28 import java.util.LinkedHashMap;
52 29
53 /** 30 /**
608 if (tag instanceof GenericSignatureHolder) { 585 if (tag instanceof GenericSignatureHolder) {
609 ((GenericSignatureHolder) tag).setGenericSignature(signature); 586 ((GenericSignatureHolder) tag).setGenericSignature(signature);
610 } 587 }
611 } 588 }
612 } 589 }
613 590
591 // since nested class init locking can explode the state space, we make it optional and controllable
592 protected static boolean nestedInit;
593 protected static StringSetMatcher includeNestedInit;
594 protected static StringSetMatcher excludeNestedInit;
595
596 protected static boolean init (Config config){
597 nestedInit = config.getBoolean("jvm.nested_init", false);
598 if (nestedInit){
599 includeNestedInit = StringSetMatcher.getNonEmpty(config.getStringArray("jvm.nested_init.include"));
600 excludeNestedInit = StringSetMatcher.getNonEmpty(config.getStringArray("jvm.nested_init.exclude"));
601 }
602
603 return true;
604 }
605
614 JVMClassInfo (String name, ClassLoaderInfo cli, ClassFile cf, String srcUrl, JVMCodeBuilder cb) throws ClassParseException { 606 JVMClassInfo (String name, ClassLoaderInfo cli, ClassFile cf, String srcUrl, JVMCodeBuilder cb) throws ClassParseException {
615 super( name, cli, srcUrl); 607 super( name, cli, srcUrl);
616 608
617 new Initializer( cf, cb); // we just need the ctor 609 new Initializer( cf, cb); // we just need the ctor
618 610
666 } catch (ClassParseException e) { 658 } catch (ClassParseException e) {
667 // we do not even get here - this a synthetic class, and at this point 659 // we do not even get here - this a synthetic class, and at this point
668 // the interfaces are already loaded. 660 // the interfaces are already loaded.
669 } 661 }
670 } 662 }
671 663
664 /**
665 * perform initialization of this class and its not-yet-initialized superclasses (top down),
666 * which includes calling clinit() methods
667 *
668 * This is overridden here to model a questionable yet consequential behavior of hotspot, which
669 * is holding derived class locks when initializing base classes. The generic implementation in
670 * ClassInfo uses non-nested locks (i.e. A.clinit() only synchronizes on A.class) and hence cannot
671 * produce the same static init deadlocks as hotspot. In order to catch such defects we implement
672 * nested locking here.
673 *
674 * The main difference is that the generic implementation only pushes DCSFs for required clinits
675 * and otherwise doesn't lock anything. Here, we create one static init specific DCSF which wraps
676 * all clinits in nested monitorenter/exits. We create this even if there is no clinit so that we
677 * mimic hotspot locking.
678 *
679 * Note this scheme also enables us to get rid of the automatic clinit sync (they don't have
680 * a 0x20 sync modifier in classfiles)
681 *
682 * @return true if client needs to re-execute because we pushed DirectCallStackFrames
683 */
684 @Override
685 public boolean initializeClass(ThreadInfo ti) {
686 if (needsInitialization(ti)) {
687 if (nestedInit && StringSetMatcher.isMatch(name, includeNestedInit, excludeNestedInit)) {
688 registerClass(ti); // this is recursively upwards
689 int nOps = 2 * (getNumberOfSuperClasses() + 1); // this is just an upper bound for the number of operands we need
690
691 MethodInfo miInitialize = new MethodInfo("[initializeClass]", "()V", Modifier.STATIC, 0, nOps);
692 JVMDirectCallStackFrame frame = new JVMDirectCallStackFrame(miInitialize, null);
693 JVMCodeBuilder cb = getSystemCodeBuilder(null, miInitialize);
694
695 addClassInit(ti, frame, cb); // this is recursively upwards until we hit a initialized superclass
696 cb.directcallreturn();
697 cb.installCode();
698
699 // this is normally initialized in the ctor, but at that point we don't have the code yet
700 frame.setPC(miInitialize.getFirstInsn());
701
702 ti.pushFrame(frame);
703 return true; // client has to re-execute, we pushed a stackframe
704
705
706 } else { // use generic initialization without nested locks (directly calling clinits)
707 return super.initializeClass(ti);
708 }
709
710 } else {
711 return false; // nothing to do
712 }
713 }
714
715 protected void addClassInit (ThreadInfo ti, JVMDirectCallStackFrame frame, JVMCodeBuilder cb){
716 int clsObjRef = getClassObjectRef();
717
718 frame.pushRef(clsObjRef);
719 cb.monitorenter();
720
721 if (superClass != null && superClass.needsInitialization(ti)) {
722 ((JVMClassInfo) superClass).addClassInit(ti, frame, cb); // go recursive
723 }
724
725 if (getMethod("<clinit>()V", false) != null) { // do we have a clinit
726 cb.invokeclinit(this);
727 } else {
728 cb.finishclinit(this);
729 // we can't just do call ci.setInitialized() since that has to be deferred
730 }
731
732 frame.pushRef(clsObjRef);
733 cb.monitorexit();
734 }
735
672 //--- call processing 736 //--- call processing
673 737
674 protected JVMCodeBuilder getSystemCodeBuilder (ClassFile cf, MethodInfo mi){ 738 protected JVMCodeBuilder getSystemCodeBuilder (ClassFile cf, MethodInfo mi){
675 JVMSystemClassLoaderInfo sysCl = (JVMSystemClassLoaderInfo) ClassLoaderInfo.getCurrentSystemClassLoader(); 739 JVMSystemClassLoaderInfo sysCl = (JVMSystemClassLoaderInfo) ClassLoaderInfo.getCurrentSystemClassLoader();
676 JVMCodeBuilder cb = sysCl.getSystemCodeBuilder(cf, mi); 740 JVMCodeBuilder cb = sysCl.getSystemCodeBuilder(cf, mi);