Mercurial > hg > Members > kono > jpf-core
view src/main/gov/nasa/jpf/util/json/JSONObject.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 | db918c531e6d |
children |
line wrap: on
line source
/* * Copyright (C) 2014, United States Government, as represented by the * Administrator of the National Aeronautics and Space Administration. * All rights reserved. * * The Java Pathfinder core (jpf-core) platform is licensed under the * Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gov.nasa.jpf.util.json; import gov.nasa.jpf.JPF; import gov.nasa.jpf.JPFException; import gov.nasa.jpf.util.JPFLogger; import gov.nasa.jpf.util.ObjectConverter; import gov.nasa.jpf.vm.ChoiceGenerator; import gov.nasa.jpf.vm.ClassInfo; import gov.nasa.jpf.vm.ClinitRequired; import gov.nasa.jpf.vm.ElementInfo; import gov.nasa.jpf.vm.FieldInfo; import gov.nasa.jpf.vm.Fields; import gov.nasa.jpf.vm.MJIEnv; import gov.nasa.jpf.vm.ThreadInfo; import java.util.HashMap; import java.util.Set; /** * Object parsed from JSON document. * @author Ivan Mushketik */ public class JSONObject{ private static final JPFLogger logger = JPF.getLogger("gov.nasa.jpf.util.json.JSONObject"); private HashMap<String, Value> keyValues = new HashMap<String, Value>(); private HashMap<String, CGCall> cgCalls = new HashMap<String, CGCall>(); void addValue(String key, Value value) { if (keyValues.containsKey(key)) { throw new JPFException("Attempt to add two nodes with the same key in JSON object"); } keyValues.put(key, value); } /** * Get value read from JSON document with specified key. * @param key - value's key. * @return read value. */ public Value getValue(String key) { return keyValues.get(key); } public String[] getValuesKeys() { Set<String> valuesKeys = keyValues.keySet(); String[] result = new String[keyValues.size()]; valuesKeys.toArray(result); return result; } public void addCGCall(String key, CGCall cgCall) { if (cgCalls.containsKey(key)) { throw new JPFException("Attempt to add two CG with the same key in JSON object"); } cgCalls.put(key, cgCall); } public CGCall getCGCall(String key) { return cgCalls.get(key); } public String[] getCGCallsKeys() { Set<String> cgKeys = cgCalls.keySet(); String[] result = new String[cgKeys.size()]; cgKeys.toArray(result); return result; } /** * check if all required ClassInfos for this object have been initialized so * that the caller can decide if it has to re-execute before proceeding * * NOTE - this currently does not support concrete field types that are subtypes * of the respective field types */ public boolean requiresClinitExecution (ClassInfo ci, ThreadInfo ti){ while (ci != null){ if (ci.initializeClass(ti)){ return true; } for (FieldInfo fi : ci.getDeclaredInstanceFields()) { ClassInfo ciField = fi.getTypeClassInfo(); if (requiresClinitExecution(ciField, ti)){ return true; } if (ciField.isArray()){ ClassInfo ciComp = ciField.getComponentClassInfo(); if (requiresClinitExecution(ciComp, ti)) { return true; } } } ci = ci.getSuperClass(); } return false; } //--- the fillers // NOTE - (pcm) before calling this method you have to make sure all required // types are initialized public int fillObject (MJIEnv env, ClassInfo ci, ChoiceGenerator<?>[] cgs, String prefix) throws ClinitRequired { int newObjRef = env.newObject(ci); ElementInfo ei = env.getHeap().getModifiable(newObjRef); // Fill all fields for this class until it has a super class while (ci != null) { FieldInfo[] fields = ci.getDeclaredInstanceFields(); for (FieldInfo fi : fields) { String fieldName = fi.getName(); Value val = getValue(fieldName); CGCall cgCall = getCGCall(fieldName); // If a value was defined in JSON document if (val != null) { fillFromValue(fi, ei, val, env, cgs, prefix); } else if (cgCall != null) { // Value of this field should be taken from CG String cgId = prefix + fieldName; ChoiceGenerator<?> cg = getCGByID(cgs, cgId); assert cg != null : "Expected CG with id " + cgId; Object cgResult = cg.getNextChoice(); if (!fi.isReference()) { convertPrimititve(ei, fi, cgResult); } else { int newFieldRef = ObjectConverter.JPFObjectFromJavaObject(env, cgResult); ei.setReferenceField(fi, newFieldRef); } } else { logger.warning("Value for field ", fi.getFullName(), " isn't specified"); } } ci = ci.getSuperClass(); } return newObjRef; } private void fillFromValue(FieldInfo fi, ElementInfo ei, Value val, MJIEnv env, ChoiceGenerator<?>[] cgs, String prefix) { String fieldName = fi.getName(); // Handle primitive types if (!fi.isReference()) { fillPrimitive(ei, fi, val); } else { if (isArrayType(fi.getType())) { int newArrRef = createArray(env, fi.getTypeClassInfo(), val, cgs, prefix + fieldName); ei.setReferenceField(fi, newArrRef); } else { Creator creator = CreatorsFactory.getCreator(fi.getType()); if (creator != null) { int newSubObjRef = creator.create(env, fi.getType(), val); ei.setReferenceField(fi, newSubObjRef); } else { // Not a special case. Fill it recursively ClassInfo ciField = fi.getTypeClassInfo(); if (ciField.initializeClass(env.getThreadInfo())){ throw new ClinitRequired(ciField); } JSONObject jsonObj = val.getObject(); int fieldRef = MJIEnv.NULL; if (jsonObj != null) { fieldRef = jsonObj.fillObject(env, ciField, cgs, prefix + fieldName); } ei.setReferenceField(fi.getName(), fieldRef); } } } } private static void fillPrimitive(ElementInfo ei, FieldInfo fi, Value val) { String primitiveName = fi.getType(); if (primitiveName.equals("boolean")) { ei.setBooleanField(fi, val.getBoolean()); } else if (primitiveName.equals("byte")) { ei.setByteField(fi, val.getDouble().byteValue()); } else if (primitiveName.equals("short")) { ei.setShortField(fi, val.getDouble().shortValue()); } else if (primitiveName.equals("int")) { ei.setIntField(fi, val.getDouble().intValue()); } else if (primitiveName.equals("long")) { ei.setLongField(fi, val.getDouble().longValue()); } else if (primitiveName.equals("float")) { ei.setFloatField(fi, val.getDouble().floatValue()); } else if (primitiveName.equals("double")) { ei.setDoubleField(fi, val.getDouble()); } } public int createArray(MJIEnv env, ClassInfo ciArray, Value value, ChoiceGenerator<?>[] cgs, String prefix) { Value vals[] = value.getArray(); ClassInfo ciElement = ciArray.getComponentClassInfo(); String arrayElementType = ciElement.getName(); int arrayRef; // Handle arrays of primitive types if (arrayElementType.equals("boolean")) { arrayRef = env.newBooleanArray(vals.length); ElementInfo arrayEI = env.getHeap().getModifiable(arrayRef); boolean bools[] = arrayEI.asBooleanArray(); for (int i = 0; i < vals.length; i++) { bools[i] = vals[i].getBoolean(); } } else if (arrayElementType.equals("byte")) { arrayRef = env.newByteArray(vals.length); ElementInfo arrayEI = env.getHeap().getModifiable(arrayRef); byte bytes[] = arrayEI.asByteArray(); for (int i = 0; i < vals.length; i++) { bytes[i] = vals[i].getDouble().byteValue(); } } else if (arrayElementType.equals("short")) { arrayRef = env.newShortArray(vals.length); ElementInfo arrayEI = env.getHeap().getModifiable(arrayRef); short shorts[] = arrayEI.asShortArray(); for (int i = 0; i < vals.length; i++) { shorts[i] = vals[i].getDouble().shortValue(); } } else if (arrayElementType.equals("int")) { arrayRef = env.newIntArray(vals.length); ElementInfo arrayEI = env.getHeap().getModifiable(arrayRef); int[] ints = arrayEI.asIntArray(); for (int i = 0; i < vals.length; i++) { ints[i] = vals[i].getDouble().intValue(); } } else if (arrayElementType.equals("long")) { arrayRef = env.newLongArray(vals.length); ElementInfo arrayEI = env.getHeap().getModifiable(arrayRef); long[] longs = arrayEI.asLongArray(); for (int i = 0; i < vals.length; i++) { longs[i] = vals[i].getDouble().longValue(); } } else if (arrayElementType.equals("float")) { arrayRef = env.newFloatArray(vals.length); ElementInfo arrayEI = env.getHeap().getModifiable(arrayRef); float[] floats = arrayEI.asFloatArray(); for (int i = 0; i < vals.length; i++) { floats[i] = vals[i].getDouble().floatValue(); } } else if (arrayElementType.equals("double")) { arrayRef = env.newDoubleArray(vals.length); ElementInfo arrayEI = env.getHeap().getModifiable(arrayRef); double[] doubles = arrayEI.asDoubleArray(); for (int i = 0; i < vals.length; i++) { doubles[i] = vals[i].getDouble(); } } else { // Not an array of primitive types arrayRef = env.newObjectArray(arrayElementType, vals.length); ElementInfo arrayEI = env.getModifiableElementInfo(arrayRef); Fields fields = arrayEI.getFields(); Creator creator = CreatorsFactory.getCreator(arrayElementType); for (int i = 0; i < vals.length; i++) { int newObjRef; if (creator != null) { newObjRef = creator.create(env, arrayElementType, vals[i]); } else{ if (isArrayType(arrayElementType)) { newObjRef = createArray(env, ciElement, vals[i], cgs, prefix + "[" + i); } else { JSONObject jsonObj = vals[i].getObject(); if (jsonObj != null) { newObjRef = jsonObj.fillObject(env, ciElement, cgs, prefix + "[" + i); } else { newObjRef = MJIEnv.NULL; } } } fields.setReferenceValue(i, newObjRef); } } return arrayRef; } private boolean isArrayType(String typeName) { return typeName.lastIndexOf('[') >= 0; } /** * This is method is used to set field of primitive type from CG result object * @param ei - ElementInfo to set field in * @param fi - FieldInfo of a field we want to set * @param cgResult - result of CG call */ private void convertPrimititve(ElementInfo ei, FieldInfo fi, Object cgResult) { String primitiveName = fi.getType(); if (primitiveName.equals("boolean") && cgResult instanceof Boolean) { Boolean bool = (Boolean) cgResult; ei.setBooleanField(fi, bool.booleanValue()); } else if (cgResult instanceof Number) { Number number = (Number) cgResult; if (primitiveName.equals("byte")) { ei.setByteField(fi, number.byteValue()); } else if (primitiveName.equals("short")) { ei.setShortField(fi, number.shortValue()); } else if (primitiveName.equals("int")) { ei.setIntField(fi, number.intValue()); } else if (primitiveName.equals("long")) { ei.setLongField(fi, number.longValue()); } else if (primitiveName.equals("float")) { ei.setFloatField(fi, number.floatValue()); } else if (primitiveName.equals("double")) { ei.setDoubleField(fi, number.doubleValue()); } } else if (cgResult instanceof Character) { Character c = (Character) cgResult; ei.setCharField(fi, c); } else { throw new JPFException("Can't convert " + cgResult.getClass().getCanonicalName() + " to " + primitiveName); } } /** * Get CG from current state CG list by it's ID * @param cgs - array of CG from current state * @param id - id of the CG that we search for * @return - CG with a specified id or null if no id with such name found */ private ChoiceGenerator<?> getCGByID(ChoiceGenerator<?>[] cgs, String id) { if (cgs == null) { return null; } for (int i = 0; i < cgs.length; i++) { if (cgs[i].getId().equals(id)) { return cgs[i]; } } return null; } }