forked from openrs2/openrs2
parent
fdcc5a217f
commit
8020ac98ab
@ -1,335 +0,0 @@ |
||||
package dev.openrs2.asm; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.PrintWriter; |
||||
import java.io.StringWriter; |
||||
import java.io.UncheckedIOException; |
||||
|
||||
import org.objectweb.asm.Opcodes; |
||||
import org.objectweb.asm.tree.AbstractInsnNode; |
||||
import org.objectweb.asm.tree.InsnNode; |
||||
import org.objectweb.asm.tree.IntInsnNode; |
||||
import org.objectweb.asm.tree.LdcInsnNode; |
||||
import org.objectweb.asm.util.Textifier; |
||||
import org.objectweb.asm.util.TraceMethodVisitor; |
||||
|
||||
public final class InsnNodeUtils { |
||||
public static AbstractInsnNode nextReal(AbstractInsnNode insn) { |
||||
while ((insn = insn.getNext()) != null && insn.getOpcode() == -1) { |
||||
/* empty */ |
||||
} |
||||
return insn; |
||||
} |
||||
|
||||
public static AbstractInsnNode previousReal(AbstractInsnNode insn) { |
||||
while ((insn = insn.getPrevious()) != null && insn.getOpcode() == -1) { |
||||
/* empty */ |
||||
} |
||||
return insn; |
||||
} |
||||
|
||||
public static AbstractInsnNode nextVirtual(AbstractInsnNode insn) { |
||||
while ((insn = insn.getNext()) != null && insn.getOpcode() != -1) { |
||||
/* empty */ |
||||
} |
||||
return insn; |
||||
} |
||||
|
||||
public static AbstractInsnNode previousVirtual(AbstractInsnNode insn) { |
||||
while ((insn = insn.getPrevious()) != null && insn.getOpcode() != -1) { |
||||
/* empty */ |
||||
} |
||||
return insn; |
||||
} |
||||
|
||||
public static boolean isIntConstant(AbstractInsnNode insn) { |
||||
switch (insn.getOpcode()) { |
||||
case Opcodes.ICONST_M1: |
||||
case Opcodes.ICONST_0: |
||||
case Opcodes.ICONST_1: |
||||
case Opcodes.ICONST_2: |
||||
case Opcodes.ICONST_3: |
||||
case Opcodes.ICONST_4: |
||||
case Opcodes.ICONST_5: |
||||
case Opcodes.BIPUSH: |
||||
case Opcodes.SIPUSH: |
||||
return true; |
||||
case Opcodes.LDC: |
||||
var ldc = (LdcInsnNode) insn; |
||||
return ldc.cst instanceof Integer; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public static int getIntConstant(AbstractInsnNode insn) { |
||||
switch (insn.getOpcode()) { |
||||
case Opcodes.ICONST_M1: |
||||
return -1; |
||||
case Opcodes.ICONST_0: |
||||
return 0; |
||||
case Opcodes.ICONST_1: |
||||
return 1; |
||||
case Opcodes.ICONST_2: |
||||
return 2; |
||||
case Opcodes.ICONST_3: |
||||
return 3; |
||||
case Opcodes.ICONST_4: |
||||
return 4; |
||||
case Opcodes.ICONST_5: |
||||
return 5; |
||||
case Opcodes.BIPUSH: |
||||
case Opcodes.SIPUSH: |
||||
var intInsn = (IntInsnNode) insn; |
||||
return intInsn.operand; |
||||
case Opcodes.LDC: |
||||
var ldc = (LdcInsnNode) insn; |
||||
if (ldc.cst instanceof Integer) { |
||||
return (Integer) ldc.cst; |
||||
} |
||||
} |
||||
|
||||
throw new IllegalArgumentException(); |
||||
} |
||||
|
||||
public static AbstractInsnNode createIntConstant(int value) { |
||||
switch (value) { |
||||
case -1: |
||||
return new InsnNode(Opcodes.ICONST_M1); |
||||
case 0: |
||||
return new InsnNode(Opcodes.ICONST_0); |
||||
case 1: |
||||
return new InsnNode(Opcodes.ICONST_1); |
||||
case 2: |
||||
return new InsnNode(Opcodes.ICONST_2); |
||||
case 3: |
||||
return new InsnNode(Opcodes.ICONST_3); |
||||
case 4: |
||||
return new InsnNode(Opcodes.ICONST_4); |
||||
case 5: |
||||
return new InsnNode(Opcodes.ICONST_5); |
||||
} |
||||
|
||||
if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { |
||||
return new IntInsnNode(Opcodes.BIPUSH, value); |
||||
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { |
||||
return new IntInsnNode(Opcodes.SIPUSH, value); |
||||
} else { |
||||
return new LdcInsnNode(value); |
||||
} |
||||
} |
||||
|
||||
public static boolean isPure(AbstractInsnNode insn) { |
||||
var opcode = insn.getOpcode(); |
||||
switch (opcode) { |
||||
case -1: |
||||
case Opcodes.NOP: |
||||
case Opcodes.ACONST_NULL: |
||||
case Opcodes.ICONST_M1: |
||||
case Opcodes.ICONST_0: |
||||
case Opcodes.ICONST_1: |
||||
case Opcodes.ICONST_2: |
||||
case Opcodes.ICONST_3: |
||||
case Opcodes.ICONST_4: |
||||
case Opcodes.ICONST_5: |
||||
case Opcodes.LCONST_0: |
||||
case Opcodes.LCONST_1: |
||||
case Opcodes.FCONST_0: |
||||
case Opcodes.FCONST_1: |
||||
case Opcodes.FCONST_2: |
||||
case Opcodes.DCONST_0: |
||||
case Opcodes.DCONST_1: |
||||
case Opcodes.BIPUSH: |
||||
case Opcodes.SIPUSH: |
||||
case Opcodes.LDC: |
||||
case Opcodes.ILOAD: |
||||
case Opcodes.LLOAD: |
||||
case Opcodes.FLOAD: |
||||
case Opcodes.DLOAD: |
||||
case Opcodes.ALOAD: |
||||
return true; |
||||
case Opcodes.IALOAD: |
||||
case Opcodes.LALOAD: |
||||
case Opcodes.FALOAD: |
||||
case Opcodes.DALOAD: |
||||
case Opcodes.AALOAD: |
||||
case Opcodes.BALOAD: |
||||
case Opcodes.CALOAD: |
||||
case Opcodes.SALOAD: |
||||
/* might throw NPE or index out of bounds exception */ |
||||
case Opcodes.ISTORE: |
||||
case Opcodes.LSTORE: |
||||
case Opcodes.FSTORE: |
||||
case Opcodes.DSTORE: |
||||
case Opcodes.ASTORE: |
||||
case Opcodes.IASTORE: |
||||
case Opcodes.LASTORE: |
||||
case Opcodes.FASTORE: |
||||
case Opcodes.DASTORE: |
||||
case Opcodes.AASTORE: |
||||
case Opcodes.BASTORE: |
||||
case Opcodes.CASTORE: |
||||
case Opcodes.SASTORE: |
||||
return false; |
||||
case Opcodes.POP: |
||||
case Opcodes.POP2: |
||||
case Opcodes.DUP: |
||||
case Opcodes.DUP_X1: |
||||
case Opcodes.DUP_X2: |
||||
case Opcodes.DUP2: |
||||
case Opcodes.DUP2_X1: |
||||
case Opcodes.DUP2_X2: |
||||
case Opcodes.SWAP: |
||||
case Opcodes.IADD: |
||||
case Opcodes.LADD: |
||||
case Opcodes.FADD: |
||||
case Opcodes.DADD: |
||||
case Opcodes.ISUB: |
||||
case Opcodes.LSUB: |
||||
case Opcodes.FSUB: |
||||
case Opcodes.DSUB: |
||||
case Opcodes.IMUL: |
||||
case Opcodes.LMUL: |
||||
case Opcodes.FMUL: |
||||
case Opcodes.DMUL: |
||||
case Opcodes.IDIV: |
||||
case Opcodes.LDIV: |
||||
case Opcodes.FDIV: |
||||
case Opcodes.DDIV: |
||||
case Opcodes.IREM: |
||||
case Opcodes.LREM: |
||||
case Opcodes.FREM: |
||||
case Opcodes.DREM: |
||||
/* |
||||
* XXX(gpe): strictly speaking the *DIV and *REM instructions have |
||||
* side effects (unless we can prove that the second argument is |
||||
* non-zero). However, treating them as having side effects reduces |
||||
* the number of dummy variables we can remove, so we pretend they |
||||
* don't have any side effects. |
||||
* |
||||
* This doesn't seem to cause any problems with the client, as it |
||||
* doesn't deliberately try to trigger divide-by-zero exceptions. |
||||
*/ |
||||
case Opcodes.INEG: |
||||
case Opcodes.LNEG: |
||||
case Opcodes.FNEG: |
||||
case Opcodes.DNEG: |
||||
case Opcodes.ISHL: |
||||
case Opcodes.LSHL: |
||||
case Opcodes.ISHR: |
||||
case Opcodes.LSHR: |
||||
case Opcodes.IUSHR: |
||||
case Opcodes.LUSHR: |
||||
case Opcodes.IAND: |
||||
case Opcodes.LAND: |
||||
case Opcodes.IOR: |
||||
case Opcodes.LOR: |
||||
case Opcodes.IXOR: |
||||
case Opcodes.LXOR: |
||||
return true; |
||||
case Opcodes.IINC: |
||||
return false; |
||||
case Opcodes.I2L: |
||||
case Opcodes.I2F: |
||||
case Opcodes.I2D: |
||||
case Opcodes.L2I: |
||||
case Opcodes.L2F: |
||||
case Opcodes.L2D: |
||||
case Opcodes.F2I: |
||||
case Opcodes.F2L: |
||||
case Opcodes.F2D: |
||||
case Opcodes.D2I: |
||||
case Opcodes.D2L: |
||||
case Opcodes.D2F: |
||||
case Opcodes.I2B: |
||||
case Opcodes.I2C: |
||||
case Opcodes.I2S: |
||||
case Opcodes.LCMP: |
||||
case Opcodes.FCMPL: |
||||
case Opcodes.FCMPG: |
||||
case Opcodes.DCMPL: |
||||
case Opcodes.DCMPG: |
||||
return true; |
||||
case Opcodes.IFEQ: |
||||
case Opcodes.IFNE: |
||||
case Opcodes.IFLT: |
||||
case Opcodes.IFGE: |
||||
case Opcodes.IFGT: |
||||
case Opcodes.IFLE: |
||||
case Opcodes.IF_ICMPEQ: |
||||
case Opcodes.IF_ICMPNE: |
||||
case Opcodes.IF_ICMPLT: |
||||
case Opcodes.IF_ICMPGE: |
||||
case Opcodes.IF_ICMPGT: |
||||
case Opcodes.IF_ICMPLE: |
||||
case Opcodes.IF_ACMPEQ: |
||||
case Opcodes.IF_ACMPNE: |
||||
case Opcodes.GOTO: |
||||
case Opcodes.JSR: |
||||
case Opcodes.RET: |
||||
case Opcodes.TABLESWITCH: |
||||
case Opcodes.LOOKUPSWITCH: |
||||
case Opcodes.IRETURN: |
||||
case Opcodes.LRETURN: |
||||
case Opcodes.FRETURN: |
||||
case Opcodes.DRETURN: |
||||
case Opcodes.ARETURN: |
||||
case Opcodes.RETURN: |
||||
return false; |
||||
case Opcodes.GETSTATIC: |
||||
return true; |
||||
case Opcodes.PUTSTATIC: |
||||
case Opcodes.GETFIELD: |
||||
/* might throw NPE */ |
||||
case Opcodes.PUTFIELD: |
||||
return false; |
||||
case Opcodes.INVOKEVIRTUAL: |
||||
case Opcodes.INVOKESPECIAL: |
||||
case Opcodes.INVOKESTATIC: |
||||
case Opcodes.INVOKEINTERFACE: |
||||
case Opcodes.INVOKEDYNAMIC: |
||||
return false; |
||||
case Opcodes.NEW: |
||||
return true; |
||||
case Opcodes.NEWARRAY: |
||||
case Opcodes.ANEWARRAY: |
||||
/* arrays might be created with negative size */ |
||||
case Opcodes.ARRAYLENGTH: |
||||
/* might throw NPE */ |
||||
case Opcodes.ATHROW: |
||||
case Opcodes.CHECKCAST: |
||||
return false; |
||||
case Opcodes.INSTANCEOF: |
||||
return true; |
||||
case Opcodes.MONITORENTER: |
||||
case Opcodes.MONITOREXIT: |
||||
case Opcodes.MULTIANEWARRAY: |
||||
case Opcodes.IFNULL: |
||||
case Opcodes.IFNONNULL: |
||||
return false; |
||||
} |
||||
|
||||
throw new IllegalArgumentException(); |
||||
} |
||||
|
||||
public static String toString(AbstractInsnNode insn) { |
||||
var printer = new Textifier(); |
||||
|
||||
var visitor = new TraceMethodVisitor(printer); |
||||
insn.accept(visitor); |
||||
|
||||
try ( |
||||
var stringWriter = new StringWriter(); |
||||
var printWriter = new PrintWriter(stringWriter) |
||||
) { |
||||
printer.print(printWriter); |
||||
return stringWriter.toString().trim(); |
||||
} catch (IOException ex) { |
||||
throw new UncheckedIOException(ex); |
||||
} |
||||
} |
||||
|
||||
private InsnNodeUtils() { |
||||
/* empty */ |
||||
} |
||||
} |
@ -0,0 +1,288 @@ |
||||
package dev.openrs2.asm |
||||
|
||||
import org.objectweb.asm.Opcodes |
||||
import org.objectweb.asm.tree.AbstractInsnNode |
||||
import org.objectweb.asm.tree.InsnNode |
||||
import org.objectweb.asm.tree.IntInsnNode |
||||
import org.objectweb.asm.tree.LdcInsnNode |
||||
import org.objectweb.asm.util.Textifier |
||||
import org.objectweb.asm.util.TraceMethodVisitor |
||||
import java.io.IOException |
||||
import java.io.PrintWriter |
||||
import java.io.StringWriter |
||||
import java.io.UncheckedIOException |
||||
|
||||
private val PURE_OPCODES = setOf( |
||||
-1, |
||||
Opcodes.NOP, |
||||
Opcodes.ACONST_NULL, |
||||
Opcodes.ICONST_M1, |
||||
Opcodes.ICONST_0, |
||||
Opcodes.ICONST_1, |
||||
Opcodes.ICONST_2, |
||||
Opcodes.ICONST_3, |
||||
Opcodes.ICONST_4, |
||||
Opcodes.ICONST_5, |
||||
Opcodes.LCONST_0, |
||||
Opcodes.LCONST_1, |
||||
Opcodes.FCONST_0, |
||||
Opcodes.FCONST_1, |
||||
Opcodes.FCONST_2, |
||||
Opcodes.DCONST_0, |
||||
Opcodes.DCONST_1, |
||||
Opcodes.BIPUSH, |
||||
Opcodes.SIPUSH, |
||||
Opcodes.LDC, |
||||
Opcodes.ILOAD, |
||||
Opcodes.LLOAD, |
||||
Opcodes.FLOAD, |
||||
Opcodes.DLOAD, |
||||
Opcodes.ALOAD, |
||||
Opcodes.POP, |
||||
Opcodes.POP2, |
||||
Opcodes.DUP, |
||||
Opcodes.DUP_X1, |
||||
Opcodes.DUP_X2, |
||||
Opcodes.DUP2, |
||||
Opcodes.DUP2_X1, |
||||
Opcodes.DUP2_X2, |
||||
Opcodes.SWAP, |
||||
Opcodes.IADD, |
||||
Opcodes.LADD, |
||||
Opcodes.FADD, |
||||
Opcodes.DADD, |
||||
Opcodes.ISUB, |
||||
Opcodes.LSUB, |
||||
Opcodes.FSUB, |
||||
Opcodes.DSUB, |
||||
Opcodes.IMUL, |
||||
Opcodes.LMUL, |
||||
Opcodes.FMUL, |
||||
Opcodes.DMUL, |
||||
/* |
||||
* XXX(gpe): strictly speaking the *DEV and *REM instructions have side |
||||
* effects (unless we can prove that the second argument is non-zero). |
||||
* However, treating them as having side effects reduces the number of |
||||
* dummy variables we can remove, so we pretend they don't have any side |
||||
* effects. |
||||
* |
||||
* This doesn't seem to cause any problems with the client, as it doesn't |
||||
* deliberately try to trigger divide-by-zero exceptions. |
||||
*/ |
||||
Opcodes.IDIV, |
||||
Opcodes.LDIV, |
||||
Opcodes.FDIV, |
||||
Opcodes.DDIV, |
||||
Opcodes.IREM, |
||||
Opcodes.LREM, |
||||
Opcodes.FREM, |
||||
Opcodes.DREM, |
||||
Opcodes.INEG, |
||||
Opcodes.LNEG, |
||||
Opcodes.FNEG, |
||||
Opcodes.DNEG, |
||||
Opcodes.ISHL, |
||||
Opcodes.LSHL, |
||||
Opcodes.ISHR, |
||||
Opcodes.LSHR, |
||||
Opcodes.IUSHR, |
||||
Opcodes.LUSHR, |
||||
Opcodes.IAND, |
||||
Opcodes.LAND, |
||||
Opcodes.IOR, |
||||
Opcodes.LOR, |
||||
Opcodes.IXOR, |
||||
Opcodes.LXOR, |
||||
Opcodes.I2L, |
||||
Opcodes.I2F, |
||||
Opcodes.I2D, |
||||
Opcodes.L2I, |
||||
Opcodes.L2F, |
||||
Opcodes.L2D, |
||||
Opcodes.F2I, |
||||
Opcodes.F2L, |
||||
Opcodes.F2D, |
||||
Opcodes.D2I, |
||||
Opcodes.D2L, |
||||
Opcodes.D2F, |
||||
Opcodes.I2B, |
||||
Opcodes.I2C, |
||||
Opcodes.I2S, |
||||
Opcodes.LCMP, |
||||
Opcodes.FCMPL, |
||||
Opcodes.FCMPG, |
||||
Opcodes.DCMPL, |
||||
Opcodes.DCMPG, |
||||
Opcodes.GETSTATIC, |
||||
Opcodes.NEW, |
||||
Opcodes.INSTANCEOF |
||||
) |
||||
|
||||
private val IMPURE_OPCODES = setOf( |
||||
Opcodes.IALOAD, |
||||
Opcodes.LALOAD, |
||||
Opcodes.FALOAD, |
||||
Opcodes.DALOAD, |
||||
Opcodes.AALOAD, |
||||
Opcodes.BALOAD, |
||||
Opcodes.CALOAD, |
||||
Opcodes.SALOAD, |
||||
Opcodes.ISTORE, |
||||
Opcodes.LSTORE, |
||||
Opcodes.FSTORE, |
||||
Opcodes.DSTORE, |
||||
Opcodes.ASTORE, |
||||
Opcodes.IASTORE, |
||||
Opcodes.LASTORE, |
||||
Opcodes.FASTORE, |
||||
Opcodes.DASTORE, |
||||
Opcodes.AASTORE, |
||||
Opcodes.BASTORE, |
||||
Opcodes.CASTORE, |
||||
Opcodes.SASTORE, |
||||
Opcodes.IINC, |
||||
Opcodes.IFEQ, |
||||
Opcodes.IFNE, |
||||
Opcodes.IFLT, |
||||
Opcodes.IFGE, |
||||
Opcodes.IFGT, |
||||
Opcodes.IFLE, |
||||
Opcodes.IF_ICMPEQ, |
||||
Opcodes.IF_ICMPNE, |
||||
Opcodes.IF_ICMPLT, |
||||
Opcodes.IF_ICMPGE, |
||||
Opcodes.IF_ICMPGT, |
||||
Opcodes.IF_ICMPLE, |
||||
Opcodes.IF_ACMPEQ, |
||||
Opcodes.IF_ACMPNE, |
||||
Opcodes.GOTO, |
||||
Opcodes.JSR, |
||||
Opcodes.RET, |
||||
Opcodes.TABLESWITCH, |
||||
Opcodes.LOOKUPSWITCH, |
||||
Opcodes.IRETURN, |
||||
Opcodes.LRETURN, |
||||
Opcodes.FRETURN, |
||||
Opcodes.DRETURN, |
||||
Opcodes.ARETURN, |
||||
Opcodes.RETURN, |
||||
Opcodes.PUTSTATIC, |
||||
Opcodes.GETFIELD, |
||||
Opcodes.PUTFIELD, |
||||
Opcodes.INVOKEVIRTUAL, |
||||
Opcodes.INVOKESPECIAL, |
||||
Opcodes.INVOKESTATIC, |
||||
Opcodes.INVOKEINTERFACE, |
||||
Opcodes.INVOKEDYNAMIC, |
||||
Opcodes.NEWARRAY, |
||||
Opcodes.ANEWARRAY, |
||||
Opcodes.ARRAYLENGTH, |
||||
Opcodes.ATHROW, |
||||
Opcodes.CHECKCAST, |
||||
Opcodes.MONITORENTER, |
||||
Opcodes.MONITOREXIT, |
||||
Opcodes.MULTIANEWARRAY, |
||||
Opcodes.IFNULL, |
||||
Opcodes.IFNONNULL |
||||
) |
||||
|
||||
val AbstractInsnNode.nextReal: AbstractInsnNode? |
||||
get() { |
||||
var insn = next |
||||
while (insn != null && insn.opcode == -1) { |
||||
insn = insn.next |
||||
} |
||||
return insn |
||||
} |
||||
|
||||
val AbstractInsnNode.previousReal: AbstractInsnNode? |
||||
get() { |
||||
var insn = previous |
||||
while (insn != null && insn.opcode == -1) { |
||||
insn = insn.previous |
||||
} |
||||
return insn |
||||
} |
||||
|
||||
val AbstractInsnNode.nextVirtual: AbstractInsnNode? |
||||
get() { |
||||
var insn = next |
||||
while (insn != null && insn.opcode != -1) { |
||||
insn = insn.next |
||||
} |
||||
return insn |
||||
} |
||||
|
||||
val AbstractInsnNode.previousVirtual: AbstractInsnNode? |
||||
get() { |
||||
var insn = previous |
||||
while (insn != null && insn.opcode != -1) { |
||||
insn = insn.previous |
||||
} |
||||
return insn |
||||
} |
||||
|
||||
val AbstractInsnNode.intConstant: Int? |
||||
get() = when (this) { |
||||
is IntInsnNode -> { |
||||
if (opcode == Opcodes.BIPUSH || opcode == Opcodes.SIPUSH) { |
||||
operand |
||||
} else { |
||||
null |
||||
} |
||||
} |
||||
is LdcInsnNode -> { |
||||
val cst = cst |
||||
if (cst is Int) { |
||||
cst |
||||
} else { |
||||
null |
||||
} |
||||
} |
||||
else -> when (opcode) { |
||||
Opcodes.ICONST_M1 -> -1 |
||||
Opcodes.ICONST_0 -> 0 |
||||
Opcodes.ICONST_1 -> 1 |
||||
Opcodes.ICONST_2 -> 2 |
||||
Opcodes.ICONST_3 -> 3 |
||||
Opcodes.ICONST_4 -> 4 |
||||
Opcodes.ICONST_5 -> 5 |
||||
else -> null |
||||
} |
||||
} |
||||
|
||||
val AbstractInsnNode.pure: Boolean |
||||
get() = when { |
||||
PURE_OPCODES.contains(opcode) -> true |
||||
IMPURE_OPCODES.contains(opcode) -> false |
||||
else -> throw IllegalArgumentException() |
||||
} |
||||
|
||||
fun createIntConstant(value: Int): AbstractInsnNode = when (value) { |
||||
-1 -> InsnNode(Opcodes.ICONST_M1) |
||||
0 -> InsnNode(Opcodes.ICONST_0) |
||||
1 -> InsnNode(Opcodes.ICONST_1) |
||||
2 -> InsnNode(Opcodes.ICONST_2) |
||||
3 -> InsnNode(Opcodes.ICONST_3) |
||||
4 -> InsnNode(Opcodes.ICONST_4) |
||||
5 -> InsnNode(Opcodes.ICONST_5) |
||||
in Byte.MIN_VALUE..Byte.MAX_VALUE -> IntInsnNode(Opcodes.BIPUSH, value) |
||||
in Short.MIN_VALUE..Short.MAX_VALUE -> IntInsnNode(Opcodes.SIPUSH, value) |
||||
else -> LdcInsnNode(value) |
||||
} |
||||
|
||||
fun AbstractInsnNode.toPrettyString(): String { |
||||
val printer = Textifier() |
||||
val visitor = TraceMethodVisitor(printer) |
||||
accept(visitor) |
||||
try { |
||||
StringWriter().use { stringWriter -> |
||||
PrintWriter(stringWriter).use { printWriter -> |
||||
printer.print(printWriter) |
||||
return stringWriter.toString().trim { it <= ' ' } |
||||
} |
||||
} |
||||
} catch (ex: IOException) { |
||||
throw UncheckedIOException(ex) |
||||
} |
||||
} |
Loading…
Reference in new issue