Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Index annotations #2

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
31 changes: 30 additions & 1 deletion gson/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,44 @@
<name>Gson</name>

<dependencies>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>11</release>
<compilerArguments>
<Xmaxerrs>10000</Xmaxerrs>
<Xmaxwarns>10000</Xmaxwarns>
</compilerArguments>
<annotationProcessorPaths>
<path>
<groupId>org.checkerframework</groupId>
<artifactId>checker</artifactId>
<version>3.4.0</version>
</path>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>org.checkerframework.checker.index.IndexChecker</annotationProcessor>
</annotationProcessors>
<compilerArgs>
<arg>-AprintErrorStack</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
Expand Down
9 changes: 7 additions & 2 deletions gson/src/main/java/com/google/gson/FieldNamingPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.lang.reflect.Field;
import java.util.Locale;
import org.checkerframework.checker.index.qual.LTLengthOf;

/**
* An enumeration that defines a few standard naming conventions for JSON field names.
Expand Down Expand Up @@ -158,8 +159,12 @@ static String separateCamelCase(String name, String separator) {
/**
* Ensures the JSON field names begins with an upper case letter.
*/
/*firstLetterIndex is assigned with 0 #1, length of a field name would be atleast 1,
substring(1) may be out or range if length of field is 1
though no exception is thrown by function substring*/
@SuppressWarnings({"index:assignment.type.incompatible","index:argument.type.incompatible"})
static String upperCaseFirstLetter(String name) {
int firstLetterIndex = 0;
@LTLengthOf(value="name") int firstLetterIndex = 0; //#1
int limit = name.length() - 1;
for(; !Character.isLetter(name.charAt(firstLetterIndex)) && firstLetterIndex < limit; ++firstLetterIndex);

Expand All @@ -170,7 +175,7 @@ static String upperCaseFirstLetter(String name) {

char uppercased = Character.toUpperCase(firstLetter);
if(firstLetterIndex == 0) { //First character in the string is the first letter, saves 1 substring
return uppercased + name.substring(1);
return uppercased + name.substring(1); //#2
}

return name.substring(0, firstLetterIndex) + uppercased + name.substring(firstLetterIndex + 1);
Expand Down
4 changes: 3 additions & 1 deletion gson/src/main/java/com/google/gson/JsonArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.checkerframework.checker.index.qual.NonNegative;

/**
* A class representing an array type in Json. An array is a list of {@link JsonElement}s each of
Expand All @@ -40,7 +41,7 @@ public JsonArray() {
elements = new ArrayList<JsonElement>();
}

public JsonArray(int capacity) {
public JsonArray(@NonNegative int capacity) {
elements = new ArrayList<JsonElement>(capacity);
}

Expand Down Expand Up @@ -391,3 +392,4 @@ public int hashCode() {
return elements.hashCode();
}
}

4 changes: 3 additions & 1 deletion gson/src/main/java/com/google/gson/JsonPrimitive.java
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,11 @@ public byte getAsByte() {
return isNumber() ? getAsNumber().byteValue() : Byte.parseByte(getAsString());
}

//getAsString returns a String of minimum length 1, charAt(0) is safe
@SuppressWarnings("index:argument.type.incompatible")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why using a @MinLen annotation to express what you said here doesn't work?

@Override
public char getAsCharacter() {
return getAsString().charAt(0);
return getAsString().charAt(0); //#1
}

@Override
Expand Down
62 changes: 43 additions & 19 deletions gson/src/main/java/com/google/gson/internal/$Gson$Types.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;

import org.checkerframework.common.value.qual.ArrayLen;
import org.checkerframework.common.value.qual.MinLen;
import org.checkerframework.checker.index.qual.NonNegative;

/**
* Static methods for working with types.
*
Expand Down Expand Up @@ -70,10 +74,12 @@ public static GenericArrayType arrayOf(Type componentType) {
* {@code ? extends CharSequence}. If {@code bound} is {@code Object.class},
* this returns {@code ?}, which is shorthand for {@code ? extends Object}.
*/
/*Missing annotation in JDK in @MinLen(1) getUpperBounds() of WildcardType #2*/
@SuppressWarnings("assignment.type.incompatible")
public static WildcardType subtypeOf(Type bound) {
Type[] upperBounds;
Type @MinLen(1)[] upperBounds;
if (bound instanceof WildcardType) {
upperBounds = ((WildcardType) bound).getUpperBounds();
upperBounds = ((WildcardType) bound).getUpperBounds(); //#2
} else {
upperBounds = new Type[] { bound };
}
Expand All @@ -100,6 +106,8 @@ public static WildcardType supertypeOf(Type bound) {
* according to {@link Object#equals(Object) Object.equals()}. The returned
* type is {@link java.io.Serializable}.
*/
/*Missing annotation in JDK in @MinLen(1) getUpperBounds() of WildcardType #3*/
@SuppressWarnings("argument.type.incompatible")
public static Type canonicalize(Type type) {
if (type instanceof Class) {
Class<?> c = (Class<?>) type;
Expand All @@ -116,14 +124,15 @@ public static Type canonicalize(Type type) {

} else if (type instanceof WildcardType) {
WildcardType w = (WildcardType) type;
return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds()); //#3

} else {
// type is either serializable as-is or unsupported
return type;
}
}

/*Missing annotation in JDK in @MinLen(1) getUpperBounds() of WildcardType #4*/
@SuppressWarnings("array.access.unsafe.high.constant")
public static Class<?> getRawType(Type type) {
if (type instanceof Class<?>) {
// type is a normal class.
Expand All @@ -149,7 +158,7 @@ public static Class<?> getRawType(Type type) {
return Object.class;

} else if (type instanceof WildcardType) {
return getRawType(((WildcardType) type).getUpperBounds()[0]);
return getRawType(((WildcardType) type).getUpperBounds()[0]); //#4

} else {
String className = type == null ? "null" : type.getClass().getName();
Expand Down Expand Up @@ -233,19 +242,22 @@ public static String typeToString(Type type) {
* IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set<Integer>} and the
* result when the supertype is {@code Collection.class} is {@code Collection<Integer>}.
*/
/*getGenericInterfaces and getInterfaces #7 return the same length array therefore #8 #9
i which is an index of `interfaces` can be used for the returned array of getGenericInterfaces*/
@SuppressWarnings("array.access.unsafe.high")
static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) {
if (toResolve == rawType) {
return context;
}

// we skip searching through interfaces if unknown is an interface
if (toResolve.isInterface()) {
Class<?>[] interfaces = rawType.getInterfaces();
Class<?>[] interfaces = rawType.getInterfaces(); //#7
for (int i = 0, length = interfaces.length; i < length; i++) {
if (interfaces[i] == toResolve) {
return rawType.getGenericInterfaces()[i];
return rawType.getGenericInterfaces()[i]; //#8
} else if (toResolve.isAssignableFrom(interfaces[i])) {
return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); //#9
}
}
}
Expand Down Expand Up @@ -274,10 +286,12 @@ static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResol
*
* @param supertype a superclass of, or interface implemented by, this.
*/
/*Missing annotation in JDK in @MinLen(1) getUpperBounds() of WildcardType #5*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to delete these comments once missing JDK annotations are added.

@SuppressWarnings("array.access.unsafe.high.constant")
static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
if (context instanceof WildcardType) {
// wildcards are useless for resolving supertypes. As the upper bound has the same raw type, use it instead
context = ((WildcardType)context).getUpperBounds()[0];
context = ((WildcardType)context).getUpperBounds()[0]; //#5
}
checkArgument(supertype.isAssignableFrom(contextRawType));
return resolve(context, contextRawType,
Expand All @@ -298,11 +312,13 @@ public static Type getArrayComponentType(Type array) {
* Returns the element type of this collection type.
* @throws IllegalArgumentException if this type is not a collection.
*/
/*Missing annotation in JDK in @MinLen(1) getUpperBounds() of WildcardType #6*/
@SuppressWarnings("array.access.unsafe.high.constant")
public static Type getCollectionElementType(Type context, Class<?> contextRawType) {
Type collectionType = getSupertype(context, contextRawType, Collection.class);

if (collectionType instanceof WildcardType) {
collectionType = ((WildcardType)collectionType).getUpperBounds()[0];
collectionType = ((WildcardType)collectionType).getUpperBounds()[0]; //#6
}
if (collectionType instanceof ParameterizedType) {
return ((ParameterizedType) collectionType).getActualTypeArguments()[0];
Expand All @@ -314,7 +330,9 @@ public static Type getCollectionElementType(Type context, Class<?> contextRawTyp
* Returns a two element array containing this map's key and value types in
* positions 0 and 1 respectively.
*/
public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {
//Map takes two parameters therefore returned array is of length 2 in #1
@SuppressWarnings("return.type.incompatible")
public static Type @ArrayLen(2)[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {
/*
* Work around a problem with the declaration of java.util.Properties. That
* class should extend Hashtable<String, String>, but it's declared to
Expand All @@ -328,7 +346,7 @@ public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawTyp
// TODO: strip wildcards?
if (mapType instanceof ParameterizedType) {
ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
return mapParameterizedType.getActualTypeArguments();
return mapParameterizedType.getActualTypeArguments(); //#1
}
return new Type[] { Object.class, Object.class };
}
Expand Down Expand Up @@ -416,6 +434,10 @@ private static Type resolve(Type context, Class<?> contextRawType, Type toResolv
}
}

/*as getTypeParameters and getTypeArguments return an array of same length
* `index` being an index of getTypeParameters's array is also a valid valid index
* for getActualTypeArguments's array #10*/
@SuppressWarnings({"array.access.unsafe.high"})
static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
Class<?> declaredByRaw = declaringClassOf(unknown);

Expand All @@ -426,14 +448,14 @@ static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVaria

Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);
if (declaredBy instanceof ParameterizedType) {
int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
return ((ParameterizedType) declaredBy).getActualTypeArguments()[index];
@NonNegative int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; //#10
}

return unknown;
}

private static int indexOf(Object[] array, Object toFind) {
private static @NonNegative int indexOf(Object[] array, Object toFind) {
for (int i = 0, length = array.length; i < length; i++) {
if (toFind.equals(array[i])) {
return i;
Expand Down Expand Up @@ -504,14 +526,16 @@ public Type getOwnerType() {
^ hashCodeOrZero(ownerType);
}

//#11 already ensures typeArguments.length is not 0 when it reaches #12
@SuppressWarnings("array.access.unsafe.high.constant")
@Override public String toString() {
int length = typeArguments.length;
if (length == 0) {
if (length == 0) { //#11
return typeToString(rawType);
}

StringBuilder stringBuilder = new StringBuilder(30 * (length + 1));
stringBuilder.append(typeToString(rawType)).append("<").append(typeToString(typeArguments[0]));
stringBuilder.append(typeToString(rawType)).append("<").append(typeToString(typeArguments[0])); //#12
for (int i = 1; i < length; i++) {
stringBuilder.append(", ").append(typeToString(typeArguments[i]));
}
Expand Down Expand Up @@ -557,7 +581,7 @@ private static final class WildcardTypeImpl implements WildcardType, Serializabl
private final Type upperBound;
private final Type lowerBound;

public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
public WildcardTypeImpl(Type @MinLen(1)[] upperBounds, Type[] lowerBounds) {
checkArgument(lowerBounds.length <= 1);
checkArgument(upperBounds.length == 1);

Expand All @@ -576,7 +600,7 @@ public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
}
}

public Type[] getUpperBounds() {
public Type @MinLen(1)[] getUpperBounds() {
return new Type[] { upperBound };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType)
* Constructors for common interface types like Map and List and their
* subtypes.
*/
@SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
//getActualTypeArguments may return an empty array #1 #2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is this a possible bug in Gson?

@SuppressWarnings({"unchecked","array.access.unsafe.high.constant"}) // use runtime checks to guarantee that 'T' is what it is
private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
final Type type, Class<? super T> rawType) {
if (Collection.class.isAssignableFrom(rawType)) {
Expand All @@ -145,7 +146,7 @@ private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
@SuppressWarnings("rawtypes")
@Override public T construct() {
if (type instanceof ParameterizedType) {
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; //#1
if (elementType instanceof Class) {
return (T) EnumSet.noneOf((Class)elementType);
} else {
Expand Down Expand Up @@ -197,7 +198,7 @@ private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
}
};
} else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom(
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) {
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) { //#2
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new LinkedHashMap<Object, Object>();
Expand Down
Loading