All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.d2j.tools.jar.InvocationWeaver Maven / Gradle / Ivy

There is a newer version: 1.0.38
Show newest version
/*
 * dex2jar - Tools to work with android .dex and java .class files
 * Copyright (c) 2009-2015 Panxiaobo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.d2j.tools.jar;

import com.googlecode.dex2jar.tools.BaseCmd;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.commons.RemappingClassAdapter;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.jar.Attributes.Name;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

/**
 * 1. Replace class A to another class B, include superclass, new for
 * 

*

 *     class Test1 extends A ...
 *     class Test2 implements A ...
 *     void amethod(A a) ...
 * 
*

* after *

*

 *     class Test1 extends B ...
 *     class Test2 extends B ...
 *     void amethod(B a) ...
 * 
*

* 2. Replace method A to another method B, method B must be public static, and in either 'public static RET b(ARGs)' or * 'public RET b(Invocation inv)' RET: same return type with method A or Object ARGs: if method A is static, ARGs is * same with method A, if method A is non-static the ARGs is 'thiz, arguments in methodA' *

*

 * public int a() {
 *     Test t = new Test();
 *     return t.test(1, 2);
 * }
 * 
*

* after *

*

 *     // direct replace
 *     public int a(){
 *         Test t=new Test();
 *         return B(t,1,2);
 *     }
 *     // or by MethodInvocation
 *     public int a(){
 *         Test t=new Test();
 *         return test_$$$_A_(t,1,2)
 *     }
 *     // the replaced invoke method
 *     public static int test$$$_A_(Test t, int a, int b){
 *         MethodInvocation i=new MethodInvocation(t, new Object[]{a.b})
 *         return B(i).intValue();
 *     }
 *     // the callback if MethodInvocation.proceed() is invoked
 *     public static Object test$$$$_callback(Test t, Object[]args) {
 *        return box(t.test(args[0].intValue(),args[1].intValue()));
 *     }
 * 
*

* 3. Replace Methods Implementations *

*

 * public int test() {
 *         ...
 * }
 * 
*

* after *

*

 *     public int test(){
 *          MethodInvocation i=new MethodInvocation(t, new Object[]{a.b})
 *          return B(i).intValue();
 *     }
 *     public int org_test(){
 *         ...
 *     }
 * 
*/ public class InvocationWeaver extends BaseWeaver implements Opcodes { private static final Type OBJECT_TYPE = Type.getType(Object.class); private Remapper remapper = new Remapper() { @Override public String mapDesc(String desc) { if (desc.length() == 1) { return desc; } String nDesc = clzDescMap.get(desc); return nDesc == null ? desc : nDesc; } }; static private void box(Type arg, MethodVisitor mv) { switch (arg.getSort()) { case Type.OBJECT: case Type.ARRAY: return; case Type.INT: mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"); break; case Type.LONG: mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"); break; case Type.FLOAT: mv.visitMethodInsn(INVOKESTATIC, "java/lang/Floag", "valueOf", "(F)Ljava/lang/Floag;"); break; case Type.DOUBLE: mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"); break; case Type.SHORT: mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"); break; case Type.CHAR: mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"); break; case Type.BOOLEAN: mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"); break; case Type.BYTE: mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"); break; case Type.VOID: mv.visitInsn(ACONST_NULL); break; } } static private void unBox(Type orgRet, Type nRet, MethodVisitor mv) { if (orgRet.equals(nRet)) { return; } if (orgRet.getSort() == Type.VOID) { mv.visitInsn(nRet.getSize() == 1 ? POP : POP2); } if (nRet.getSort() != Type.OBJECT) { throw new RuntimeException("invalid ret type:" + nRet); } switch (orgRet.getSort()) { case Type.OBJECT: case Type.ARRAY: mv.visitTypeInsn(CHECKCAST, orgRet.getInternalName()); break; case Type.INT: mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I"); break; case Type.FLOAT: mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "floatValue", "()F"); break; case Type.LONG: mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "longValue", "()J"); break; case Type.DOUBLE: mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "doubleValue", "()D"); break; case Type.BYTE: mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "byteValue", "()B"); break; case Type.SHORT: mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "shortValue", "()S"); break; case Type.CHAR: mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"); break; case Type.BOOLEAN: mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"); break; } } public byte[] wave0(byte[] data) throws IOException { final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); wave0(data, cw); return cw.toByteArray(); } public void wave0(byte[] data, final ClassVisitor cv) throws IOException { new ClassReader(data).accept(wrapper(cv), ClassReader.EXPAND_FRAMES); } public ClassVisitor wrapper(final ClassVisitor cv) { return new RemappingClassAdapter(cv, remapper) { Map toCreate = new HashMap(); String clzName; private MtdInfo newMethodA(int opcode, MtdInfo t, MtdInfo mapTo) { MtdInfo n = toCreate.get(t); if (n != null) { return n; } n = new MtdInfo(); n.owner = t.owner; n.name = buildMethodAName(t.name); boolean hasThis = opcode != INVOKESTATIC; if (hasThis) { Type[] args = Type.getArgumentTypes(t.desc); Type ret = Type.getReturnType(t.desc); List ts = new ArrayList<>(args.length + 1); ts.add(Type.getType(t.owner)); ts.addAll(Arrays.asList(args)); n.desc = Type.getMethodDescriptor(ret, ts.toArray(new Type[ts.size()])); } else { n.desc = t.desc; } toCreate.put(t, n); MethodVisitor mv = cv.visitMethod(ACC_SYNTHETIC | ACC_PRIVATE | ACC_STATIC, n.name, n.desc, null, null); mv.visitCode(); genMethodACode(opcode, t, mapTo, mv, t); return n; } private void genMethodACode(int opcode, MtdInfo t, MtdInfo mapTo, MethodVisitor mv, MtdInfo src) { boolean hasThis = opcode != INVOKESTATIC; Type[] args = Type.getArgumentTypes(t.desc); Type ret = Type.getReturnType(t.desc); final int start; mv.visitTypeInsn(NEW, getCurrentInvocationName()); mv.visitInsn(DUP); if (hasThis) { mv.visitVarInsn(ALOAD, 0); start = 1; } else { mv.visitInsn(ACONST_NULL); start = 0; } if (args.length == 0) { mv.visitInsn(ACONST_NULL); } else { mv.visitLdcInsn(args.length); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); for (int i = 0; i < args.length; i++) { mv.visitInsn(DUP); mv.visitLdcInsn(i); mv.visitVarInsn(args[i].getOpcode(ILOAD), i + start); box(args[i], mv); mv.visitInsn(AASTORE); } } int nextIdx = callbacks.size(); mv.visitLdcInsn(nextIdx); mv.visitMethodInsn(INVOKESPECIAL, getCurrentInvocationName(), "", "(Ljava/lang/Object;[Ljava/lang/Object;I)V"); mv.visitMethodInsn(INVOKESTATIC, toInternal(mapTo.owner), mapTo.name, mapTo.desc); unBox(ret, Type.getReturnType(mapTo.desc), mv); mv.visitInsn(ret.getOpcode(IRETURN)); mv.visitMaxs(-1, -1); mv.visitEnd(); Callback cb = new Callback(); cb.idx = nextIdx; cb.callback = newMethodCallback(opcode, t); cb.target = src; cb.isSpecial = opcode == INVOKESPECIAL; cb.isStatic = opcode == INVOKESTATIC; callbacks.add(cb); } private MtdInfo newMethodCallback(int opcode, MtdInfo t) { MtdInfo n = new MtdInfo(); n.owner = "L" + className + ";"; n.name = buildCallbackMethodName(t.name) ; if (opcode == INVOKESPECIAL || opcode == INVOKESTATIC) { n.desc = "([Ljava/lang/Object;)Ljava/lang/Object;"; } else { n.desc = "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"; } MethodVisitor mv = cv.visitMethod(opcode == INVOKESPECIAL ? ACC_PUBLIC : ACC_PUBLIC | ACC_STATIC, n.name, n.desc, null, null); mv.visitCode(); int start; if (opcode != INVOKESTATIC) { mv.visitVarInsn(ALOAD, 0); if (opcode != INVOKESPECIAL) { mv.visitTypeInsn(CHECKCAST, toInternal(t.owner)); } start = 1; } else { start = 0; } Type[] args = Type.getArgumentTypes(t.desc); for (int i = 0; i < args.length; i++) { mv.visitVarInsn(ALOAD, start); mv.visitLdcInsn(i); mv.visitInsn(AALOAD); unBox(args[i], OBJECT_TYPE, mv); } mv.visitMethodInsn(opcode, toInternal(t.owner), t.name, t.desc); Type ret = Type.getReturnType(t.desc); box(ret, mv); mv.visitInsn(ARETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); return n; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); clzName = name; } public MethodVisitor visitMethod(int access, final String name, String desc, String signature, String[] exceptions) { final MethodVisitor superMv = superMethodVisitor(access, name, desc, signature, exceptions); final MtdInfo mapTo = findDefinedTargetMethod("L" + clzName + ";", name, desc); if (mapTo != null) { final MtdInfo t1 = new MtdInfo(); t1.owner = "L" + clzName + ";"; t1.name = buildMethodAName(name) ; t1.desc = desc; final MtdInfo t = t1; final MtdInfo src = new MtdInfo(); src.owner = t.owner; src.name = name; src.desc = desc; return new MethodNode(Opcodes.ASM4, access, name, desc, signature, exceptions) { @Override public void visitEnd() { InsnList instructions = this.instructions; List tryCatchBlocks = this.tryCatchBlocks; List localVariables = this.localVariables; this.instructions = new InsnList(); this.tryCatchBlocks = new ArrayList<>(); this.localVariables = new ArrayList<>(); this.maxLocals = -1; this.maxStack = -1; accept(superMv); int opcode; if (Modifier.isStatic(access)) { opcode = Opcodes.INVOKESTATIC; } else { opcode = Opcodes.INVOKEVIRTUAL; } genMethodACode(opcode, t, mapTo, superMv, src); int newAccess = (access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC; // make sure public MethodVisitor rmv = wrap(superMethodVisitor(newAccess, t.name, desc, null, null)); if(rmv!=null) { rmv.visitCode(); int n, i; n = tryCatchBlocks == null ? 0 : tryCatchBlocks.size(); for (i = 0; i < n; ++i) { tryCatchBlocks.get(i).accept(rmv); } instructions.accept(rmv); n = localVariables == null ? 0 : localVariables.size(); for (i = 0; i < n; ++i) { localVariables.get(i).accept(rmv); } rmv.visitMaxs(-1, -1); rmv.visitEnd(); } } }; } else { return wrap(superMv); } } private MethodVisitor superMethodVisitor(int access, String name, String desc, String signature, String[] exceptions) { return super.visitMethod(access, name, desc, signature, exceptions); } MethodVisitor wrap(MethodVisitor mv){ return mv==null?null: new ReplaceMethodVisitor(mv); } class ReplaceMethodVisitor extends MethodVisitor { public ReplaceMethodVisitor(MethodVisitor mv) { super(Opcodes.ASM4, mv); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { MtdInfo mapTo = findTargetMethod("L" + owner + ";", name, desc); if (mapTo != null) { boolean isStatic = opcode == INVOKESTATIC; Type orgRet = Type.getReturnType(desc); Type orgArgs[] = Type.getArgumentTypes(desc); Type nRet = Type.getReturnType(mapTo.desc); Type nArgs[] = Type.getArgumentTypes(mapTo.desc); if (orgRet.getSort() != Type.VOID && nRet.getSort() == Type.VOID) { throw new RuntimeException("can't cast " + nRet + " to " + orgRet); } if (nArgs.length == 1 && nArgs[0].getDescriptor().equals(invocationInterfaceDesc)) { MtdInfo t = new MtdInfo(); t.owner = "L" + owner + ";"; t.name = name; t.desc = desc; MtdInfo n = newMethodA(opcode, t, mapTo); super.visitMethodInsn(INVOKESTATIC, clzName, n.name, n.desc); } else { // simple replace // checking for invalid replace if (isStatic) { if (!Arrays.deepEquals(orgArgs, nArgs)) { throw new RuntimeException("arguments not equal: " + owner + "." + name + desc + " <> " + mapTo.owner + "." + mapTo.name + mapTo.desc); } } else { if (nArgs.length != orgArgs.length + 1) { throw new RuntimeException("arguments not equal: " + owner + "." + name + desc + " <> " + mapTo.owner + "." + mapTo.name + mapTo.desc); } if (orgArgs.length > 0) { for (int i = 0; i < orgArgs.length; i++) { if (!orgArgs[i].equals(nArgs[i + 1])) { throw new RuntimeException("arguments not equal: " + owner + "." + name + desc + " <> " + mapTo.owner + "." + mapTo.name + mapTo.desc); } } } } // replace it! super.visitMethodInsn(INVOKESTATIC, toInternal(mapTo.owner), mapTo.name, mapTo.desc); unBox(orgRet, nRet, this.mv); } } else { super.visitMethodInsn(opcode, owner, name, desc); } } } }; } public void wave(Path from, final Path to) throws IOException { BaseCmd.walkJarOrDir(from, new BaseCmd.FileVisitorX() { @Override public void visitFile(Path file, String relative) throws IOException { String name = relative; Path targetPath = to.resolve(relative); BaseCmd.createParentDirectories(targetPath); if (name.endsWith(".class")) { String clzName = name.substring(0, name.length() - ".class".length()); if (ignores.contains(clzName)) { Files.copy(file, targetPath); } else { byte[] out = wave0(Files.readAllBytes(file)); Files.write(targetPath, out); } } else { if (name.startsWith("META-INF/")) { if (name.equals(JarFile.MANIFEST_NAME)) { try (InputStream in = Files.newInputStream(file)) { Manifest mf = new Manifest(in); mf.getMainAttributes().put(new Name("X-NOTICE"), "Modified"); mf.getEntries().clear(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); mf.write(baos); baos.flush(); Files.write(targetPath, baos.toByteArray()); } } else if (name.endsWith(".DSA") || name.endsWith(".RSA") || name.endsWith(".SF") || name.endsWith(".ECDSA")) { // ignored } else { Files.copy(file, targetPath); } } else { Files.copy(file, targetPath); } } } }); if (callbacks.size() > 0) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); String type = buildInvocationClz(cw); byte[] data = cw.toByteArray(); Path target = to.resolve(type + ".class"); BaseCmd.createParentDirectories(target); Files.write(target, data); nextInvocationName(); } } public String buildInvocationClz(ClassVisitor cw) { String typeName = getCurrentInvocationName(); cw.visit(V1_6, ACC_PUBLIC, typeName, null, "java/lang/Object", new String[]{ toInternal(invocationInterfaceDesc)}); cw.visitField(ACC_PRIVATE | ACC_FINAL, "thiz", "Ljava/lang/Object;", null, null).visitEnd(); cw.visitField(ACC_PRIVATE | ACC_FINAL, "args", "[Ljava/lang/Object;", null, null).visitEnd(); cw.visitField(ACC_PRIVATE | ACC_FINAL, "idx", "I", null, null).visitEnd(); { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "(Ljava/lang/Object;[Ljava/lang/Object;I)V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, typeName, "thiz", "Ljava/lang/Object;"); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 2); mv.visitFieldInsn(PUTFIELD, typeName, "args", "[Ljava/lang/Object;"); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ILOAD, 3); mv.visitFieldInsn(PUTFIELD, typeName, "idx", "I"); mv.visitInsn(RETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); } { genSwitchMethod(cw, typeName, "getMethodOwner", new CB() { @Override public String getKey(MtdInfo mtd) { return toInternal(mtd.owner); } }); genSwitchMethod(cw, typeName, "getMethodName", new CB() { @Override public String getKey(MtdInfo mtd) { return mtd.name; } }); genSwitchMethod(cw, typeName, "getMethodDesc", new CB() { @Override public String getKey(MtdInfo mtd) { return mtd.desc; } }); } { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getArguments", "()[Ljava/lang/Object;", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "args", "[Ljava/lang/Object;"); mv.visitInsn(ARETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); } { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getThis", "()Ljava/lang/Object;", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "thiz", "Ljava/lang/Object;"); mv.visitInsn(ARETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); } { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "proceed", "()Ljava/lang/Object;", null, new String[]{"java/lang/Throwable"}); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "idx", "I"); Label def = new Label(); Label[] labels = new Label[callbacks.size()]; for (int i = 0; i < labels.length; i++) { labels[i] = new Label(); } mv.visitTableSwitchInsn(0, callbacks.size() - 1, def, labels); for (int i = 0; i < labels.length; i++) { mv.visitLabel(labels[i]); Callback cb = callbacks.get(i); MtdInfo m = (MtdInfo) cb.callback; if (cb.isStatic) { mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "args", "[Ljava/lang/Object;"); mv.visitMethodInsn(INVOKESTATIC, toInternal(m.owner), m.name, m.desc); } else if (cb.isSpecial) { mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "thiz", "Ljava/lang/Object;"); mv.visitTypeInsn(CHECKCAST, toInternal(m.owner)); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "args", "[Ljava/lang/Object;"); mv.visitMethodInsn(INVOKEVIRTUAL, toInternal(m.owner), m.name, m.desc); } else { mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "thiz", "Ljava/lang/Object;"); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "args", "[Ljava/lang/Object;"); mv.visitMethodInsn(INVOKESTATIC, toInternal(m.owner), m.name, m.desc); } Type ret = Type.getReturnType(m.desc); box(ret, mv); mv.visitInsn(ret.getOpcode(IRETURN)); } mv.visitLabel(def); mv.visitTypeInsn(NEW, "java/lang/RuntimeException"); mv.visitInsn(DUP); mv.visitLdcInsn("invalid idx"); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "", "(Ljava/lang/String;)V"); mv.visitInsn(ATHROW); mv.visitMaxs(-1, -1); mv.visitEnd(); } return typeName; } interface CB { String getKey(MtdInfo mtd); } private void genSwitchMethod(ClassVisitor cw, String typeName, String methodName, CB callback) { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "()Ljava/lang/String;", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, typeName, "idx", "I"); Label def = new Label(); Label[] labels = new Label[callbacks.size()]; Map strMap = new TreeMap<>(); for (int i = 0; i < labels.length; i++) { Callback cb = callbacks.get(i); String key = callback.getKey((MtdInfo) cb.target); Label label = strMap.get(key); if (label == null) { label = new Label(); strMap.put(key, label); } labels[i] = label; } mv.visitTableSwitchInsn(0, callbacks.size() - 1, def, labels); for (Map.Entry e : strMap.entrySet()) { mv.visitLabel(e.getValue()); mv.visitLdcInsn(e.getKey()); mv.visitInsn(ARETURN); } mv.visitLabel(def); mv.visitTypeInsn(NEW, "java/lang/RuntimeException"); mv.visitInsn(DUP); mv.visitLdcInsn("invalid idx"); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "", "(Ljava/lang/String;)V"); mv.visitInsn(ATHROW); mv.visitMaxs(-1, -1); mv.visitEnd(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy