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