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