001    /*
002     * Copyright 2010-2016 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.codegen.serialization;
018    
019    import com.intellij.openapi.util.Pair;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.codegen.ClassBuilderMode;
023    import org.jetbrains.kotlin.codegen.state.GenerationState;
024    import org.jetbrains.kotlin.descriptors.*;
025    import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
026    import org.jetbrains.kotlin.load.java.JvmAbi;
027    import org.jetbrains.kotlin.load.java.lazy.types.RawTypeImpl;
028    import org.jetbrains.kotlin.load.kotlin.JavaFlexibleTypeDeserializer;
029    import org.jetbrains.kotlin.load.kotlin.TypeSignatureMappingKt;
030    import org.jetbrains.kotlin.name.ClassId;
031    import org.jetbrains.kotlin.serialization.AnnotationSerializer;
032    import org.jetbrains.kotlin.serialization.ProtoBuf;
033    import org.jetbrains.kotlin.serialization.SerializerExtension;
034    import org.jetbrains.kotlin.serialization.StringTable;
035    import org.jetbrains.kotlin.serialization.jvm.ClassMapperLite;
036    import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
037    import org.jetbrains.kotlin.types.FlexibleType;
038    import org.jetbrains.kotlin.types.KotlinType;
039    import org.jetbrains.org.objectweb.asm.Type;
040    import org.jetbrains.org.objectweb.asm.commons.Method;
041    
042    import static org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.*;
043    
044    public class JvmSerializerExtension extends SerializerExtension {
045        private final JvmSerializationBindings bindings;
046        private final StringTable stringTable;
047        private final AnnotationSerializer annotationSerializer;
048        private final boolean useTypeTable;
049        private final String moduleName;
050        private final ClassBuilderMode classBuilderMode;
051    
052        public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull GenerationState state) {
053            this.bindings = bindings;
054            this.stringTable = new JvmStringTable(state.getTypeMapper());
055            this.annotationSerializer = new AnnotationSerializer(stringTable);
056            this.useTypeTable = state.getUseTypeTableInSerializer();
057            this.moduleName = state.getModuleName();
058            this.classBuilderMode = state.getClassBuilderMode();
059        }
060    
061        @NotNull
062        @Override
063        public StringTable getStringTable() {
064            return stringTable;
065        }
066    
067        @Override
068        public boolean shouldUseTypeTable() {
069            return useTypeTable;
070        }
071    
072        @Override
073        public void serializeClass(@NotNull ClassDescriptor descriptor, @NotNull ProtoBuf.Class.Builder proto) {
074            if (!moduleName.equals(JvmAbi.DEFAULT_MODULE_NAME)) {
075                proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName));
076            }
077        }
078    
079        @Override
080        public void serializePackage(@NotNull ProtoBuf.Package.Builder proto) {
081            if (!moduleName.equals(JvmAbi.DEFAULT_MODULE_NAME)) {
082                proto.setExtension(JvmProtoBuf.packageModuleName, stringTable.getStringIndex(moduleName));
083            }
084        }
085    
086        @Override
087        public void serializeFlexibleType(
088                @NotNull FlexibleType flexibleType,
089                @NotNull ProtoBuf.Type.Builder lowerProto,
090                @NotNull ProtoBuf.Type.Builder upperProto
091        ) {
092            lowerProto.setFlexibleTypeCapabilitiesId(getStringTable().getStringIndex(JavaFlexibleTypeDeserializer.INSTANCE.getId()));
093    
094            if (flexibleType instanceof RawTypeImpl) {
095                lowerProto.setExtension(JvmProtoBuf.isRaw, true);
096    
097                // we write this Extension for compatibility with old compiler
098                upperProto.setExtension(JvmProtoBuf.isRaw, true);
099            }
100        }
101    
102        @Override
103        public void serializeType(@NotNull KotlinType type, @NotNull ProtoBuf.Type.Builder proto) {
104            // TODO: don't store type annotations in our binary metadata on Java 8, use *TypeAnnotations attributes instead
105            for (AnnotationDescriptor annotation : type.getAnnotations()) {
106                proto.addExtension(JvmProtoBuf.typeAnnotation, annotationSerializer.serializeAnnotation(annotation));
107            }
108        }
109    
110        @Override
111        public void serializeTypeParameter(
112                @NotNull TypeParameterDescriptor typeParameter, @NotNull ProtoBuf.TypeParameter.Builder proto
113        ) {
114            for (AnnotationDescriptor annotation : typeParameter.getAnnotations()) {
115                proto.addExtension(JvmProtoBuf.typeParameterAnnotation, annotationSerializer.serializeAnnotation(annotation));
116            }
117        }
118    
119        @Override
120        public void serializeConstructor(@NotNull ConstructorDescriptor descriptor, @NotNull ProtoBuf.Constructor.Builder proto) {
121            Method method = bindings.get(METHOD_FOR_FUNCTION, descriptor);
122            if (method != null) {
123                JvmProtoBuf.JvmMethodSignature signature = new SignatureSerializer().methodSignature(descriptor, method);
124                if (signature != null) {
125                    proto.setExtension(JvmProtoBuf.constructorSignature, signature);
126                }
127            }
128        }
129    
130        @Override
131        public void serializeFunction(@NotNull FunctionDescriptor descriptor, @NotNull ProtoBuf.Function.Builder proto) {
132            Method method = bindings.get(METHOD_FOR_FUNCTION, descriptor);
133            if (method != null) {
134                JvmProtoBuf.JvmMethodSignature signature = new SignatureSerializer().methodSignature(descriptor, method);
135                if (signature != null) {
136                    proto.setExtension(JvmProtoBuf.methodSignature, signature);
137                }
138            }
139        }
140    
141        @Override
142        public void serializeProperty(@NotNull PropertyDescriptor descriptor, @NotNull ProtoBuf.Property.Builder proto) {
143            SignatureSerializer signatureSerializer = new SignatureSerializer();
144    
145            PropertyGetterDescriptor getter = descriptor.getGetter();
146            PropertySetterDescriptor setter = descriptor.getSetter();
147            Method getterMethod = getter == null ? null : bindings.get(METHOD_FOR_FUNCTION, getter);
148            Method setterMethod = setter == null ? null : bindings.get(METHOD_FOR_FUNCTION, setter);
149    
150            Pair<Type, String> field = bindings.get(FIELD_FOR_PROPERTY, descriptor);
151            Method syntheticMethod = bindings.get(SYNTHETIC_METHOD_FOR_PROPERTY, descriptor);
152    
153            JvmProtoBuf.JvmPropertySignature signature = signatureSerializer.propertySignature(
154                    descriptor,
155                    field != null ? field.second : null,
156                    field != null ? field.first.getDescriptor() : null,
157                    syntheticMethod != null ? signatureSerializer.methodSignature(null, syntheticMethod) : null,
158                    getterMethod != null ? signatureSerializer.methodSignature(null, getterMethod) : null,
159                    setterMethod != null ? signatureSerializer.methodSignature(null, setterMethod) : null
160            );
161    
162            proto.setExtension(JvmProtoBuf.propertySignature, signature);
163        }
164    
165        @Override
166        public void serializeErrorType(@NotNull KotlinType type, @NotNull ProtoBuf.Type.Builder builder) {
167            if (classBuilderMode == ClassBuilderMode.KAPT || classBuilderMode == ClassBuilderMode.KAPT3) {
168                builder.setClassName(stringTable.getStringIndex(TypeSignatureMappingKt.NON_EXISTENT_CLASS_NAME));
169                return;
170            }
171    
172            super.serializeErrorType(type, builder);
173        }
174    
175        private class SignatureSerializer {
176            @Nullable
177            public JvmProtoBuf.JvmMethodSignature methodSignature(@Nullable FunctionDescriptor descriptor, @NotNull Method method) {
178                JvmProtoBuf.JvmMethodSignature.Builder builder = JvmProtoBuf.JvmMethodSignature.newBuilder();
179                if (descriptor == null || !descriptor.getName().asString().equals(method.getName())) {
180                    builder.setName(stringTable.getStringIndex(method.getName()));
181                }
182                if (descriptor == null || requiresSignature(descriptor, method.getDescriptor())) {
183                    builder.setDesc(stringTable.getStringIndex(method.getDescriptor()));
184                }
185                return builder.hasName() || builder.hasDesc() ? builder.build() : null;
186            }
187    
188            // We don't write those signatures which can be trivially reconstructed from already serialized data
189            // TODO: make JvmStringTable implement NameResolver and use JvmProtoBufUtil#getJvmMethodSignature instead
190            private boolean requiresSignature(@NotNull FunctionDescriptor descriptor, @NotNull String desc) {
191                StringBuilder sb = new StringBuilder();
192                sb.append("(");
193                ReceiverParameterDescriptor receiverParameter = descriptor.getExtensionReceiverParameter();
194                if (receiverParameter != null) {
195                    String receiverDesc = mapTypeDefault(receiverParameter.getValue().getType());
196                    if (receiverDesc == null) return true;
197                    sb.append(receiverDesc);
198                }
199    
200                for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
201                    String paramDesc = mapTypeDefault(valueParameter.getType());
202                    if (paramDesc == null) return true;
203                    sb.append(paramDesc);
204                }
205    
206                sb.append(")");
207    
208                KotlinType returnType = descriptor.getReturnType();
209                String returnTypeDesc = returnType == null ? "V" : mapTypeDefault(returnType);
210                if (returnTypeDesc == null) return true;
211                sb.append(returnTypeDesc);
212    
213                return !sb.toString().equals(desc);
214            }
215    
216            private boolean requiresSignature(@NotNull PropertyDescriptor descriptor, @NotNull String desc) {
217                return !desc.equals(mapTypeDefault(descriptor.getType()));
218            }
219    
220            @Nullable
221            private String mapTypeDefault(@NotNull KotlinType type) {
222                ClassifierDescriptor classifier = type.getConstructor().getDeclarationDescriptor();
223                if (!(classifier instanceof ClassDescriptor)) return null;
224                ClassId classId = classId((ClassDescriptor) classifier);
225                return classId == null ? null : ClassMapperLite.mapClass(classId);
226            }
227    
228            @Nullable
229            private ClassId classId(@NotNull ClassDescriptor descriptor) {
230                DeclarationDescriptor container = descriptor.getContainingDeclaration();
231                if (container instanceof PackageFragmentDescriptor) {
232                    return ClassId.topLevel(((PackageFragmentDescriptor) container).getFqName().child(descriptor.getName()));
233                }
234                else if (container instanceof ClassDescriptor) {
235                    ClassId outerClassId = classId((ClassDescriptor) container);
236                    return outerClassId == null ? null : outerClassId.createNestedClassId(descriptor.getName());
237                }
238                else {
239                    return null;
240                }
241            }
242    
243            @NotNull
244            public JvmProtoBuf.JvmPropertySignature propertySignature(
245                    @NotNull PropertyDescriptor descriptor,
246                    @Nullable String fieldName,
247                    @Nullable String fieldDesc,
248                    @Nullable JvmProtoBuf.JvmMethodSignature syntheticMethod,
249                    @Nullable JvmProtoBuf.JvmMethodSignature getter,
250                    @Nullable JvmProtoBuf.JvmMethodSignature setter
251            ) {
252                JvmProtoBuf.JvmPropertySignature.Builder signature = JvmProtoBuf.JvmPropertySignature.newBuilder();
253    
254                if (fieldDesc != null) {
255                    assert fieldName != null : "Field name shouldn't be null when there's a field type: " + fieldDesc;
256                    signature.setField(fieldSignature(descriptor, fieldName, fieldDesc));
257                }
258    
259                if (syntheticMethod != null) {
260                    signature.setSyntheticMethod(syntheticMethod);
261                }
262    
263                if (getter != null) {
264                    signature.setGetter(getter);
265                }
266                if (setter != null) {
267                    signature.setSetter(setter);
268                }
269    
270                return signature.build();
271            }
272    
273            @NotNull
274            public JvmProtoBuf.JvmFieldSignature fieldSignature(
275                    @NotNull PropertyDescriptor descriptor,
276                    @NotNull String name,
277                    @NotNull String desc
278            ) {
279                JvmProtoBuf.JvmFieldSignature.Builder builder = JvmProtoBuf.JvmFieldSignature.newBuilder();
280                if (!descriptor.getName().asString().equals(name)) {
281                    builder.setName(stringTable.getStringIndex(name));
282                }
283                if (requiresSignature(descriptor, desc)) {
284                    builder.setDesc(stringTable.getStringIndex(desc));
285                }
286                return builder.build();
287            }
288        }
289    }