mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin debugger] IDEA-357870 Introduce MetadataDebugHelper
Kotlin compiler stores meta information about every class in the `kotlin.Metadata` annotation. Unfortunately, there is no way to fetch this annotation using plain JDI interface, because unlike method information, class annotations are not exposed to the debugger. This commit adds a helper class that allows fetching `kotlin.Metadata` of a specific class as a JSON string. GitOrigin-RevId: 4d463abcd21f75acf90a2358d5782ee1b8b524d2
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d7d6d06351
commit
856194d31d
@@ -0,0 +1,132 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.rt.debugger;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class MetadataDebugHelper {
|
||||
private static final String METADATA_CLASS_NAME = "kotlin.Metadata";
|
||||
|
||||
private static final String KIND = "kind";
|
||||
private static final String METADATA_VERSION = "metadataVersion";
|
||||
private static final String DATA_1 = "data1";
|
||||
private static final String DATA_2 = "data2";
|
||||
private static final String EXTRA_STRING = "extraString";
|
||||
private static final String PACKAGE_NAME = "packageName";
|
||||
private static final String EXTRA_INT = "extraInt";
|
||||
|
||||
private static final String KIND_METHOD_NAME = "k";
|
||||
private static final String METADATA_VERSION_METHOD_NAME = "mv";
|
||||
private static final String DATA_1_METHOD_NAME = "d1";
|
||||
private static final String DATA_2_METHOD_NAME = "d2";
|
||||
private static final String EXTRA_STRING_METHOD_NAME = "xs";
|
||||
private static final String PACKAGE_NAME_METHOD_NAME = "pn";
|
||||
private static final String EXTRA_INT_METHOD_NAME = "xi";
|
||||
|
||||
/*
|
||||
* Kotlin debugger uses this function to fetch @kotlin.Metadata for a given class.
|
||||
* Originally, reading annotations from the debugger is not supported by JVM.
|
||||
*
|
||||
* The metadata is returned as a string in JSON format, where the `data1` field is
|
||||
* base64 encoded to keep the format valid.
|
||||
*
|
||||
* Returning metadata as a single string allows us to minimize the number of JDI calls from
|
||||
* the debugger side. Any JDWP communication can be very expensive when debugging remotely,
|
||||
* especially when debugging Android applications on a phone.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static String getDebugMetadataAsJson(Class<?> cls) {
|
||||
try {
|
||||
Class<?> metadataClass = Class.forName(METADATA_CLASS_NAME, false, cls.getClassLoader());
|
||||
Method kindMethod = metadataClass.getDeclaredMethod(KIND_METHOD_NAME);
|
||||
Method metadataVersionMethod = metadataClass.getDeclaredMethod(METADATA_VERSION_METHOD_NAME);
|
||||
Method data1Method = metadataClass.getDeclaredMethod(DATA_1_METHOD_NAME);
|
||||
Method data2Method = metadataClass.getDeclaredMethod(DATA_2_METHOD_NAME);
|
||||
Method extraStringMethod = metadataClass.getDeclaredMethod(EXTRA_STRING_METHOD_NAME);
|
||||
Method packageNameMethod = metadataClass.getDeclaredMethod(PACKAGE_NAME_METHOD_NAME);
|
||||
Method extraIntMethod = metadataClass.getDeclaredMethod(EXTRA_INT_METHOD_NAME);
|
||||
|
||||
Object metadata = cls.getAnnotation((Class<Annotation>)metadataClass);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
|
||||
int kind = (int)kindMethod.invoke(metadata);
|
||||
appendAsJsonValue(sb, KIND, kind);
|
||||
|
||||
int[] metadataVersion = (int[]) metadataVersionMethod.invoke(metadata);
|
||||
appendAsJsonValue(sb, METADATA_VERSION, Arrays.toString(metadataVersion));
|
||||
|
||||
String[] data1 = (String[])data1Method.invoke(metadata);
|
||||
appendAsJsonValue(sb, DATA_1, Arrays.toString(toJsonBase64Encoded(data1)));
|
||||
|
||||
String[] data2 = (String[])data2Method.invoke(metadata);
|
||||
appendAsJsonValue(sb, DATA_2, Arrays.toString(toJson(data2)));
|
||||
|
||||
String extraString = (String) extraStringMethod.invoke(metadata);
|
||||
appendAsJsonValue(sb, EXTRA_STRING, toJson(extraString));
|
||||
|
||||
String packageName = (String) packageNameMethod.invoke(metadata);
|
||||
appendAsJsonValue(sb, PACKAGE_NAME, toJson(packageName));
|
||||
|
||||
int extraInt = (int)extraIntMethod.invoke(metadata);
|
||||
appendAsJsonValueNoComma(sb, EXTRA_INT, extraInt);
|
||||
|
||||
sb.append("}");
|
||||
|
||||
return sb.toString();
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendAsJsonValue(StringBuilder sb, String name, Object value) {
|
||||
appendAsJsonValueNoComma(sb, name, value).append(',');
|
||||
}
|
||||
|
||||
private static StringBuilder appendAsJsonValueNoComma(StringBuilder sb, String name, Object value) {
|
||||
return sb.append(toJson(name)).append(':').append(value);
|
||||
}
|
||||
|
||||
private static String toJson(String str) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('"');
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '"') {
|
||||
sb.append("\\\"");
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
sb.append('"');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String[] toJson(String[] array) {
|
||||
String[] result = new String[array.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
result[i] = toJson(array[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PrimitiveArrayArgumentToVarargsMethod")
|
||||
private static String[] toJsonBase64Encoded(String[] array) throws Exception {
|
||||
Class<?> base64Class = Class.forName("java.util.Base64");
|
||||
Method getEncoding = base64Class.getDeclaredMethod("getEncoder");
|
||||
Object encoder = getEncoding.invoke(base64Class);
|
||||
Class<?> encoderClass = Class.forName("java.util.Base64$Encoder");
|
||||
Method encodeToString = encoderClass.getDeclaredMethod("encodeToString", byte[].class);
|
||||
|
||||
String[] result = new String[array.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
byte[] bytes = array[i].getBytes(StandardCharsets.UTF_8);
|
||||
String encoded = (String)encodeToString.invoke(encoder, bytes);
|
||||
result[i] = toJson(encoded);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user