[java] Fix race conditions in JavaTypeDefinitionSimple

- The data is now a fixed-size array instead of a fixed-size arraylist,
this removes the need to manually add null elements all over.
 - This in turn avoids the resizing / runtime exceptions under
multithreaded accesses.
 - Take the change to fix the way `isGeneric` is resolved to be
consistent over time and correct.
 - Fixes #1691
This commit is contained in:
Juan Martín Sotuyo Dodero
2019-04-12 14:23:13 -03:00
parent 595c440465
commit 12afff4f5b

View File

@ -16,8 +16,7 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable; import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType; import java.lang.reflect.WildcardType;
import java.util.ArrayList; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -28,7 +27,7 @@ import java.util.logging.Logger;
/* default */ class JavaTypeDefinitionSimple extends JavaTypeDefinition { /* default */ class JavaTypeDefinitionSimple extends JavaTypeDefinition {
private final Class<?> clazz; private final Class<?> clazz;
private final List<JavaTypeDefinition> genericArgs; private final JavaTypeDefinition[] genericArgs;
// cached because calling clazz.getTypeParameters().length create a new array every time // cached because calling clazz.getTypeParameters().length create a new array every time
private final int typeParameterCount; private final int typeParameterCount;
private final boolean isGeneric; private final boolean isGeneric;
@ -36,6 +35,7 @@ import java.util.logging.Logger;
private final JavaTypeDefinition enclosingClass; private final JavaTypeDefinition enclosingClass;
private static final Logger LOG = Logger.getLogger(JavaTypeDefinitionSimple.class.getName()); private static final Logger LOG = Logger.getLogger(JavaTypeDefinitionSimple.class.getName());
private static final JavaTypeDefinition[] NO_GENERICS = {};
protected JavaTypeDefinitionSimple(Class<?> clazz, JavaTypeDefinition... boundGenerics) { protected JavaTypeDefinitionSimple(Class<?> clazz, JavaTypeDefinition... boundGenerics) {
super(EXACT); super(EXACT);
@ -59,12 +59,10 @@ import java.util.logging.Logger;
isRawType = isGeneric && boundGenerics.length == 0; isRawType = isGeneric && boundGenerics.length == 0;
if (isGeneric) { if (isGeneric) {
// Generics will be lazily loaded // Generics will be lazily loaded if not already known
this.genericArgs = new ArrayList<>(typeParameters.length); this.genericArgs = Arrays.copyOf(boundGenerics, typeParameterCount);
// boundGenerics would be empty if this is a raw type, hence the lazy loading
Collections.addAll(this.genericArgs, boundGenerics);
} else { } else {
this.genericArgs = Collections.emptyList(); this.genericArgs = NO_GENERICS;
} }
enclosingClass = forClass(clazz.getEnclosingClass()); enclosingClass = forClass(clazz.getEnclosingClass());
@ -82,7 +80,7 @@ import java.util.logging.Logger;
@Override @Override
public boolean isGeneric() { public boolean isGeneric() {
return !genericArgs.isEmpty(); return isGeneric;
} }
private JavaTypeDefinition getGenericType(final String parameterName, Method method, private JavaTypeDefinition getGenericType(final String parameterName, Method method,
@ -126,29 +124,22 @@ import java.util.logging.Logger;
@Override @Override
public JavaTypeDefinition getGenericType(final int index) { public JavaTypeDefinition getGenericType(final int index) {
// Check if it has been lazily initialized first // Check if it has been lazily initialized first
if (genericArgs.size() > index) { final JavaTypeDefinition cachedDefinition = genericArgs[index];
final JavaTypeDefinition cachedDefinition = genericArgs.get(index);
if (cachedDefinition != null) { if (cachedDefinition != null) {
return cachedDefinition; return cachedDefinition;
} }
}
// Force the list to have enough elements
for (int i = genericArgs.size(); i <= index; i++) {
genericArgs.add(null);
}
/* /*
* Set a default to circuit-brake any recursions (ie: raw types with no generic info) * Set a default to circuit-brake any recursions (ie: raw types with no generic info)
* Object.class is a right answer in those scenarios * Object.class is a right answer in those scenarios
*/ */
genericArgs.set(index, forClass(Object.class)); genericArgs[index] = forClass(Object.class);
final TypeVariable<?> typeVariable = clazz.getTypeParameters()[index]; final TypeVariable<?> typeVariable = clazz.getTypeParameters()[index];
final JavaTypeDefinition typeDefinition = resolveTypeDefinition(typeVariable.getBounds()[0]); final JavaTypeDefinition typeDefinition = resolveTypeDefinition(typeVariable.getBounds()[0]);
// cache result // cache result
genericArgs.set(index, typeDefinition); genericArgs[index] = typeDefinition;
return typeDefinition; return typeDefinition;
} }
@ -279,7 +270,7 @@ import java.util.logging.Logger;
.append(", genericArgs=["); .append(", genericArgs=[");
// Forcefully resolve all generic types // Forcefully resolve all generic types
for (int i = 0; i < genericArgs.size(); i++) { for (int i = 0; i < genericArgs.length; i++) {
getGenericType(i); getGenericType(i);
} }
@ -287,7 +278,7 @@ import java.util.logging.Logger;
sb.append(jtd.shallowString()).append(", "); sb.append(jtd.shallowString()).append(", ");
} }
if (!genericArgs.isEmpty()) { if (genericArgs.length != 0) {
sb.replace(sb.length() - 3, sb.length() - 1, ""); // remove last comma sb.replace(sb.length() - 3, sb.length() - 1, ""); // remove last comma
} }