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.ClassMapperLite;
033 import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
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 : ClassMapperLite.mapClass(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 }