Mercurial > hg > Members > kono > jpf-core
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); |