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.inline;
018
019 import com.intellij.openapi.vfs.VirtualFile;
020 import com.intellij.psi.PsiFile;
021 import kotlin.text.StringsKt;
022 import org.jetbrains.annotations.NotNull;
023 import org.jetbrains.annotations.Nullable;
024 import org.jetbrains.kotlin.backend.common.output.OutputFile;
025 import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
026 import org.jetbrains.kotlin.codegen.AsmUtil;
027 import org.jetbrains.kotlin.codegen.ExpressionCodegen;
028 import org.jetbrains.kotlin.codegen.MemberCodegen;
029 import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
030 import org.jetbrains.kotlin.codegen.context.CodegenContext;
031 import org.jetbrains.kotlin.codegen.context.CodegenContextUtil;
032 import org.jetbrains.kotlin.codegen.context.InlineLambdaContext;
033 import org.jetbrains.kotlin.codegen.context.MethodContext;
034 import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructorsKt;
035 import org.jetbrains.kotlin.codegen.state.GenerationState;
036 import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
037 import org.jetbrains.kotlin.codegen.when.WhenByEnumsMapping;
038 import org.jetbrains.kotlin.descriptors.*;
039 import org.jetbrains.kotlin.fileClasses.FileClasses;
040 import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider;
041 import org.jetbrains.kotlin.load.java.JvmAbi;
042 import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder;
043 import org.jetbrains.kotlin.name.ClassId;
044 import org.jetbrains.kotlin.name.FqName;
045 import org.jetbrains.kotlin.name.Name;
046 import org.jetbrains.kotlin.psi.KtFile;
047 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
048 import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
049 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
050 import org.jetbrains.kotlin.types.KotlinType;
051 import org.jetbrains.kotlin.util.OperatorNameConventions;
052 import org.jetbrains.org.objectweb.asm.*;
053 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
054 import org.jetbrains.org.objectweb.asm.tree.*;
055 import org.jetbrains.org.objectweb.asm.util.Textifier;
056 import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
057
058 import java.io.IOException;
059 import java.io.PrintWriter;
060 import java.io.StringWriter;
061 import java.util.List;
062 import java.util.ListIterator;
063
064 import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.ENUM_TYPE;
065 import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_CLASS_TYPE;
066
067 public class InlineCodegenUtil {
068 public static final boolean GENERATE_SMAP = true;
069 public static final int API = Opcodes.ASM5;
070
071 private static final String CAPTURED_FIELD_PREFIX = "$";
072 private static final String NON_CAPTURED_FIELD_PREFIX = "$$";
073 public static final String THIS$0 = "this$0";
074 public static final String THIS = "this";
075 private static final String RECEIVER$0 = "receiver$0";
076 private static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$";
077 public static final String FIRST_FUN_LABEL = "$$$$$ROOT$$$$$";
078 public static final String NUMBERED_FUNCTION_PREFIX = "kotlin/jvm/functions/Function";
079 private static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker";
080 private static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall";
081 private static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall";
082 private static final String INLINE_MARKER_FINALLY_START = "finallyStart";
083 private static final String INLINE_MARKER_FINALLY_END = "finallyEnd";
084 public static final String SPECIAL_TRANSFORMATION_NAME = "$special";
085 public static final String INLINE_TRANSFORMATION_SUFFIX = "$inlined";
086 public static final String INLINE_CALL_TRANSFORMATION_SUFFIX = "$" + INLINE_TRANSFORMATION_SUFFIX;
087 public static final String INLINE_FUN_THIS_0_SUFFIX = "$inline_fun";
088 public static final String INLINE_FUN_VAR_SUFFIX = "$iv";
089
090 @Nullable
091 public static SMAPAndMethodNode getMethodNode(
092 byte[] classData,
093 final String methodName,
094 final String methodDescriptor,
095 ClassId classId,
096 final @NotNull GenerationState state
097 ) {
098 ClassReader cr = new ClassReader(classData);
099 final MethodNode[] node = new MethodNode[1];
100 final String[] debugInfo = new String[2];
101 final int[] lines = new int[2];
102 lines[0] = Integer.MAX_VALUE;
103 lines[1] = Integer.MIN_VALUE;
104 //noinspection PointlessBitwiseExpression
105 cr.accept(new ClassVisitor(API) {
106 @Override
107 public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
108 assertVersionNotGreaterThanGeneratedOne(version, name, state);
109 }
110
111 @Override
112 public void visitSource(String source, String debug) {
113 super.visitSource(source, debug);
114 debugInfo[0] = source;
115 debugInfo[1] = debug;
116 }
117
118 @Override
119 public MethodVisitor visitMethod(
120 int access,
121 @NotNull String name,
122 @NotNull String desc,
123 String signature,
124 String[] exceptions
125 ) {
126 if (methodName.equals(name) && methodDescriptor.equals(desc)) {
127 node[0] = new MethodNode(API, access, name, desc, signature, exceptions) {
128 @Override
129 public void visitLineNumber(int line, @NotNull Label start) {
130 super.visitLineNumber(line, start);
131 lines[0] = Math.min(lines[0], line);
132 lines[1] = Math.max(lines[1], line);
133 }
134 };
135 return node[0];
136 }
137 return null;
138 }
139 }, ClassReader.SKIP_FRAMES | (GENERATE_SMAP ? 0 : ClassReader.SKIP_DEBUG));
140
141 if (node[0] == null) {
142 return null;
143 }
144
145 if (classId.equals(IntrinsicArrayConstructorsKt.getClassId())) {
146 // Don't load source map for intrinsic array constructors
147 debugInfo[0] = null;
148 }
149
150 SMAP smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classId.asString(), lines[0], lines[1]);
151 return new SMAPAndMethodNode(node[0], smap);
152 }
153
154 public static void assertVersionNotGreaterThanGeneratedOne(int version, String internalName, @NotNull GenerationState state) {
155 // TODO: report a proper diagnostic
156 if (version > state.getClassFileVersion() && !"true".equals(System.getProperty("kotlin.skip.bytecode.version.check"))) {
157 throw new UnsupportedOperationException(
158 "Cannot inline bytecode of class " + internalName + " which has version " + version + ". " +
159 "This compiler can only inline Java 1.6 bytecode (version " + Opcodes.V1_6 + ")"
160 );
161 }
162 }
163
164 public static void initDefaultSourceMappingIfNeeded(
165 @NotNull CodegenContext context, @NotNull MemberCodegen codegen, @NotNull GenerationState state
166 ) {
167 if (state.isInlineDisabled()) return;
168
169 CodegenContext<?> parentContext = context.getParentContext();
170 while (parentContext != null) {
171 if (parentContext.isInlineMethodContext()) {
172 //just init default one to one mapping
173 codegen.getOrCreateSourceMapper();
174 break;
175 }
176 parentContext = parentContext.getParentContext();
177 }
178 }
179
180 @Nullable
181 public static VirtualFile findVirtualFile(@NotNull GenerationState state, @NotNull ClassId classId) {
182 return JvmVirtualFileFinder.SERVICE.getInstance(state.getProject()).findVirtualFileWithHeader(classId);
183 }
184
185 @Nullable
186 private static VirtualFile findVirtualFileImprecise(@NotNull GenerationState state, @NotNull String internalClassName) {
187 FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName();
188 String classNameWithDollars = StringsKt.substringAfterLast(internalClassName, "/", internalClassName);
189 //TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file
190 // we construct valid.package.name/RelativeClassNameAsSingleName that should work in compiler, but fails for inner classes in IDE
191 return findVirtualFile(state, new ClassId(packageFqName, Name.identifier(classNameWithDollars)));
192 }
193
194 @NotNull
195 public static String getInlineName(
196 @NotNull CodegenContext codegenContext,
197 @NotNull KotlinTypeMapper typeMapper,
198 @NotNull JvmFileClassesProvider fileClassesManager
199 ) {
200 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper, fileClassesManager);
201 }
202
203 @NotNull
204 private static String getInlineName(
205 @NotNull CodegenContext codegenContext,
206 @NotNull DeclarationDescriptor currentDescriptor,
207 @NotNull KotlinTypeMapper typeMapper,
208 @NotNull JvmFileClassesProvider fileClassesProvider
209 ) {
210 if (currentDescriptor instanceof PackageFragmentDescriptor) {
211 PsiFile file = DescriptorToSourceUtils.getContainingFile(codegenContext.getContextDescriptor());
212
213 Type implementationOwnerType;
214 if (file == null) {
215 implementationOwnerType = CodegenContextUtil.getImplementationOwnerClassType(codegenContext);
216 }
217 else {
218 implementationOwnerType = FileClasses.getFileClassType(fileClassesProvider, (KtFile) file);
219 }
220
221 if (implementationOwnerType == null) {
222 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
223 //noinspection ConstantConditions
224 throw new RuntimeException(
225 "Couldn't find declaration for " +
226 contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() +
227 "; context: " + codegenContext
228 );
229 }
230
231 return implementationOwnerType.getInternalName();
232 }
233 else if (currentDescriptor instanceof ClassifierDescriptor) {
234 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor);
235 return type.getInternalName();
236 }
237 else if (currentDescriptor instanceof FunctionDescriptor) {
238 ClassDescriptor descriptor =
239 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_CALLABLE, (FunctionDescriptor) currentDescriptor);
240 if (descriptor != null) {
241 return typeMapper.mapType(descriptor).getInternalName();
242 }
243 }
244
245 //TODO: add suffix for special case
246 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString();
247
248 //noinspection ConstantConditions
249 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper, fileClassesProvider) + "$" + suffix;
250 }
251
252 public static boolean isInvokeOnLambda(@NotNull String owner, @NotNull String name) {
253 return OperatorNameConventions.INVOKE.asString().equals(name) &&
254 owner.startsWith(NUMBERED_FUNCTION_PREFIX) &&
255 isInteger(owner.substring(NUMBERED_FUNCTION_PREFIX.length()));
256 }
257
258 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) {
259 return "<init>".equals(methodName) && isAnonymousClass(internalName);
260 }
261
262 public static boolean isWhenMappingAccess(@NotNull String internalName, @NotNull String fieldName) {
263 return fieldName.startsWith(WhenByEnumsMapping.MAPPING_ARRAY_FIELD_PREFIX) &&
264 internalName.endsWith(WhenByEnumsMapping.MAPPINGS_CLASS_NAME_POSTFIX);
265 }
266
267 public static boolean isAnonymousSingletonLoad(@NotNull String internalName, @NotNull String fieldName) {
268 return JvmAbi.INSTANCE_FIELD.equals(fieldName) && isAnonymousClass(internalName);
269 }
270
271 public static boolean isAnonymousClass(@NotNull String internalName) {
272 String shortName = getLastNamePart(internalName);
273 int index = shortName.lastIndexOf("$");
274
275 if (index < 0) {
276 return false;
277 }
278
279 String suffix = shortName.substring(index + 1);
280 return isInteger(suffix);
281 }
282
283 @NotNull
284 private static String getLastNamePart(@NotNull String internalName) {
285 int index = internalName.lastIndexOf("/");
286 return index < 0 ? internalName : internalName.substring(index + 1);
287 }
288
289 @NotNull
290 public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
291 return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode);
292 }
293
294 private static boolean isInteger(@NotNull String string) {
295 if (string.isEmpty()) {
296 return false;
297 }
298
299 for (int i = 0; i < string.length(); i++) {
300 if (!Character.isDigit(string.charAt(i))) {
301 return false;
302 }
303 }
304
305 return true;
306 }
307
308 public static boolean isCapturedFieldName(@NotNull String fieldName) {
309 // TODO: improve this heuristic
310 return fieldName.startsWith(CAPTURED_FIELD_PREFIX) &&
311 !fieldName.startsWith(NON_CAPTURED_FIELD_PREFIX) ||
312 THIS$0.equals(fieldName) ||
313 RECEIVER$0.equals(fieldName);
314 }
315
316 public static boolean isReturnOpcode(int opcode) {
317 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN;
318 }
319
320 //marked return could be either non-local or local in case of labeled lambda self-returns
321 public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) {
322 return getMarkedReturnLabelOrNull(returnIns) != null;
323 }
324
325 @Nullable
326 public static String getMarkedReturnLabelOrNull(@NotNull AbstractInsnNode returnInsn) {
327 if (!isReturnOpcode(returnInsn.getOpcode())) {
328 return null;
329 }
330 AbstractInsnNode previous = returnInsn.getPrevious();
331 if (previous instanceof MethodInsnNode) {
332 MethodInsnNode marker = (MethodInsnNode) previous;
333 if (NON_LOCAL_RETURN.equals(marker.owner)) {
334 return marker.name;
335 }
336 }
337 return null;
338 }
339
340 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) {
341 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false);
342 }
343
344 @NotNull
345 public static Type getReturnType(int opcode) {
346 switch (opcode) {
347 case Opcodes.RETURN:
348 return Type.VOID_TYPE;
349 case Opcodes.IRETURN:
350 return Type.INT_TYPE;
351 case Opcodes.DRETURN:
352 return Type.DOUBLE_TYPE;
353 case Opcodes.FRETURN:
354 return Type.FLOAT_TYPE;
355 case Opcodes.LRETURN:
356 return Type.LONG_TYPE;
357 default:
358 return AsmTypes.OBJECT_TYPE;
359 }
360 }
361
362 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) {
363 ListIterator<AbstractInsnNode> iterator = from.instructions.iterator();
364 while (iterator.hasNext()) {
365 AbstractInsnNode next = iterator.next();
366 to.instructions.insertBefore(beforeNode, next);
367 }
368 }
369
370 @NotNull
371 public static MethodNode createEmptyMethodNode() {
372 return new MethodNode(API, 0, "fake", "()V", null, null);
373 }
374
375 @NotNull
376 public static LabelNode firstLabelInChain(@NotNull LabelNode node) {
377 LabelNode curNode = node;
378 while (curNode.getPrevious() instanceof LabelNode) {
379 curNode = (LabelNode) curNode.getPrevious();
380 }
381 return curNode;
382 }
383
384 @NotNull
385 public static String getNodeText(@Nullable MethodNode node) {
386 Textifier textifier = new Textifier();
387 if (node == null) {
388 return "Not generated";
389 }
390 node.accept(new TraceMethodVisitor(textifier));
391 StringWriter sw = new StringWriter();
392 textifier.print(new PrintWriter(sw));
393 sw.flush();
394 return node.name + " " + node.desc + ":\n" + sw.getBuffer().toString();
395 }
396
397 @NotNull
398 public static String getInsnText(@Nullable AbstractInsnNode node) {
399 if (node == null) return "<null>";
400 Textifier textifier = new Textifier();
401 node.accept(new TraceMethodVisitor(textifier));
402 StringWriter sw = new StringWriter();
403 textifier.print(new PrintWriter(sw));
404 sw.flush();
405 return sw.toString().trim();
406 }
407
408 @NotNull
409 /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) {
410 //try to find just compiled classes then in dependencies
411 try {
412 OutputFile outputFile = state.getFactory().get(internalName + ".class");
413 if (outputFile != null) {
414 return new ClassReader(outputFile.asByteArray());
415 }
416 VirtualFile file = findVirtualFileImprecise(state, internalName);
417 if (file != null) {
418 return new ClassReader(file.contentsToByteArray());
419 }
420 throw new RuntimeException("Couldn't find virtual file for " + internalName);
421 }
422 catch (IOException e) {
423 throw new RuntimeException(e);
424 }
425 }
426
427 public static void generateFinallyMarker(@NotNull InstructionAdapter v, int depth, boolean start) {
428 v.iconst(depth);
429 v.invokestatic(INLINE_MARKER_CLASS_NAME, start ? INLINE_MARKER_FINALLY_START : INLINE_MARKER_FINALLY_END, "(I)V", false);
430 }
431
432 public static boolean isFinallyEnd(@NotNull AbstractInsnNode node) {
433 return isFinallyMarker(node, INLINE_MARKER_FINALLY_END);
434 }
435
436 public static boolean isFinallyStart(@NotNull AbstractInsnNode node) {
437 return isFinallyMarker(node, INLINE_MARKER_FINALLY_START);
438 }
439
440 public static boolean isFinallyMarker(@Nullable AbstractInsnNode node) {
441 return node != null && (isFinallyStart(node) || isFinallyEnd(node));
442 }
443
444 private static boolean isFinallyMarker(@NotNull AbstractInsnNode node, String name) {
445 if (!(node instanceof MethodInsnNode)) return false;
446 MethodInsnNode method = (MethodInsnNode) node;
447 return INLINE_MARKER_CLASS_NAME.equals(method.owner) && name.equals(method.name);
448 }
449
450 public static boolean isFinallyMarkerRequired(@NotNull MethodContext context) {
451 return context.isInlineMethodContext() || context instanceof InlineLambdaContext;
452 }
453
454 public static int getConstant(@NotNull AbstractInsnNode ins) {
455 int opcode = ins.getOpcode();
456 Integer value;
457 if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) {
458 return opcode - Opcodes.ICONST_0;
459 }
460 else if (opcode == Opcodes.BIPUSH || opcode == Opcodes.SIPUSH) {
461 return ((IntInsnNode) ins).operand;
462 }
463 else {
464 LdcInsnNode index = (LdcInsnNode) ins;
465 return (Integer) index.cst;
466 }
467 }
468
469 public static void addInlineMarker(@NotNull InstructionAdapter v, boolean isStartNotEnd) {
470 v.visitMethodInsn(
471 Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME,
472 isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME,
473 "()V", false
474 );
475 }
476
477 public static boolean isInlineMarker(@NotNull AbstractInsnNode insn) {
478 return isInlineMarker(insn, null);
479 }
480
481 private static boolean isInlineMarker(@NotNull AbstractInsnNode insn, @Nullable String name) {
482 if (!(insn instanceof MethodInsnNode)) {
483 return false;
484 }
485
486 MethodInsnNode methodInsnNode = (MethodInsnNode) insn;
487 return insn.getOpcode() == Opcodes.INVOKESTATIC &&
488 methodInsnNode.owner.equals(INLINE_MARKER_CLASS_NAME) &&
489 (name != null ? methodInsnNode.name.equals(name)
490 : methodInsnNode.name.equals(INLINE_MARKER_BEFORE_METHOD_NAME) ||
491 methodInsnNode.name.equals(INLINE_MARKER_AFTER_METHOD_NAME));
492 }
493
494 public static boolean isBeforeInlineMarker(@NotNull AbstractInsnNode insn) {
495 return isInlineMarker(insn, INLINE_MARKER_BEFORE_METHOD_NAME);
496 }
497
498 public static boolean isAfterInlineMarker(@NotNull AbstractInsnNode insn) {
499 return isInlineMarker(insn, INLINE_MARKER_AFTER_METHOD_NAME);
500 }
501
502 public static int getLoadStoreArgSize(int opcode) {
503 return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1;
504 }
505
506 public static boolean isStoreInstruction(int opcode) {
507 return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE;
508 }
509
510 public static int calcMarkerShift(@NotNull Parameters parameters, @NotNull MethodNode node) {
511 int markerShiftTemp = getIndexAfterLastMarker(node);
512 return markerShiftTemp - parameters.getRealParametersSizeOnStack() + parameters.getArgsSizeOnStack();
513 }
514
515 private static int getIndexAfterLastMarker(@NotNull MethodNode node) {
516 int result = -1;
517 for (LocalVariableNode variable : node.localVariables) {
518 if (isFakeLocalVariableForInline(variable.name)) {
519 result = Math.max(result, variable.index + 1);
520 }
521 }
522 return result;
523 }
524
525 public static boolean isFakeLocalVariableForInline(@NotNull String name) {
526 return name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) ||
527 name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT);
528 }
529
530 public static boolean isThis0(@NotNull String name) {
531 return THIS$0.equals(name);
532 }
533
534 public static boolean isSpecialEnumMethod(@NotNull FunctionDescriptor functionDescriptor) {
535 DeclarationDescriptor containingDeclaration = functionDescriptor.getContainingDeclaration();
536 if (!(containingDeclaration instanceof PackageFragmentDescriptor)) {
537 return false;
538 }
539 if (!((PackageFragmentDescriptor) containingDeclaration).getFqName().equals(KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME)) {
540 return false;
541 }
542 if (functionDescriptor.getTypeParameters().size() != 1) {
543 return false;
544 }
545 String name = functionDescriptor.getName().asString();
546 List<ValueParameterDescriptor> parameters = functionDescriptor.getValueParameters();
547 return "enumValues".equals(name) && parameters.size() == 0 ||
548 "enumValueOf".equals(name) && parameters.size() == 1 && KotlinBuiltIns.isString(parameters.get(0).getType());
549 }
550
551 public static MethodNode createSpecialEnumMethodBody(
552 @NotNull ExpressionCodegen codegen,
553 @NotNull String name,
554 @NotNull KotlinType type,
555 @NotNull KotlinTypeMapper typeMapper
556 ) {
557 boolean isValueOf = "enumValueOf".equals(name);
558 Type invokeType = typeMapper.mapType(type);
559 String desc = getSpecialEnumFunDescriptor(invokeType, isValueOf);
560 MethodNode node = new MethodNode(API, Opcodes.ACC_STATIC, "fake", desc, null, null);
561 codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.ENUM_REIFIED, new InstructionAdapter(node));
562 if (isValueOf) {
563 node.visitInsn(Opcodes.ACONST_NULL);
564 node.visitVarInsn(Opcodes.ALOAD, 0);
565
566 node.visitMethodInsn(Opcodes.INVOKESTATIC, ENUM_TYPE.getInternalName(), "valueOf",
567 Type.getMethodDescriptor(ENUM_TYPE, JAVA_CLASS_TYPE, AsmTypes.JAVA_STRING_TYPE), false);
568 }
569 else {
570 node.visitInsn(Opcodes.ICONST_0);
571 node.visitTypeInsn(Opcodes.ANEWARRAY, ENUM_TYPE.getInternalName());
572 }
573 node.visitInsn(Opcodes.ARETURN);
574 node.visitMaxs(isValueOf ? 3 : 2, isValueOf ? 1 : 0);
575 return node;
576 }
577
578 @NotNull
579 public static String getSpecialEnumFunDescriptor(@NotNull Type type, boolean isValueOf) {
580 return isValueOf ? Type.getMethodDescriptor(type, AsmTypes.JAVA_STRING_TYPE) : Type.getMethodDescriptor(AsmUtil.getArrayType(type));
581 }
582 }
583