@@ -575,3 +575,180 @@ class JavapTask(val loader: ClassLoader, repl: DisassemblerRepl) extends Disasse
575
575
inputs.map(runInput).toList
576
576
end apply
577
577
end JavapTask
578
+
579
+ object Asmp extends Disassembler :
580
+ import Disassembler .*
581
+
582
+ def apply (opts : DisassemblerOptions )(using repl : DisassemblerRepl ): List [DisResult ] =
583
+ val tool = AsmpTool ()
584
+ val clazz = DisassemblyClass (repl.classLoader, repl)
585
+ tool(opts.flags)(opts.targets.map(clazz.bytes(_)))
586
+
587
+ val helps = List (
588
+ " usage" -> " :asmp [opts] [path or class or -]..." ,
589
+ " -help" -> " Prints this help message" ,
590
+ " -verbose/-v" -> " Stack size, number of locals, method args" ,
591
+ " -private/-p" -> " Private classes and members" ,
592
+ " -package" -> " Package-private classes and members" ,
593
+ " -protected" -> " Protected classes and members" ,
594
+ " -public" -> " Public classes and members" ,
595
+ " -c" -> " Disassembled code" ,
596
+ " -s" -> " Internal type signatures" ,
597
+ " -filter" -> " Filter REPL machinery from output" ,
598
+ " -raw" -> " Don't post-process output from ASM" , // TODO for debugging
599
+ " -decls" -> " Declarations" ,
600
+ " -bridges" -> " Bridges" ,
601
+ " -synthetics" -> " Synthetics" ,
602
+ )
603
+
604
+ override def filters (target : String , opts : DisassemblerOptions ): List [String => String ] =
605
+ val commonFilters = super .filters(target, opts)
606
+ if opts.flags.contains(" -decls" ) then filterCommentsBlankLines :: commonFilters
607
+ else squashConsectiveBlankLines :: commonFilters
608
+
609
+ private def squashConsectiveBlankLines (s : String ) = s.replaceAll(" \n {3,}" , " \n\n " )
610
+
611
+ private def filterCommentsBlankLines (s : String ): String =
612
+ val comment = raw " \s*// .* " .r
613
+ def isBlankLine (s : String ) = s.trim == " "
614
+ def isComment (s : String ) = comment.matches(s)
615
+ filteredLines(s, t => ! isComment(t) && ! isBlankLine(t))
616
+ end Asmp
617
+
618
+ object AsmpOptions extends DisassemblerOptionParser (Asmp .helps):
619
+ val defaultToolOptions = List (" -protected" , " -verbose" )
620
+
621
+ class AsmpTool extends DisassemblyTool :
622
+ import DisassemblyTool .*
623
+ import Disassembler .splitHashMember
624
+ import java .io .{PrintWriter , StringWriter }
625
+ import scala .tools .asm .{Attribute , ClassReader , Label , Opcodes }
626
+ import scala .tools .asm .util .{Textifier , TraceClassVisitor }
627
+ import dotty .tools .backend .jvm .ClassNode1
628
+
629
+ enum Mode :
630
+ case Verbose , Code , Signatures
631
+
632
+ class FilteringTextifier (opts : Seq [String ], mode : Mode , accessFilter : Int => Boolean , nameFilter : Option [String ]) extends Textifier (Opcodes .ASM9 ):
633
+ private def keep (access : Int , name : String ): Boolean =
634
+ accessFilter(access) && nameFilter.map(_ == name).getOrElse(true )
635
+
636
+ override def visitField (access : Int , name : String , descriptor : String , signature : String , value : Any ): Textifier =
637
+ if keep(access, name) then
638
+ super .visitField(access, name, descriptor, signature, value)
639
+ addNewTextifier(discard = (mode == Mode .Signatures ))
640
+ else
641
+ addNewTextifier(discard = true )
642
+
643
+ override def visitMethod (access: Int , name : String , descriptor : String , signature : String , exceptions : Array [String ]): Textifier =
644
+ if keep(access, name) then
645
+ super .visitMethod(access, name, descriptor, signature, exceptions)
646
+ addNewTextifier(discard = (mode == Mode .Signatures ))
647
+ else
648
+ addNewTextifier(discard = true )
649
+
650
+ override def visitInnerClass (name : String , outerName : String , innerName : String , access : Int ): Unit =
651
+ if mode == Mode .Verbose && keep(access, name) then
652
+ super .visitInnerClass(name, outerName, innerName, access)
653
+
654
+ override def visitClassAttribute (attribute : Attribute ): Unit =
655
+ if mode == Mode .Verbose && nameFilter.isEmpty then
656
+ super .visitClassAttribute(attribute)
657
+
658
+ override def visitClassAnnotation (descriptor : String , visible : Boolean ): Textifier =
659
+ // suppress ScalaSignature unless -raw given. Should we? TODO
660
+ if mode == Mode .Verbose && nameFilter.isEmpty && descriptor != " Lscala/reflect/ScalaSignature;" then
661
+ super .visitClassAnnotation(descriptor, visible)
662
+ else
663
+ addNewTextifier(discard = true )
664
+
665
+ override def visitSource (file : String , debug : String ): Unit =
666
+ if mode == Mode .Verbose && nameFilter.isEmpty then
667
+ super .visitSource(file, debug)
668
+
669
+ override def visitAnnotation (descriptor : String , visible : Boolean ): Textifier =
670
+ if mode == Mode .Verbose then
671
+ super .visitAnnotation(descriptor, visible)
672
+ else
673
+ addNewTextifier(discard = true )
674
+
675
+ override def visitLineNumber (line : Int , start : Label ): Unit =
676
+ if mode == Mode .Verbose then
677
+ super .visitLineNumber(line, start)
678
+
679
+ override def visitMaxs (maxStack : Int , maxLocals : Int ): Unit =
680
+ if mode == Mode .Verbose then
681
+ super .visitMaxs(maxStack, maxLocals)
682
+
683
+ override def visitLocalVariable (name : String , descriptor : String , signature : String , start : Label , end : Label , index : Int ): Unit =
684
+ if mode == Mode .Verbose then
685
+ super .visitLocalVariable(name, descriptor, signature, start, end, index)
686
+
687
+ private def isLabel (s : String ) = raw " \s*L\d+\s* " .r.matches(s)
688
+
689
+ // ugly hack to prevent orphaned label when local vars, max stack not displayed (e.g. in -c mode)
690
+ override def visitMethodEnd (): Unit = text.size match
691
+ case 0 =>
692
+ case n =>
693
+ if isLabel(text.get(n - 1 ).toString) then
694
+ try text.remove(n - 1 )
695
+ catch case _ : UnsupportedOperationException => ()
696
+
697
+ private def addNewTextifier (discard : Boolean = false ): Textifier =
698
+ val tx = FilteringTextifier (opts, mode, accessFilter, nameFilter)
699
+ if ! discard then text.add(tx.getText());
700
+ tx
701
+ end FilteringTextifier
702
+
703
+ override def apply (options : Seq [String ])(inputs : Seq [Input ]): List [DisResult ] =
704
+ def parseAccessOption (opts : Seq [String ]): Int =
705
+ if opts.contains(" -public" ) then Opcodes .ACC_PUBLIC
706
+ else if opts.contains(" -protected" ) then Opcodes .ACC_PROTECTED
707
+ else if opts.contains(" -private" ) || opts.contains(" -p" ) then Opcodes .ACC_PRIVATE
708
+ else 0
709
+
710
+ def accessFilter (opts : Seq [String ]): Int => Boolean =
711
+ inline def contains (mask : Int ) = (a : Int ) => (a & mask) != 0
712
+ inline def excludes (mask : Int ) = (a : Int ) => (a & mask) == 0
713
+ def accessible : Int => Boolean = parseAccessOption(opts) match
714
+ case Opcodes .ACC_PUBLIC => contains(Opcodes .ACC_PUBLIC )
715
+ case Opcodes .ACC_PROTECTED => contains(Opcodes .ACC_PUBLIC | Opcodes .ACC_PROTECTED )
716
+ case Opcodes .ACC_PRIVATE => _ => true
717
+ case _ /* package*/ => excludes(Opcodes .ACC_PRIVATE )
718
+ def included (access : Int ): Boolean = mode(opts) match
719
+ case Mode .Verbose => true
720
+ case _ =>
721
+ val isBridge = contains(Opcodes .ACC_BRIDGE )(access)
722
+ val isSynthetic = contains(Opcodes .ACC_SYNTHETIC )(access)
723
+ if isSynthetic && opts.contains(" -synthetics" ) then true
724
+ else if isBridge && opts.contains(" -bridges" ) then true
725
+ else if isSynthetic || isBridge then false
726
+ else true
727
+ (x : Int ) => accessible(x) && included(x)
728
+
729
+ def mode (opts : Seq [String ]): Mode =
730
+ if opts.contains(" -c" ) then Mode .Code
731
+ else if opts.contains(" -s" ) || opts.contains(" -decls" ) then Mode .Signatures
732
+ else Mode .Verbose // default
733
+
734
+ def runInput (input : Input ): DisResult = input match
735
+ case Input (target, actual, Success (bytes)) =>
736
+ val sw = StringWriter ()
737
+ val pw = PrintWriter (sw)
738
+ val node = ClassNode1 ()
739
+
740
+ def nameFilter = splitHashMember(target).map(s => if s.isEmpty then " apply" else s)
741
+ val tx =
742
+ if options.contains(" -raw" ) then Textifier ()
743
+ else FilteringTextifier (options, mode(options), accessFilter(options), nameFilter)
744
+
745
+ ClassReader (bytes).accept(node, 0 )
746
+ node.accept(TraceClassVisitor (null , tx, pw))
747
+ pw.flush()
748
+ DisSuccess (target, sw.toString)
749
+ case Input (_, _, Failure (e)) =>
750
+ DisError (e.getMessage)
751
+ end runInput
752
+
753
+ inputs.map(runInput).toList
754
+ end AsmpTool
0 commit comments