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