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

Nullness annotations to gson [review 1] #1

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.nullness.NullnessChecker</annotationProcessor>
</annotationProcessors>
<compilerArgs>
<arg>-AprintErrorStack</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
* This type adapter supports three subclasses of date: Date, Timestamp, and
Expand Down Expand Up @@ -120,7 +121,7 @@ public void write(JsonWriter out, Date value) throws IOException {
}

@Override
public Date read(JsonReader in) throws IOException {
public @Nullable Date read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
Expand Down
5 changes: 3 additions & 2 deletions gson/src/main/java/com/google/gson/FieldAttributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
* A data object that stores attributes of a field.
Expand Down Expand Up @@ -107,7 +108,7 @@ public Class<?> getDeclaredClass() {
* @param annotation the class of the annotation that will be retrieved
* @return the annotation instance if it is bound to the field; otherwise {@code null}
*/
public <T extends Annotation> T getAnnotation(Class<T> annotation) {
public @Nullable <T extends Annotation> T getAnnotation(Class<T> annotation) {
return field.getAnnotation(annotation);
}

Expand Down Expand Up @@ -146,7 +147,7 @@ public boolean hasModifier(int modifier) {
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
Object get(Object instance) throws IllegalAccessException {
@Nullable Object get(Object instance) throws IllegalAccessException {
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the documentation it is not specified that the return result can be null. This annotation is forced here because the method get(Object) in java.lang.reflect has its return type annotated with @Nullable. I am not sure if this annotation is correct, since get from jdk.internal.reflect.FieldAccessor.java is not annotated as @Nullable.

return field.get(instance);
}

Expand Down
57 changes: 34 additions & 23 deletions gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.gson.stream.MalformedJsonException;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
* This is the main class for using Gson. Gson is typically used by first constructing a
Expand Down Expand Up @@ -121,8 +122,8 @@ public final class Gson {
* lookup would stack overflow. We cheat by returning a proxy type adapter.
* The proxy is wired up once the initial adapter has been created.
*/
private final ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>> calls
= new ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>>();
private final ThreadLocal<@Nullable Map<TypeToken<?>, FutureTypeAdapter<?>>> calls
= new ThreadLocal<@Nullable Map<TypeToken<?>, FutureTypeAdapter<?>>>();

private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<TypeToken<?>, TypeAdapter<?>>();

Expand All @@ -141,7 +142,7 @@ public final class Gson {
final boolean prettyPrinting;
final boolean lenient;
final boolean serializeSpecialFloatingPointValues;
final String datePattern;
final @Nullable String datePattern;
final int dateStyle;
final int timeStyle;
final LongSerializationPolicy longSerializationPolicy;
Expand Down Expand Up @@ -192,11 +193,13 @@ public Gson() {
Collections.<TypeAdapterFactory>emptyList());
}

/*dangerous to call an instance method inside a constructor, framework could identify #1 and #2 out of 4 such errors*/
@SuppressWarnings("method.invocation.invalid")
Copy link
Collaborator

Choose a reason for hiding this comment

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

The errors you suppressed are InitializationChecker errors. You can use this when doing nullness case studies. Can you make sure adding such annotations still produce method.invocation.invalid errors?

Copy link
Author

Choose a reason for hiding this comment

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

@Maxi17 I could not completely understand this comment. Adding which annotations still produce this error?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am saying that the errors you are suppressing are because of InitializationChecker. What I'm asking is if you add InitializationChecker annotations (link @Initialized) works here.

Copy link
Author

Choose a reason for hiding this comment

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

Annotation needs to be added to the current object or this which is not possible, therefore the suppression

Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
LongSerializationPolicy longSerializationPolicy, @Nullable String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories,
List<TypeAdapterFactory> builderHierarchyFactories,
List<TypeAdapterFactory> factoriesToBeAdded) {
Expand Down Expand Up @@ -239,9 +242,9 @@ public Gson() {
TypeAdapter<Number> longAdapter = longAdapter(longSerializationPolicy);
factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));
factories.add(TypeAdapters.newFactory(double.class, Double.class,
doubleAdapter(serializeSpecialFloatingPointValues)));
doubleAdapter(serializeSpecialFloatingPointValues))); //#1
factories.add(TypeAdapters.newFactory(float.class, Float.class,
floatAdapter(serializeSpecialFloatingPointValues)));
floatAdapter(serializeSpecialFloatingPointValues))); //#2
factories.add(TypeAdapters.NUMBER_FACTORY);
factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);
factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);
Expand Down Expand Up @@ -311,7 +314,7 @@ private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointV
return TypeAdapters.DOUBLE;
}
return new TypeAdapter<Number>() {
@Override public Double read(JsonReader in) throws IOException {
@Override public @Nullable Double read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
Expand All @@ -335,7 +338,7 @@ private TypeAdapter<Number> floatAdapter(boolean serializeSpecialFloatingPointVa
return TypeAdapters.FLOAT;
}
return new TypeAdapter<Number>() {
@Override public Float read(JsonReader in) throws IOException {
@Override public @Nullable Float read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
Expand Down Expand Up @@ -367,7 +370,7 @@ private static TypeAdapter<Number> longAdapter(LongSerializationPolicy longSeria
return TypeAdapters.LONG;
}
return new TypeAdapter<Number>() {
@Override public Number read(JsonReader in) throws IOException {
@Override public @Nullable Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
Expand All @@ -389,8 +392,12 @@ private static TypeAdapter<AtomicLong> atomicLongAdapter(final TypeAdapter<Numbe
@Override public void write(JsonWriter out, AtomicLong value) throws IOException {
longAdapter.write(out, value.get());
}
/*The method is private and the developers ensure to call the below
* function with an `in` that returns non null value #3, therefore code
* is safe*/
@SuppressWarnings("dereference.of.nullable")
Copy link
Collaborator

Choose a reason for hiding this comment

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

The method is private. Still, can you verify that it is not possible to throw a NullPointerException internally?

Copy link
Author

Choose a reason for hiding this comment

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

No NullPointerException is thrown internally but as the method is private developers ensure the concerned read function does not return null so the code is safe

@Override public AtomicLong read(JsonReader in) throws IOException {
Number value = longAdapter.read(in);
Number value = longAdapter.read(in); //#3
return new AtomicLong(value.longValue());
}
}.nullSafe();
Expand All @@ -405,12 +412,14 @@ private static TypeAdapter<AtomicLongArray> atomicLongArrayAdapter(final TypeAda
}
out.endArray();
}
/*in.hasNext() ensures read(in) doesnt return null in #4*/
@SuppressWarnings({"dereference.of.nullable"})
@Override public AtomicLongArray read(JsonReader in) throws IOException {
List<Long> list = new ArrayList<Long>();
in.beginArray();
while (in.hasNext()) {
while (in.hasNext()) { //#4
long value = longAdapter.read(in).longValue();
list.add(value);
list.add(value); //#5
}
in.endArray();
int length = list.size();
Expand Down Expand Up @@ -522,7 +531,9 @@ public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
*
* @since 2.2
*/
public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
// Possible missing annotation in class List method contains(@Nullable Object)
@SuppressWarnings("nullness:argument.type.incompatible")
public <T> TypeAdapter<T> getDelegateAdapter(@Nullable TypeAdapterFactory skipPast, TypeToken<T> type) {
// Hack. If the skipPast factory isn't registered, assume the factory is being requested via
// our @JsonAdapter annotation.
if (!factories.contains(skipPast)) {
Expand Down Expand Up @@ -813,7 +824,7 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce
* @throws JsonSyntaxException if json is not a valid representation for an object of type
* classOfT
*/
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
public @Nullable <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
Expand All @@ -838,7 +849,7 @@ public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException
* @throws JsonSyntaxException if json is not a valid representation for an object of type
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
public @Nullable <T> T fromJson(@Nullable String json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
Expand All @@ -865,7 +876,7 @@ public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
* @throws JsonSyntaxException if json is not a valid representation for an object of type
* @since 1.2
*/
public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException, JsonIOException {
public @Nullable <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException, JsonIOException {
JsonReader jsonReader = newJsonReader(json);
Object object = fromJson(jsonReader, classOfT);
assertFullConsumption(object, jsonReader);
Expand All @@ -892,14 +903,14 @@ public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException
* @since 1.2
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
public @Nullable <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
JsonReader jsonReader = newJsonReader(json);
T object = (T) fromJson(jsonReader, typeOfT);
assertFullConsumption(object, jsonReader);
return object;
}
PRITI1999 marked this conversation as resolved.
Show resolved Hide resolved

private static void assertFullConsumption(Object obj, JsonReader reader) {
private static void assertFullConsumption(@Nullable Object obj, JsonReader reader) {
try {
if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
Expand All @@ -920,7 +931,7 @@ private static void assertFullConsumption(Object obj, JsonReader reader) {
* @throws JsonSyntaxException if json is not a valid representation for an object of type
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
public @Nullable <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(true);
Expand Down Expand Up @@ -971,7 +982,7 @@ public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J
* @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT
* @since 1.3
*/
public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
public @Nullable <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
Expand All @@ -996,15 +1007,15 @@ public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxExce
* @since 1.3
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {
public @Nullable <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
return (T) fromJson(new JsonTreeReader(json), typeOfT);
}

static class FutureTypeAdapter<T> extends TypeAdapter<T> {
private TypeAdapter<T> delegate;
private @Nullable TypeAdapter<T> delegate;

public void setDelegate(TypeAdapter<T> typeAdapter) {
if (delegate != null) {
Expand All @@ -1013,7 +1024,7 @@ public void setDelegate(TypeAdapter<T> typeAdapter) {
delegate = typeAdapter;
}

@Override public T read(JsonReader in) throws IOException {
@Override public @Nullable T read(JsonReader in) throws IOException {
if (delegate == null) {
throw new IllegalStateException();
}
Expand Down
5 changes: 3 additions & 2 deletions gson/src/main/java/com/google/gson/GsonBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT;
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
Expand Down Expand Up @@ -85,7 +86,7 @@ public final class GsonBuilder {
/** tree-style hierarchy factories. These come after factories for backwards compatibility. */
private final List<TypeAdapterFactory> hierarchyFactories = new ArrayList<TypeAdapterFactory>();
private boolean serializeNulls = DEFAULT_SERIALIZE_NULLS;
private String datePattern;
private @Nullable String datePattern;
private int dateStyle = DateFormat.DEFAULT;
private int timeStyle = DateFormat.DEFAULT;
private boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS;
Expand Down Expand Up @@ -603,7 +604,7 @@ public Gson create() {
}

@SuppressWarnings("unchecked")
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
private void addTypeAdaptersForDate(@Nullable String datePattern, int dateStyle, int timeStyle,
List<TypeAdapterFactory> factories) {
DefaultDateTypeAdapter dateTypeAdapter;
TypeAdapter<Timestamp> timestampTypeAdapter;
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 @@ -22,6 +22,8 @@
import java.util.Iterator;
import java.util.List;

import org.checkerframework.checker.nullness.qual.Nullable;

/**
* A class representing an array type in Json. An array is a list of {@link JsonElement}s each of
* which can be of a different type. This is an ordered list, meaning that the order in which
Expand Down Expand Up @@ -382,7 +384,7 @@ public boolean getAsBoolean() {
}

@Override
public boolean equals(Object o) {
public boolean equals(@Nullable Object o) {
return (o == this) || (o instanceof JsonArray && ((JsonArray) o).elements.equals(elements));
}

Expand Down
3 changes: 2 additions & 1 deletion gson/src/main/java/com/google/gson/JsonNull.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.gson;

import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A class representing a Json {@code null} value.
*
Expand Down Expand Up @@ -61,7 +62,7 @@ public int hashCode() {
* All instances of JsonNull are the same
*/
@Override
public boolean equals(Object other) {
public boolean equals(@Nullable Object other) {
return this == other || other instanceof JsonNull;
}
}
Loading