我无法在 ASM JAVA 中为 invokedynamic 加载局部变量
I can't load local variables for invokedynamic in ASM JAVA
我已经为方法创建了一个迷你记录器,并且我使用了 ASM。我需要通过描述符方法参数来确定并打印它。
但是我有一个错误
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
ru/otus/TestLogging.calc(IFD)V @6: invokedynamic
Reason:
Type 'java/io/PrintStream' (current frame, stack[4]) is not assignable to double_2nd
Current Frame:
bci: @6
flags: { }
locals: { 'ru/otus/TestLogging', integer, float, double, double_2nd }
stack: { 'ru/otus/TestLogging', float, double, double_2nd, 'java/io/PrintStream' }
Bytecode:
0000000: 2a24 29b2 0007 ba00 3e00 00b6 0011 b200
0000010: 071b 2429 ba00 0d00 00b6 0011 b1
这是我的代理人的代码
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.contains("ru/otus/")) {
return changeMethod(classfileBuffer, className);
}
return classfileBuffer;
}
});
}
private static byte[] changeMethod(byte[] originalClass, String className) {
ClassReader reader = new ClassReader(originalClass);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
ArrayList<String> list = new ArrayList<>();
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){
System.out.println("visitMethod: access="+access+" name="+name+" desc="+descriptor+" signature="+signature+" exceptions="+exceptions);
Method thisMethod = new Method(name, descriptor);
MethodVisitor mv = new MethodAnnotationScanner(Opcodes.ASM5, super.visitMethod(access, name, descriptor, signature, exceptions), thisMethod, className);
return mv;
}
};
reader.accept(visitor, Opcodes.ASM5);
for(String methodName : list) {
System.out.println(methodName);
}
byte[] finalClass = writer.toByteArray();
if(className.contains("Test")) {
try (OutputStream fos = new FileOutputStream("TestLogging.class")) {
fos.write(finalClass);
} catch (Exception e) {
e.printStackTrace();
}
}
return writer.toByteArray();
}
static class MethodAnnotationScanner extends MethodVisitor {
private Method thisMethod;
private boolean isChangeMethod = false;
private String className = null;
private StringBuilder descriptor = new StringBuilder("(");
public MethodAnnotationScanner(int api, MethodVisitor methodVisitor, Method thisMethod, String className) {
super(api, methodVisitor);
this.thisMethod = thisMethod;
this.className = className;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc="+desc+" visible="+visible);
if(desc.contains("ru/otus/annotations/Log")) {
this.isChangeMethod = true;
return super.visitAnnotation(desc, visible);
}
this.isChangeMethod = false;
return super.visitAnnotation(desc, visible);
}
@Override
public void visitCode() {
if(this.isChangeMethod) {
super.visitVarInsn(Opcodes.ALOAD, 0);
int i = 1;
for(Type arg : thisMethod.getArgumentTypes()) {
this.descriptor.append(arg.getDescriptor());
if (arg.getDescriptor().equals("J")) {
super.visitVarInsn(Opcodes.LLOAD, i);
++i;
} else if (arg.getDescriptor().equals("D")) {
super.visitVarInsn(Opcodes.DLOAD, i);
++i;
} else if (arg.getDescriptor().equals("F")) {
super.visitVarInsn(Opcodes.FLOAD, i);
} else if(arg.getDescriptor().equals("I")) {
super.visitVarInsn(Opcodes.ILOAD, i);
}
i++;
}
Handle handle = new Handle(
H_INVOKESTATIC,
Type.getInternalName(java.lang.invoke.StringConcatFactory.class),
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
false);
this.descriptor.append(")Ljava/lang/String;");
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
super.visitMaxs(0, 0);
}
if (mv != null) {
super.visitCode();
}
super.visitEnd();
}
}
}
我有两个 class 可以重现此功能。
首先 - 测试记录
第二个 - AutoLogger
在第一个class中,我有一个方法需要记录,
第二个开始 class 包含方法 main.
您在读取字段 System.out
之前推送字符串连接的参数,然后尝试执行字符串连接。因此,用于执行字符串连接的 invokedynamic
指令在操作数堆栈上发现了不匹配的 PrintStream
。
一个简单的解决方法是更改说明
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
到
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInsn(Opcodes.SWAP);
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
或者将 GETSTATIC
移动到将推送 concat 参数的代码之前,到执行过时的 super.visitVarInsn(Opcodes.ALOAD, 0);
的位置。那么,你不需要SWAP
.
但是代码的问题比较多。您在推送值时计算局部变量,正确考虑 long
和 double
取两个变量,但是,您在 ", param: \u0001".repeat(i)
表达式中使用相同的数字,这将告诉StringConcatFactory
在 long
和 double
的情况下有两个值。你需要分开柜台。此外,您没有推送引用类型参数,但由于您正在对它们进行计数并将它们包含在 concat 调用的签名中,因此您还必须将它们推送到操作数堆栈。
此外,虽然在这里没有效果,但 visitMaxs(0, 0)
调用和 visitEnd()
调用是不合适的。您在代码的开头注入,您不会拦截的其他访问调用将跟随,包括自动执行的 visitMaxs
和 visitEnd
.
所有修复后,代码看起来像
public void visitCode() {
if(this.isChangeMethod) {
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
int varIndex = 1, numArgs = 0;
for(Type arg : thisMethod.getArgumentTypes()) {
this.descriptor.append(arg.getDescriptor());
if (arg.getDescriptor().equals("J")) {
super.visitVarInsn(Opcodes.LLOAD, varIndex);
++varIndex;
} else if (arg.getDescriptor().equals("D")) {
super.visitVarInsn(Opcodes.DLOAD, varIndex);
++varIndex;
} else if (arg.getDescriptor().equals("F")) {
super.visitVarInsn(Opcodes.FLOAD, varIndex);
} else if(arg.getDescriptor().equals("I")) {
super.visitVarInsn(Opcodes.ILOAD, varIndex);
} else {
super.visitVarInsn(Opcodes.ALOAD, varIndex);
}
varIndex++;
numArgs++;
}
Handle handle = new Handle(
H_INVOKESTATIC,
Type.getInternalName(java.lang.invoke.StringConcatFactory.class),
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
false);
this.descriptor.append(")Ljava/lang/String;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(numArgs));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitCode();
}
附带说明一下,当你为连接推送所有参数时,你可以简化描述符的构造,因为连接描述符几乎与方法的描述符相同;您只需将 return 类型替换为 Ljava/lang/String;
.
您可以在不处理 Method
和 Type
对象的情况下完成整个操作,只需使用已经传递给 visitMethod
.
的两个字符串
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
System.out.println("visitMethod: access="+access+" name="+name
+" desc="+descriptor+" signature="+signature+" exceptions="+exceptions);
MethodVisitor mv = new MethodAnnotationScanner(Opcodes.ASM5, name, descriptor,
super.visitMethod(access, name, descriptor, signature, exceptions));
return mv;
}
static class MethodAnnotationScanner extends MethodVisitor {
private boolean isChangeMethod;
private final String name, descriptor;
public MethodAnnotationScanner(int api, String name,
String methodDesciptor, MethodVisitor methodVisitor){
super(api, methodVisitor);
this.name = name;
this.descriptor = methodDesciptor;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc="+desc+" visible="+visible);
if(desc.contains("ru/otus/annotations/Log")) {
this.isChangeMethod = true;
return super.visitAnnotation(desc, visible);
}
this.isChangeMethod = false;
return super.visitAnnotation(desc, visible);
}
@Override
public void visitCode() {
if(this.isChangeMethod) {
super.visitFieldInsn(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
int varIndex = 1, numArgs = 0, p;
for(p = 1; descriptor.charAt(p) != ')'; p++) {
switch(descriptor.charAt(p)) {
case 'J':
super.visitVarInsn(Opcodes.LLOAD, varIndex); ++varIndex; break;
case 'D':
super.visitVarInsn(Opcodes.DLOAD, varIndex); ++varIndex; break;
case 'F': super.visitVarInsn(Opcodes.FLOAD, varIndex); break;
case 'I': super.visitVarInsn(Opcodes.ILOAD, varIndex); break;
case 'L': super.visitVarInsn(Opcodes.ALOAD, varIndex);
p = descriptor.indexOf(';', p);
break;
case '[': super.visitVarInsn(Opcodes.ALOAD, varIndex);
do {} while(descriptor.charAt(++p)=='[');
if(descriptor.charAt(p) == 'L') p = descriptor.indexOf(';', p);
break;
default: throw new IllegalStateException(descriptor);
}
varIndex++;
numArgs++;
}
String ret = "Ljava/lang/String;";
String concatSig = new StringBuilder(++p + ret.length())
.append(descriptor, 0, p).append(ret).toString();
Handle handle = new Handle(
H_INVOKESTATIC,
"java/lang/invoke/StringConcatFactory",
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class,
String.class, MethodType.class, String.class, Object[].class)
.toMethodDescriptorString(),
false);
super.visitInvokeDynamicInsn("makeConcatWithConstants", concatSig, handle,
"executed method: " + name + ", param: \u0001".repeat(numArgs));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitCode();
}
}
我已经为方法创建了一个迷你记录器,并且我使用了 ASM。我需要通过描述符方法参数来确定并打印它。 但是我有一个错误
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
ru/otus/TestLogging.calc(IFD)V @6: invokedynamic
Reason:
Type 'java/io/PrintStream' (current frame, stack[4]) is not assignable to double_2nd
Current Frame:
bci: @6
flags: { }
locals: { 'ru/otus/TestLogging', integer, float, double, double_2nd }
stack: { 'ru/otus/TestLogging', float, double, double_2nd, 'java/io/PrintStream' }
Bytecode:
0000000: 2a24 29b2 0007 ba00 3e00 00b6 0011 b200
0000010: 071b 2429 ba00 0d00 00b6 0011 b1
这是我的代理人的代码
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.contains("ru/otus/")) {
return changeMethod(classfileBuffer, className);
}
return classfileBuffer;
}
});
}
private static byte[] changeMethod(byte[] originalClass, String className) {
ClassReader reader = new ClassReader(originalClass);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
ArrayList<String> list = new ArrayList<>();
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){
System.out.println("visitMethod: access="+access+" name="+name+" desc="+descriptor+" signature="+signature+" exceptions="+exceptions);
Method thisMethod = new Method(name, descriptor);
MethodVisitor mv = new MethodAnnotationScanner(Opcodes.ASM5, super.visitMethod(access, name, descriptor, signature, exceptions), thisMethod, className);
return mv;
}
};
reader.accept(visitor, Opcodes.ASM5);
for(String methodName : list) {
System.out.println(methodName);
}
byte[] finalClass = writer.toByteArray();
if(className.contains("Test")) {
try (OutputStream fos = new FileOutputStream("TestLogging.class")) {
fos.write(finalClass);
} catch (Exception e) {
e.printStackTrace();
}
}
return writer.toByteArray();
}
static class MethodAnnotationScanner extends MethodVisitor {
private Method thisMethod;
private boolean isChangeMethod = false;
private String className = null;
private StringBuilder descriptor = new StringBuilder("(");
public MethodAnnotationScanner(int api, MethodVisitor methodVisitor, Method thisMethod, String className) {
super(api, methodVisitor);
this.thisMethod = thisMethod;
this.className = className;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc="+desc+" visible="+visible);
if(desc.contains("ru/otus/annotations/Log")) {
this.isChangeMethod = true;
return super.visitAnnotation(desc, visible);
}
this.isChangeMethod = false;
return super.visitAnnotation(desc, visible);
}
@Override
public void visitCode() {
if(this.isChangeMethod) {
super.visitVarInsn(Opcodes.ALOAD, 0);
int i = 1;
for(Type arg : thisMethod.getArgumentTypes()) {
this.descriptor.append(arg.getDescriptor());
if (arg.getDescriptor().equals("J")) {
super.visitVarInsn(Opcodes.LLOAD, i);
++i;
} else if (arg.getDescriptor().equals("D")) {
super.visitVarInsn(Opcodes.DLOAD, i);
++i;
} else if (arg.getDescriptor().equals("F")) {
super.visitVarInsn(Opcodes.FLOAD, i);
} else if(arg.getDescriptor().equals("I")) {
super.visitVarInsn(Opcodes.ILOAD, i);
}
i++;
}
Handle handle = new Handle(
H_INVOKESTATIC,
Type.getInternalName(java.lang.invoke.StringConcatFactory.class),
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
false);
this.descriptor.append(")Ljava/lang/String;");
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
super.visitMaxs(0, 0);
}
if (mv != null) {
super.visitCode();
}
super.visitEnd();
}
}
}
我有两个 class 可以重现此功能。 首先 - 测试记录 第二个 - AutoLogger
在第一个class中,我有一个方法需要记录, 第二个开始 class 包含方法 main.
您在读取字段 System.out
之前推送字符串连接的参数,然后尝试执行字符串连接。因此,用于执行字符串连接的 invokedynamic
指令在操作数堆栈上发现了不匹配的 PrintStream
。
一个简单的解决方法是更改说明
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
到
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInsn(Opcodes.SWAP);
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
或者将 GETSTATIC
移动到将推送 concat 参数的代码之前,到执行过时的 super.visitVarInsn(Opcodes.ALOAD, 0);
的位置。那么,你不需要SWAP
.
但是代码的问题比较多。您在推送值时计算局部变量,正确考虑 long
和 double
取两个变量,但是,您在 ", param: \u0001".repeat(i)
表达式中使用相同的数字,这将告诉StringConcatFactory
在 long
和 double
的情况下有两个值。你需要分开柜台。此外,您没有推送引用类型参数,但由于您正在对它们进行计数并将它们包含在 concat 调用的签名中,因此您还必须将它们推送到操作数堆栈。
此外,虽然在这里没有效果,但 visitMaxs(0, 0)
调用和 visitEnd()
调用是不合适的。您在代码的开头注入,您不会拦截的其他访问调用将跟随,包括自动执行的 visitMaxs
和 visitEnd
.
所有修复后,代码看起来像
public void visitCode() {
if(this.isChangeMethod) {
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
int varIndex = 1, numArgs = 0;
for(Type arg : thisMethod.getArgumentTypes()) {
this.descriptor.append(arg.getDescriptor());
if (arg.getDescriptor().equals("J")) {
super.visitVarInsn(Opcodes.LLOAD, varIndex);
++varIndex;
} else if (arg.getDescriptor().equals("D")) {
super.visitVarInsn(Opcodes.DLOAD, varIndex);
++varIndex;
} else if (arg.getDescriptor().equals("F")) {
super.visitVarInsn(Opcodes.FLOAD, varIndex);
} else if(arg.getDescriptor().equals("I")) {
super.visitVarInsn(Opcodes.ILOAD, varIndex);
} else {
super.visitVarInsn(Opcodes.ALOAD, varIndex);
}
varIndex++;
numArgs++;
}
Handle handle = new Handle(
H_INVOKESTATIC,
Type.getInternalName(java.lang.invoke.StringConcatFactory.class),
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
false);
this.descriptor.append(")Ljava/lang/String;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(numArgs));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitCode();
}
附带说明一下,当你为连接推送所有参数时,你可以简化描述符的构造,因为连接描述符几乎与方法的描述符相同;您只需将 return 类型替换为 Ljava/lang/String;
.
您可以在不处理 Method
和 Type
对象的情况下完成整个操作,只需使用已经传递给 visitMethod
.
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
System.out.println("visitMethod: access="+access+" name="+name
+" desc="+descriptor+" signature="+signature+" exceptions="+exceptions);
MethodVisitor mv = new MethodAnnotationScanner(Opcodes.ASM5, name, descriptor,
super.visitMethod(access, name, descriptor, signature, exceptions));
return mv;
}
static class MethodAnnotationScanner extends MethodVisitor {
private boolean isChangeMethod;
private final String name, descriptor;
public MethodAnnotationScanner(int api, String name,
String methodDesciptor, MethodVisitor methodVisitor){
super(api, methodVisitor);
this.name = name;
this.descriptor = methodDesciptor;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc="+desc+" visible="+visible);
if(desc.contains("ru/otus/annotations/Log")) {
this.isChangeMethod = true;
return super.visitAnnotation(desc, visible);
}
this.isChangeMethod = false;
return super.visitAnnotation(desc, visible);
}
@Override
public void visitCode() {
if(this.isChangeMethod) {
super.visitFieldInsn(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
int varIndex = 1, numArgs = 0, p;
for(p = 1; descriptor.charAt(p) != ')'; p++) {
switch(descriptor.charAt(p)) {
case 'J':
super.visitVarInsn(Opcodes.LLOAD, varIndex); ++varIndex; break;
case 'D':
super.visitVarInsn(Opcodes.DLOAD, varIndex); ++varIndex; break;
case 'F': super.visitVarInsn(Opcodes.FLOAD, varIndex); break;
case 'I': super.visitVarInsn(Opcodes.ILOAD, varIndex); break;
case 'L': super.visitVarInsn(Opcodes.ALOAD, varIndex);
p = descriptor.indexOf(';', p);
break;
case '[': super.visitVarInsn(Opcodes.ALOAD, varIndex);
do {} while(descriptor.charAt(++p)=='[');
if(descriptor.charAt(p) == 'L') p = descriptor.indexOf(';', p);
break;
default: throw new IllegalStateException(descriptor);
}
varIndex++;
numArgs++;
}
String ret = "Ljava/lang/String;";
String concatSig = new StringBuilder(++p + ret.length())
.append(descriptor, 0, p).append(ret).toString();
Handle handle = new Handle(
H_INVOKESTATIC,
"java/lang/invoke/StringConcatFactory",
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class,
String.class, MethodType.class, String.class, Object[].class)
.toMethodDescriptorString(),
false);
super.visitInvokeDynamicInsn("makeConcatWithConstants", concatSig, handle,
"executed method: " + name + ", param: \u0001".repeat(numArgs));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitCode();
}
}