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;
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.builtins.KotlinBuiltIns;
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.load.kotlin.SignatureDeserializer;
028    import org.jetbrains.kotlin.name.FqName;
029    import org.jetbrains.kotlin.name.Name;
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.deserialization.NameResolver;
035    import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor;
036    import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor;
037    import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
038    import org.jetbrains.kotlin.types.JetType;
039    import org.jetbrains.org.objectweb.asm.Type;
040    import org.jetbrains.org.objectweb.asm.commons.Method;
041    
042    import java.util.Arrays;
043    
044    import static org.jetbrains.kotlin.codegen.AsmUtil.shortNameByAsmType;
045    import static org.jetbrains.kotlin.codegen.JvmSerializationBindings.*;
046    
047    public class JvmSerializerExtension extends SerializerExtension {
048        private final JvmSerializationBindings bindings;
049        private final JetTypeMapper typeMapper;
050        private final AnnotationSerializer annotationSerializer = new AnnotationSerializer();
051    
052        public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull JetTypeMapper typeMapper) {
053            this.bindings = bindings;
054            this.typeMapper = typeMapper;
055        }
056    
057        @Override
058        public void serializeClass(@NotNull ClassDescriptor descriptor, @NotNull ProtoBuf.Class.Builder proto, @NotNull StringTable stringTable) {
059            AnnotationDescriptor annotation = descriptor.getAnnotations().findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation);
060            if (annotation != null) {
061                proto.addExtension(JvmProtoBuf.classAnnotation, annotationSerializer.serializeAnnotation(annotation, stringTable));
062            }
063        }
064    
065        @Override
066        public void serializeCallable(
067                @NotNull CallableMemberDescriptor callable,
068                @NotNull ProtoBuf.Callable.Builder proto,
069                @NotNull StringTable stringTable
070        ) {
071            saveSignature(callable, proto, stringTable);
072            saveImplClassName(callable, proto, stringTable);
073        }
074    
075        @Override
076        public void serializeValueParameter(
077                @NotNull ValueParameterDescriptor descriptor,
078                @NotNull ProtoBuf.Callable.ValueParameter.Builder proto,
079                @NotNull StringTable stringTable
080        ) {
081            Integer index = bindings.get(INDEX_FOR_VALUE_PARAMETER, descriptor);
082            if (index != null) {
083                proto.setExtension(JvmProtoBuf.index, index);
084            }
085        }
086    
087        @Override
088        public void serializeType(@NotNull JetType type, @NotNull ProtoBuf.Type.Builder proto, @NotNull StringTable stringTable) {
089            // TODO: don't store type annotations in our binary metadata on Java 8, use *TypeAnnotations attributes instead
090            for (AnnotationDescriptor annotation : type.getAnnotations()) {
091                proto.addExtension(JvmProtoBuf.typeAnnotation, annotationSerializer.serializeAnnotation(annotation, stringTable));
092            }
093    
094            if (type.getCapabilities() instanceof RawTypeCapabilities) {
095                proto.setExtension(JvmProtoBuf.isRaw, true);
096            }
097        }
098    
099        @Override
100        @NotNull
101        public String getLocalClassName(@NotNull ClassDescriptor descriptor) {
102            return shortNameByAsmType(typeMapper.mapClass(descriptor));
103        }
104    
105        private void saveSignature(
106                @NotNull CallableMemberDescriptor callable,
107                @NotNull ProtoBuf.Callable.Builder proto,
108                @NotNull StringTable stringTable
109        ) {
110            SignatureSerializer signatureSerializer = new SignatureSerializer(stringTable);
111            if (callable instanceof FunctionDescriptor) {
112                JvmProtoBuf.JvmMethodSignature signature;
113                if (callable instanceof DeserializedSimpleFunctionDescriptor) {
114                    DeserializedSimpleFunctionDescriptor deserialized = (DeserializedSimpleFunctionDescriptor) callable;
115                    signature = signatureSerializer.copyMethodSignature(
116                            deserialized.getProto().getExtension(JvmProtoBuf.methodSignature), deserialized.getNameResolver());
117                }
118                else {
119                    Method method = bindings.get(METHOD_FOR_FUNCTION, (FunctionDescriptor) callable);
120                    signature = method != null ? signatureSerializer.methodSignature(method) : null;
121                }
122                if (signature != null) {
123                    proto.setExtension(JvmProtoBuf.methodSignature, signature);
124                }
125            }
126            else if (callable instanceof PropertyDescriptor) {
127                PropertyDescriptor property = (PropertyDescriptor) callable;
128    
129                PropertyGetterDescriptor getter = property.getGetter();
130                PropertySetterDescriptor setter = property.getSetter();
131                Method getterMethod = getter == null ? null : bindings.get(METHOD_FOR_FUNCTION, getter);
132                Method setterMethod = setter == null ? null : bindings.get(METHOD_FOR_FUNCTION, setter);
133    
134                Pair<Type, String> field = bindings.get(FIELD_FOR_PROPERTY, property);
135                Type fieldType;
136                String fieldName;
137                boolean isStaticInOuter;
138                Method syntheticMethod;
139                if (field != null) {
140                    fieldType = field.first;
141                    fieldName = field.second;
142                    isStaticInOuter = bindings.get(STATIC_FIELD_IN_OUTER_CLASS, property);
143                    syntheticMethod = bindings.get(SYNTHETIC_METHOD_FOR_PROPERTY, property);
144                }
145                else {
146                    fieldType = null;
147                    fieldName = null;
148                    isStaticInOuter = false;
149                    syntheticMethod = bindings.get(SYNTHETIC_METHOD_FOR_PROPERTY, property);
150                }
151    
152                JvmProtoBuf.JvmPropertySignature signature;
153                if (callable instanceof DeserializedPropertyDescriptor) {
154                    DeserializedPropertyDescriptor deserializedCallable = (DeserializedPropertyDescriptor) callable;
155                    signature = signatureSerializer.copyPropertySignature(
156                            deserializedCallable.getProto().getExtension(JvmProtoBuf.propertySignature),
157                            deserializedCallable.getNameResolver()
158                    );
159                }
160                else {
161                    signature = signatureSerializer
162                            .propertySignature(fieldType, fieldName, isStaticInOuter, syntheticMethod, getterMethod, setterMethod);
163                }
164                proto.setExtension(JvmProtoBuf.propertySignature, signature);
165            }
166        }
167    
168        private void saveImplClassName(
169                @NotNull CallableMemberDescriptor callable,
170                @NotNull ProtoBuf.Callable.Builder proto,
171                @NotNull StringTable stringTable
172        ) {
173            String name = bindings.get(IMPL_CLASS_NAME_FOR_CALLABLE, callable);
174            if (name != null) {
175                proto.setExtension(JvmProtoBuf.implClassName, stringTable.getSimpleNameIndex(Name.identifier(name)));
176            }
177        }
178    
179        private static class SignatureSerializer {
180            private final StringTable stringTable;
181    
182            public SignatureSerializer(@NotNull StringTable stringTable) {
183                this.stringTable = stringTable;
184            }
185    
186            @NotNull
187            public JvmProtoBuf.JvmMethodSignature copyMethodSignature(
188                    @NotNull JvmProtoBuf.JvmMethodSignature signature,
189                    @NotNull NameResolver nameResolver
190            ) {
191                String method = new SignatureDeserializer(nameResolver).methodSignatureString(signature);
192                return methodSignature(getAsmMethod(method));
193            }
194    
195            @NotNull
196            public JvmProtoBuf.JvmMethodSignature methodSignature(@NotNull Method method) {
197                JvmProtoBuf.JvmMethodSignature.Builder signature = JvmProtoBuf.JvmMethodSignature.newBuilder();
198    
199                signature.setName(stringTable.getStringIndex(method.getName()));
200    
201                signature.setReturnType(type(method.getReturnType()));
202    
203                for (Type type : method.getArgumentTypes()) {
204                    signature.addParameterType(type(type));
205                }
206    
207                return signature.build();
208            }
209    
210            @NotNull
211            public JvmProtoBuf.JvmPropertySignature copyPropertySignature(
212                    @NotNull JvmProtoBuf.JvmPropertySignature signature,
213                    @NotNull NameResolver nameResolver
214            ) {
215                Type fieldType;
216                String fieldName;
217                boolean isStaticInOuter;
218                SignatureDeserializer signatureDeserializer = new SignatureDeserializer(nameResolver);
219                if (signature.hasField()) {
220                    JvmProtoBuf.JvmFieldSignature field = signature.getField();
221                    fieldType = Type.getType(signatureDeserializer.typeDescriptor(field.getType()));
222                    fieldName = nameResolver.getName(field.getName()).asString();
223                    isStaticInOuter = field.getIsStaticInOuter();
224                }
225                else {
226                    fieldType = null;
227                    fieldName = null;
228                    isStaticInOuter = false;
229                }
230    
231                Method syntheticMethod = signature.hasSyntheticMethod()
232                        ? getAsmMethod(signatureDeserializer.methodSignatureString(signature.getSyntheticMethod()))
233                        : null;
234    
235                Method getter = signature.hasGetter() ? getAsmMethod(signatureDeserializer.methodSignatureString(signature.getGetter())) : null;
236                Method setter = signature.hasSetter() ? getAsmMethod(signatureDeserializer.methodSignatureString(signature.getSetter())) : null;
237    
238                return propertySignature(fieldType, fieldName, isStaticInOuter, syntheticMethod, getter, setter);
239            }
240    
241            @NotNull
242            public JvmProtoBuf.JvmPropertySignature propertySignature(
243                    @Nullable Type fieldType,
244                    @Nullable String fieldName,
245                    boolean isStaticInOuter,
246                    @Nullable Method syntheticMethod,
247                    @Nullable Method getter,
248                    @Nullable Method setter
249            ) {
250                JvmProtoBuf.JvmPropertySignature.Builder signature = JvmProtoBuf.JvmPropertySignature.newBuilder();
251    
252                if (fieldType != null) {
253                    assert fieldName != null : "Field name shouldn't be null when there's a field type: " + fieldType;
254                    signature.setField(fieldSignature(fieldType, fieldName, isStaticInOuter));
255                }
256    
257                if (syntheticMethod != null) {
258                    signature.setSyntheticMethod(methodSignature(syntheticMethod));
259                }
260    
261                if (getter != null) {
262                    signature.setGetter(methodSignature(getter));
263                }
264                if (setter != null) {
265                    signature.setSetter(methodSignature(setter));
266                }
267    
268                return signature.build();
269            }
270    
271            @NotNull
272            public JvmProtoBuf.JvmFieldSignature fieldSignature(@NotNull Type type, @NotNull String name, boolean isStaticInOuter) {
273                JvmProtoBuf.JvmFieldSignature.Builder signature = JvmProtoBuf.JvmFieldSignature.newBuilder();
274                signature.setName(stringTable.getStringIndex(name));
275                signature.setType(type(type));
276                if (isStaticInOuter) {
277                    signature.setIsStaticInOuter(true);
278                }
279                return signature.build();
280            }
281    
282            @NotNull
283            public JvmProtoBuf.JvmType type(@NotNull Type givenType) {
284                JvmProtoBuf.JvmType.Builder builder = JvmProtoBuf.JvmType.newBuilder();
285    
286                Type type = givenType;
287                if (type.getSort() == Type.ARRAY) {
288                    builder.setArrayDimension(type.getDimensions());
289                    type = type.getElementType();
290                }
291    
292                if (type.getSort() == Type.OBJECT) {
293                    FqName fqName = internalNameToFqName(type.getInternalName());
294                    builder.setClassFqName(stringTable.getFqNameIndex(fqName));
295                }
296                else {
297                    builder.setPrimitiveType(JvmProtoBuf.JvmType.PrimitiveType.valueOf(type.getSort()));
298                }
299    
300                return builder.build();
301            }
302    
303            @NotNull
304            private static FqName internalNameToFqName(@NotNull String internalName) {
305                return FqName.fromSegments(Arrays.asList(internalName.split("/")));
306            }
307        }
308    
309        @NotNull
310        private static Method getAsmMethod(@NotNull String nameAndDesc) {
311            int indexOf = nameAndDesc.indexOf('(');
312            return new Method(nameAndDesc.substring(0, indexOf), nameAndDesc.substring(indexOf));
313        }
314    }