001    /*
002     * Copyright 2010-2015 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.RawTypeCapabilities;
028    import org.jetbrains.kotlin.name.ClassId;
029    import org.jetbrains.kotlin.serialization.AnnotationSerializer;
030    import org.jetbrains.kotlin.serialization.ProtoBuf;
031    import org.jetbrains.kotlin.serialization.SerializerExtension;
032    import org.jetbrains.kotlin.serialization.StringTable;
033    import org.jetbrains.kotlin.serialization.jvm.ClassMapperLite;
034    import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
035    import org.jetbrains.kotlin.types.KotlinType;
036    import org.jetbrains.org.objectweb.asm.Type;
037    import org.jetbrains.org.objectweb.asm.commons.Method;
038    
039    import static org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.*;
040    
041    public class JvmSerializerExtension extends SerializerExtension {
042        private final JvmSerializationBindings bindings;
043        private final StringTable stringTable;
044        private final AnnotationSerializer annotationSerializer;
045        private final boolean useTypeTable;
046        private final String moduleName;
047        private final ClassBuilderMode classBuilderMode;
048    
049        public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull GenerationState state) {
050            this.bindings = bindings;
051            this.stringTable = new JvmStringTable(state.getTypeMapper());
052            this.annotationSerializer = new AnnotationSerializer(stringTable);
053            this.useTypeTable = state.getUseTypeTableInSerializer();
054            this.moduleName = state.getModuleName();
055            this.classBuilderMode = state.getClassBuilderMode();
056        }
057    
058        @NotNull
059        @Override
060        public StringTable getStringTable() {
061            return stringTable;
062        }
063    
064        @Override
065        public boolean shouldUseTypeTable() {
066            return useTypeTable;
067        }
068    
069        @Override
070        public void serializeClass(@NotNull ClassDescriptor descriptor, @NotNull ProtoBuf.Class.Builder proto) {
071            if (!moduleName.equals(JvmAbi.DEFAULT_MODULE_NAME)) {
072                proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName));
073            }
074        }
075    
076        @Override
077        public void serializePackage(@NotNull ProtoBuf.Package.Builder proto) {
078            if (!moduleName.equals(JvmAbi.DEFAULT_MODULE_NAME)) {
079                proto.setExtension(JvmProtoBuf.packageModuleName, stringTable.getStringIndex(moduleName));
080            }
081        }
082    
083        @Override
084        public void serializeType(@NotNull KotlinType type, @NotNull ProtoBuf.Type.Builder proto) {
085            // TODO: don't store type annotations in our binary metadata on Java 8, use *TypeAnnotations attributes instead
086            for (AnnotationDescriptor annotation : type.getAnnotations()) {
087                proto.addExtension(JvmProtoBuf.typeAnnotation, annotationSerializer.serializeAnnotation(annotation));
088            }
089    
090            if (type.getCapabilities() instanceof RawTypeCapabilities) {
091                proto.setExtension(JvmProtoBuf.isRaw, true);
092            }
093        }
094    
095        @Override
096        public void serializeTypeParameter(
097                @NotNull TypeParameterDescriptor typeParameter, @NotNull ProtoBuf.TypeParameter.Builder proto
098        ) {
099            for (AnnotationDescriptor annotation : typeParameter.getAnnotations()) {
100                proto.addExtension(JvmProtoBuf.typeParameterAnnotation, annotationSerializer.serializeAnnotation(annotation));
101            }
102        }
103    
104        @Override
105        public void serializeConstructor(@NotNull ConstructorDescriptor descriptor, @NotNull ProtoBuf.Constructor.Builder proto) {
106            Method method = bindings.get(METHOD_FOR_FUNCTION, descriptor);
107            if (method != null) {
108                JvmProtoBuf.JvmMethodSignature signature = new SignatureSerializer().methodSignature(descriptor, method);
109                if (signature != null) {
110                    proto.setExtension(JvmProtoBuf.constructorSignature, signature);
111                }
112            }
113        }
114    
115        @Override
116        public void serializeFunction(@NotNull FunctionDescriptor descriptor, @NotNull ProtoBuf.Function.Builder proto) {
117            Method method = bindings.get(METHOD_FOR_FUNCTION, descriptor);
118            if (method != null) {
119                JvmProtoBuf.JvmMethodSignature signature = new SignatureSerializer().methodSignature(descriptor, method);
120                if (signature != null) {
121                    proto.setExtension(JvmProtoBuf.methodSignature, signature);
122                }
123            }
124        }
125    
126        @Override
127        public void serializeProperty(@NotNull PropertyDescriptor descriptor, @NotNull ProtoBuf.Property.Builder proto) {
128            SignatureSerializer signatureSerializer = new SignatureSerializer();
129    
130            PropertyGetterDescriptor getter = descriptor.getGetter();
131            PropertySetterDescriptor setter = descriptor.getSetter();
132            Method getterMethod = getter == null ? null : bindings.get(METHOD_FOR_FUNCTION, getter);
133            Method setterMethod = setter == null ? null : bindings.get(METHOD_FOR_FUNCTION, setter);
134    
135            Pair<Type, String> field = bindings.get(FIELD_FOR_PROPERTY, descriptor);
136            Method syntheticMethod = bindings.get(SYNTHETIC_METHOD_FOR_PROPERTY, descriptor);
137    
138            JvmProtoBuf.JvmPropertySignature signature = signatureSerializer.propertySignature(
139                    descriptor,
140                    field != null ? field.second : null,
141                    field != null ? field.first.getDescriptor() : null,
142                    syntheticMethod != null ? signatureSerializer.methodSignature(null, syntheticMethod) : null,
143                    getterMethod != null ? signatureSerializer.methodSignature(null, getterMethod) : null,
144                    setterMethod != null ? signatureSerializer.methodSignature(null, setterMethod) : null
145            );
146    
147            proto.setExtension(JvmProtoBuf.propertySignature, signature);
148        }
149    
150        @Override
151        public void serializeErrorType(@NotNull KotlinType type, @NotNull ProtoBuf.Type.Builder builder) {
152            if (classBuilderMode == ClassBuilderMode.KAPT) {
153                builder.setClassName(stringTable.getStringIndex("error.NonExistingClass"));
154                return;
155            }
156    
157            super.serializeErrorType(type, builder);
158        }
159    
160        private class SignatureSerializer {
161            @Nullable
162            public JvmProtoBuf.JvmMethodSignature methodSignature(@Nullable FunctionDescriptor descriptor, @NotNull Method method) {
163                JvmProtoBuf.JvmMethodSignature.Builder builder = JvmProtoBuf.JvmMethodSignature.newBuilder();
164                if (descriptor == null || !descriptor.getName().asString().equals(method.getName())) {
165                    builder.setName(stringTable.getStringIndex(method.getName()));
166                }
167                if (descriptor == null || requiresSignature(descriptor, method.getDescriptor())) {
168                    builder.setDesc(stringTable.getStringIndex(method.getDescriptor()));
169                }
170                return builder.hasName() || builder.hasDesc() ? builder.build() : null;
171            }
172    
173            // We don't write those signatures which can be trivially reconstructed from already serialized data
174            // TODO: make JvmStringTable implement NameResolver and use JvmProtoBufUtil#getJvmMethodSignature instead
175            private boolean requiresSignature(@NotNull FunctionDescriptor descriptor, @NotNull String desc) {
176                StringBuilder sb = new StringBuilder();
177                sb.append("(");
178                ReceiverParameterDescriptor receiverParameter = descriptor.getExtensionReceiverParameter();
179                if (receiverParameter != null) {
180                    String receiverDesc = mapTypeDefault(receiverParameter.getValue().getType());
181                    if (receiverDesc == null) return true;
182                    sb.append(receiverDesc);
183                }
184    
185                for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
186                    String paramDesc = mapTypeDefault(valueParameter.getType());
187                    if (paramDesc == null) return true;
188                    sb.append(paramDesc);
189                }
190    
191                sb.append(")");
192    
193                KotlinType returnType = descriptor.getReturnType();
194                String returnTypeDesc = returnType == null ? "V" : mapTypeDefault(returnType);
195                if (returnTypeDesc == null) return true;
196                sb.append(returnTypeDesc);
197    
198                return !sb.toString().equals(desc);
199            }
200    
201            private boolean requiresSignature(@NotNull PropertyDescriptor descriptor, @NotNull String desc) {
202                return !desc.equals(mapTypeDefault(descriptor.getType()));
203            }
204    
205            @Nullable
206            private String mapTypeDefault(@NotNull KotlinType type) {
207                ClassifierDescriptor classifier = type.getConstructor().getDeclarationDescriptor();
208                if (!(classifier instanceof ClassDescriptor)) return null;
209                ClassId classId = classId((ClassDescriptor) classifier);
210                return classId == null ? null : ClassMapperLite.mapClass(classId);
211            }
212    
213            @Nullable
214            private ClassId classId(@NotNull ClassDescriptor descriptor) {
215                DeclarationDescriptor container = descriptor.getContainingDeclaration();
216                if (container instanceof PackageFragmentDescriptor) {
217                    return ClassId.topLevel(((PackageFragmentDescriptor) container).getFqName().child(descriptor.getName()));
218                }
219                else if (container instanceof ClassDescriptor) {
220                    ClassId outerClassId = classId((ClassDescriptor) container);
221                    return outerClassId == null ? null : outerClassId.createNestedClassId(descriptor.getName());
222                }
223                else {
224                    return null;
225                }
226            }
227    
228            @NotNull
229            public JvmProtoBuf.JvmPropertySignature propertySignature(
230                    @NotNull PropertyDescriptor descriptor,
231                    @Nullable String fieldName,
232                    @Nullable String fieldDesc,
233                    @Nullable JvmProtoBuf.JvmMethodSignature syntheticMethod,
234                    @Nullable JvmProtoBuf.JvmMethodSignature getter,
235                    @Nullable JvmProtoBuf.JvmMethodSignature setter
236            ) {
237                JvmProtoBuf.JvmPropertySignature.Builder signature = JvmProtoBuf.JvmPropertySignature.newBuilder();
238    
239                if (fieldDesc != null) {
240                    assert fieldName != null : "Field name shouldn't be null when there's a field type: " + fieldDesc;
241                    signature.setField(fieldSignature(descriptor, fieldName, fieldDesc));
242                }
243    
244                if (syntheticMethod != null) {
245                    signature.setSyntheticMethod(syntheticMethod);
246                }
247    
248                if (getter != null) {
249                    signature.setGetter(getter);
250                }
251                if (setter != null) {
252                    signature.setSetter(setter);
253                }
254    
255                return signature.build();
256            }
257    
258            @NotNull
259            public JvmProtoBuf.JvmFieldSignature fieldSignature(
260                    @NotNull PropertyDescriptor descriptor,
261                    @NotNull String name,
262                    @NotNull String desc
263            ) {
264                JvmProtoBuf.JvmFieldSignature.Builder builder = JvmProtoBuf.JvmFieldSignature.newBuilder();
265                if (!descriptor.getName().asString().equals(name)) {
266                    builder.setName(stringTable.getStringIndex(name));
267                }
268                if (requiresSignature(descriptor, desc)) {
269                    builder.setDesc(stringTable.getStringIndex(desc));
270                }
271                return builder.build();
272            }
273        }
274    }