From 9aca47782417d07b3bcf96ff6e496747f49f3b00 Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 1 Dec 2023 16:04:23 -0800 Subject: [PATCH] Implement @AutoCodec.Interner code generation. PiperOrigin-RevId: 587149949 Change-Id: Iaa2a1328ede8e97e4ec67a3a07b9f4961f9ec959 --- .../serialization/ArrayProcessor.java | 10 + .../serialization/InterningObjectCodec.java | 47 ++ .../skyframe/serialization/autocodec/BUILD | 3 + .../InterningObjectCodecFieldGenerators.java | 475 ++++++++++++++++++ .../InterningObjectCodecGenerator.java | 141 ++++++ .../autocodec/TypeOperations.java | 32 ++ 6 files changed, 708 insertions(+) create mode 100644 src/main/java/com/google/devtools/build/lib/skyframe/serialization/InterningObjectCodec.java create mode 100644 src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/InterningObjectCodecFieldGenerators.java create mode 100644 src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/InterningObjectCodecGenerator.java diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ArrayProcessor.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ArrayProcessor.java index 00094e2e02f08a..2b8dd6c1ee6904 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ArrayProcessor.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ArrayProcessor.java @@ -353,6 +353,16 @@ public double[] deserializeArrayData(CodedInputStream codedIn, int length) } }; + /** + * Handles possibly nested arrays of {@code Object} or any type derived from {@code Object}. + * + *

This processor observes the nesting level of the array by reflective operations on the + * {@code type} parameter passed into its methods. It similarly uses reflective calls to create + * nested arrays of appropriate type and nesting level. + * + *

Finally, at the leaf level, it uses the {@code (Ser|Deser)ializationContext} to apply codecs + * to the base array components. + */ public static final ArrayProcessor OBJECT_ARRAY_PROCESSOR = new ArrayProcessor() { // Special case uses `Array.newInstance` to also create leaf-level arrays. Additionally, diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/InterningObjectCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/InterningObjectCodec.java new file mode 100644 index 00000000000000..6154e2b9e61ba8 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/InterningObjectCodec.java @@ -0,0 +1,47 @@ +// Copyright 2023 The Bazel Authors. All rights reserved. +// +// 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 com.google.devtools.build.lib.skyframe.serialization; + +import com.google.protobuf.CodedInputStream; +import java.io.IOException; + +/** Codec variant that interns the deserialization result. */ +public abstract class InterningObjectCodec implements ObjectCodec { + + @Override + public final MemoizationStrategy getStrategy() { + // There is no fixed reference to an interned object until after it has been constructed and + // passes through the interner. Therefore this is always MEMOIZE_AFTER. + return MemoizationStrategy.MEMOIZE_AFTER; + } + + /** + * Adapter for synchronous contexts. + * + *

Deserializes using {@link #deserializeInterned} then calls {@link #intern} on the result. + */ + @Override + public final T deserialize(DeserializationContext context, CodedInputStream codedIn) + throws SerializationException, IOException { + return intern(deserializeInterned(context, codedIn)); + } + + /** Performs the (async) deserialization work. */ + public abstract T deserializeInterned( + AsyncDeserializationContext context, CodedInputStream codedIn) + throws SerializationException, IOException; + + /** Interns the result of {@link #deserializeInterned}. */ + public abstract T intern(T value); +} diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD index 6bc56248fbaafe..acf9e9974550e3 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD @@ -68,6 +68,7 @@ java_library( deps = [ ":serialization-processing-exception", "//third_party:guava", + "//third_party:jsr305", "//third_party/java/javapoet", ], ) @@ -110,6 +111,8 @@ java_library( "CodecGenerator.java", "FieldGenerator.java", "Initializers.java", + "InterningObjectCodecFieldGenerators.java", + "InterningObjectCodecGenerator.java", "Marshallers.java", "SerializationCodeGenerator.java", ], diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/InterningObjectCodecFieldGenerators.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/InterningObjectCodecFieldGenerators.java new file mode 100644 index 00000000000000..67a6d146555135 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/InterningObjectCodecFieldGenerators.java @@ -0,0 +1,475 @@ +// Copyright 2023 The Bazel Authors. All rights reserved. +// +// 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 com.google.devtools.build.lib.skyframe.serialization.autocodec; + +import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.getErasure; +import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.resolveBaseArrayComponentType; + +import com.google.devtools.build.lib.skyframe.serialization.ArrayProcessor; +import com.google.devtools.build.lib.skyframe.serialization.CodecHelpers; +import com.google.devtools.build.lib.unsafe.UnsafeProvider; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.WildcardTypeName; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +/** Handles fields for {@link AutoCodec} interned types. */ +abstract class InterningObjectCodecFieldGenerators { + /** + * Creates a generator for the given variable. + * + * @param hierarchyLevel a variable could occur in either the class being serialized or in one of + * its ancestor classes. This is 0 for the class itself, 1 for its superclass, and so on. It + * is used to create unique names without risk of shadowing. + */ + static FieldGenerator create( + VariableElement variable, int hierarchyLevel, ProcessingEnvironment env) + throws SerializationProcessingException { + TypeMirror type = variable.asType(); + switch (type.getKind()) { + case ARRAY: + ArrayType arrayType = (ArrayType) type; + TypeMirror componentType = arrayType.getComponentType(); + TypeKind componentKind = componentType.getKind(); + if (componentKind.isPrimitive()) { + return new PrimitiveArrayFieldGenerator(variable, componentKind, hierarchyLevel); + } + if (componentKind.equals(TypeKind.ARRAY)) { + return new NestedArrayFieldGenerator( + variable, hierarchyLevel, resolveBaseArrayComponentType(componentType).getKind()); + } + return new ObjectArrayFieldGenerator( + variable, hierarchyLevel, getErasure(componentType, env)); + case BOOLEAN: + return new BooleanFieldGenerator(variable, hierarchyLevel); + case BYTE: + return new ByteFieldGenerator(variable, hierarchyLevel); + case CHAR: + return new CharFieldGenerator(variable, hierarchyLevel); + case DOUBLE: + return new DoubleFieldGenerator(variable, hierarchyLevel); + case FLOAT: + return new FloatFieldGenerator(variable, hierarchyLevel); + case INT: + return new IntFieldGenerator(variable, hierarchyLevel); + case LONG: + return new LongFieldGenerator(variable, hierarchyLevel); + case SHORT: + return new ShortFieldGenerator(variable, hierarchyLevel); + case TYPEVAR: + case DECLARED: + return new ObjectFieldGenerator(variable, hierarchyLevel); + default: + // There are other TypeKinds, for example, NONE, NULL, VOID and WILDCARD, (and more, + // depending on the JDK version), but none are known to occur in code that defines the type + // of a member variable. + TypeElement parent = (TypeElement) variable.getEnclosingElement(); + throw new SerializationProcessingException( + parent, + "%s had field %s having unexpected type %s", + parent.getQualifiedName(), + variable.getSimpleName(), + type); + } + } + + private static final class NestedArrayFieldGenerator extends FieldGenerator { + private final String processorName; + + private NestedArrayFieldGenerator( + VariableElement variable, int hierarchyLevel, TypeKind baseComponentKind) { + super(variable, hierarchyLevel); + switch (baseComponentKind) { + case BOOLEAN: + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + this.processorName = baseComponentKind.name() + "_ARRAY_PROCESSOR"; + break; + case DECLARED: + case TYPEVAR: + // See comments of `ArrayProcessor.OBJECT_ARRAY_PROCESSOR` to understand how it works for + // any type of object array. + this.processorName = "OBJECT_ARRAY_PROCESSOR"; + break; + default: + throw new IllegalStateException( + "Unexpected base array component kind " + + baseComponentKind + + " for array field " + + variable); + } + } + + private String getTypeName() { + return getNamePrefix() + "_type"; + } + + @Override + void generateAdditionalMemberVariables(TypeSpec.Builder builder) { + builder.addField( + // Specifies Class type. + ParameterizedTypeName.get( + ClassName.get(Class.class), WildcardTypeName.subtypeOf(Object.class)), + getTypeName(), + Modifier.PRIVATE, + Modifier.FINAL); + } + + @Override + void generateConstructorCode(MethodSpec.Builder constructor) { + constructor.addStatement( + "this.$L = $T.class.getDeclaredField(\"$L\").getType()", + getTypeName(), + getParentName(), + getParameterName()); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "$T.$L.serialize(context, codedOut, $L, $T.unsafe().getObject(obj, $L))", + ArrayProcessor.class, + processorName, + getTypeName(), + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.$L.deserialize(context, codedIn, $L, instance, $L)", + ArrayProcessor.class, + processorName, + getTypeName(), + getOffsetName()); + } + } + + private static class PrimitiveArrayFieldGenerator extends FieldGenerator { + private final TypeKind componentType; + + private PrimitiveArrayFieldGenerator( + VariableElement variable, TypeKind componentType, int hierarchyLevel) { + super(variable, hierarchyLevel); + this.componentType = componentType; + } + + private String getProcessorName() { + return componentType + "_ARRAY_PROCESSOR"; + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + String objName = getNamePrefix() + "_obj"; + serialize + .addStatement( + "$T $L = $T.unsafe().getObject(obj, $L)", + Object.class, + objName, + UnsafeProvider.class, + getOffsetName()) + .beginControlFlow("if ($L == null)", objName) + .addStatement("codedOut.writeInt32NoTag(0)") + .nextControlFlow("else") + .addStatement( + "$T.$L.serializeArrayData(codedOut, $L)", + ArrayProcessor.class, + getProcessorName(), + objName) + .endControlFlow(); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + String lengthName = getNamePrefix() + "_length"; + deserialize + .addStatement("int $L = codedIn.readInt32()", lengthName) + .beginControlFlow("if ($L > 0)", lengthName) + .addStatement("$L--", lengthName) + .addStatement( + "$T.unsafe().putObject(instance, $L, $T.$L.deserializeArrayData(codedIn, $L))", + UnsafeProvider.class, + getOffsetName(), + ArrayProcessor.class, + getProcessorName(), + lengthName) + .endControlFlow(); + } + } + + private static class ObjectArrayFieldGenerator extends FieldGenerator { + private final TypeName componentTypeName; + + private ObjectArrayFieldGenerator( + VariableElement variable, int hierarchyLevel, TypeName componentTypeName) { + super(variable, hierarchyLevel); + this.componentTypeName = componentTypeName; + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + String arrName = getNamePrefix() + "_arr"; + serialize + .addStatement( + "$T $L = $T.unsafe().getObject(obj, $L)", + Object.class, + arrName, + UnsafeProvider.class, + getOffsetName()) + .beginControlFlow("if ($L == null)", arrName) + .addStatement("codedOut.writeInt32NoTag(0)") + .nextControlFlow("else") + .addStatement( + "$T.serializeObjectArray(context, codedOut, $L)", ArrayProcessor.class, arrName) + .endControlFlow(); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + String lengthName = getNamePrefix() + "_length"; + String arrName = getNamePrefix() + "_arr"; + deserialize + .addStatement("int $L = codedIn.readInt32()", lengthName) + .beginControlFlow("if ($L > 0)", lengthName) + .addStatement("$L--", lengthName) + .addStatement( + "$T[] $L = new $T[$L]", componentTypeName, arrName, componentTypeName, lengthName) + .addStatement( + "$T.unsafe().putObject(instance, $L, $L)", + UnsafeProvider.class, + getOffsetName(), + arrName) + .addStatement( + "$T.deserializeObjectArray(context, codedIn, $L, $L)", + ArrayProcessor.class, + arrName, + lengthName) + .endControlFlow(); + } + } + + private static class BooleanFieldGenerator extends FieldGenerator { + private BooleanFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "codedOut.writeBoolNoTag($T.unsafe().getBoolean(obj, $L))", + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.unsafe().putBoolean(instance, $L, codedIn.readBool())", + UnsafeProvider.class, + getOffsetName()); + } + } + + private static class ByteFieldGenerator extends FieldGenerator { + private ByteFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "codedOut.writeRawByte($T.unsafe().getByte(obj, $L))", + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.unsafe().putByte(instance, $L, codedIn.readRawByte())", + UnsafeProvider.class, + getOffsetName()); + } + } + + private static class CharFieldGenerator extends FieldGenerator { + private CharFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "$T.writeChar(codedOut, $T.unsafe().getChar(obj, $L))", + CodecHelpers.class, + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.unsafe().putChar(instance, $L, $T.readChar(codedIn))", + UnsafeProvider.class, + getOffsetName(), + CodecHelpers.class); + } + } + + private static class DoubleFieldGenerator extends FieldGenerator { + private DoubleFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "codedOut.writeDoubleNoTag($T.unsafe().getDouble(obj, $L))", + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.unsafe().putDouble(instance, $L, codedIn.readDouble())", + UnsafeProvider.class, + getOffsetName()); + } + } + + private static class FloatFieldGenerator extends FieldGenerator { + private FloatFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "codedOut.writeFloatNoTag($T.unsafe().getFloat(obj, $L))", + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.unsafe().putFloat(instance, $L, codedIn.readFloat())", + UnsafeProvider.class, + getOffsetName()); + } + } + + private static class IntFieldGenerator extends FieldGenerator { + private IntFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "codedOut.writeInt32NoTag($T.unsafe().getInt(obj, $L))", + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.unsafe().putInt(instance, $L, codedIn.readInt32())", + UnsafeProvider.class, + getOffsetName()); + } + } + + private static class LongFieldGenerator extends FieldGenerator { + private LongFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "codedOut.writeInt64NoTag($T.unsafe().getLong(obj, $L))", + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.unsafe().putLong(instance, $L, codedIn.readInt64())", + UnsafeProvider.class, + getOffsetName()); + } + } + + private static class ShortFieldGenerator extends FieldGenerator { + private ShortFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "$T.writeShort(codedOut, $T.unsafe().getShort(obj, $L))", + CodecHelpers.class, + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement( + "$T.unsafe().putShort(instance, $L, $T.readShort(codedIn))", + UnsafeProvider.class, + getOffsetName(), + CodecHelpers.class); + } + } + + private static class ObjectFieldGenerator extends FieldGenerator { + private ObjectFieldGenerator(VariableElement variable, int hierarchyLevel) { + super(variable, hierarchyLevel); + } + + @Override + void generateSerializeCode(MethodSpec.Builder serialize) { + serialize.addStatement( + "context.serialize($T.unsafe().getObject(obj, $L), codedOut)", + UnsafeProvider.class, + getOffsetName()); + } + + @Override + void generateDeserializeCode(MethodSpec.Builder deserialize) { + deserialize.addStatement("context.deserialize(codedIn, instance, $L)", getOffsetName()); + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/InterningObjectCodecGenerator.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/InterningObjectCodecGenerator.java new file mode 100644 index 00000000000000..d027dfa74e629d --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/InterningObjectCodecGenerator.java @@ -0,0 +1,141 @@ +// Copyright 2023 The Bazel Authors. All rights reserved. +// +// 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 com.google.devtools.build.lib.skyframe.serialization.autocodec; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.getErasure; +import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.getSuperclass; +import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.isSerializableField; +import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.matchesType; +import static javax.lang.model.util.ElementFilter.fieldsIn; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.skyframe.serialization.AsyncDeserializationContext; +import com.google.devtools.build.lib.skyframe.serialization.InterningObjectCodec; +import com.google.devtools.build.lib.skyframe.serialization.SerializationException; +import com.google.devtools.build.lib.unsafe.UnsafeProvider; +import com.google.protobuf.CodedInputStream; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.io.IOException; +import java.util.ArrayList; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +/** Generates instances of {@link InterningObjectCodec}. */ +final class InterningObjectCodecGenerator extends CodecGenerator { + + InterningObjectCodecGenerator(ProcessingEnvironment env) { + super(env); + } + + @Override + ImmutableList getFieldGenerators(TypeElement type) + throws SerializationProcessingException { + // Collects the type and its supertypes. + ArrayList types = new ArrayList<>(); + for (TypeElement next = type; + next != null && !matchesType(next.asType(), Object.class, env); + next = getSuperclass(next)) { + types.add(next); + } + + ImmutableList.Builder result = ImmutableList.builder(); + // Iterates in reverse order so variables are ordered highest superclass first, as they would + // be ordered in the class layout. + for (int i = types.size() - 1; i >= 0; i--) { + for (VariableElement variable : fieldsIn(types.get(i).getEnclosedElements())) { + if (!isSerializableField(variable)) { + continue; + } + result.add(InterningObjectCodecFieldGenerators.create(variable, i, env)); + } + } + return result.build(); + } + + @Override + void performAdditionalCodecInitialization( + TypeSpec.Builder classBuilder, TypeElement encodedType, ExecutableElement internMethod) { + TypeName returnType = getErasure(encodedType, env); + classBuilder.superclass( + ParameterizedTypeName.get(ClassName.get(InterningObjectCodec.class), returnType)); + + // Defines the `InterningObjectCodec.intern` implementation. + VariableElement param = getOnlyElement(internMethod.getParameters()); + classBuilder.addMethod( + MethodSpec.methodBuilder("intern") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(getErasure(param.asType(), env), "value") + .returns(returnType) + .addStatement( + "return $T.$L(value)", getErasure(encodedType, env), internMethod.getSimpleName()) + .build()); + } + + @Override + MethodSpec.Builder initializeConstructor(boolean hasFields) { + MethodSpec.Builder constructor = MethodSpec.constructorBuilder(); + if (hasFields) { + constructor.beginControlFlow("try"); + } + return constructor; + } + + /** Initializes the {@link InterningObjectCodec#deserializeInterned} method. */ + @Override + MethodSpec.Builder initializeDeserializeMethod(TypeElement encodedType) { + TypeName typeName = getErasure(encodedType, env); + return MethodSpec.methodBuilder("deserializeInterned") + .addModifiers(Modifier.PUBLIC) + .returns(typeName) + .addAnnotation(Override.class) + .addException(SerializationException.class) + .addException(IOException.class) + .addParameter(AsyncDeserializationContext.class, "context") + .addParameter(CodedInputStream.class, "codedIn") + .addStatement("$T instance", typeName) + .beginControlFlow("try") + .addStatement( + "instance = ($T) $T.unsafe().allocateInstance($T.class)", + typeName, + UnsafeProvider.class, + typeName) + .nextControlFlow("catch ($T e)", ReflectiveOperationException.class) + .addStatement("throw new $T(e)", IllegalStateException.class) + .endControlFlow(); + } + + @Override + void addImplementationToEndOfMethods( + ExecutableElement unusedInterner, + MethodSpec.Builder constructor, + MethodSpec.Builder deserialize, + boolean hasFields) { + if (hasFields) { + constructor + .nextControlFlow("catch ($T e)", NoSuchFieldException.class) + .addStatement("throw new $T(e)", AssertionError.class) + .endControlFlow(); + } + deserialize.addStatement("return instance"); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/TypeOperations.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/TypeOperations.java index 2a673729eacb4c..3f5e3bc128fc0c 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/TypeOperations.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/TypeOperations.java @@ -18,10 +18,16 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import java.io.IOException; +import java.util.Set; +import javax.annotation.Nullable; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; @@ -100,5 +106,31 @@ static TypeName getErasure(TypeElement type, ProcessingEnvironment env) { return getErasure(type.asType(), env); } + static boolean isSerializableField(VariableElement variable) { + Set modifiers = variable.getModifiers(); + return !modifiers.contains(Modifier.STATIC) && !modifiers.contains(Modifier.TRANSIENT); + } + + @Nullable + static TypeElement getSuperclass(TypeElement type) { + TypeMirror mirror = type.getSuperclass(); + if (!(mirror instanceof DeclaredType)) { + // `type` represents Object or some interface. + return null; + } + // `DeclaredType.asElement` can return a `TypeParameterElement` instance if `mirror` is a + // generic type parameter, which isn't possible here. + return (TypeElement) ((DeclaredType) mirror).asElement(); + } + + static TypeMirror resolveBaseArrayComponentType(TypeMirror type) { + if (!type.getKind().equals(TypeKind.ARRAY)) { + return type; + } + ArrayType arrayType = (ArrayType) type; + TypeMirror componentType = arrayType.getComponentType(); + return resolveBaseArrayComponentType(componentType); + } + private TypeOperations() {} }