@@ -625,3 +625,206 @@ object JavapTask:
625
625
// introduced in JDK7 as internal API
626
626
val taskClassName = " com.sun.tools.javap.JavapTask"
627
627
end JavapTask
628
+
629
+ /** A disassembler implemented using the ASM library (a dependency of the backend)
630
+ * Supports flags similar to javap, with some additions and omissions.
631
+ */
632
+ object Asmp extends Disassembler :
633
+ import Disassembler .*
634
+
635
+ def apply (opts : DisassemblerOptions )(using repl : DisassemblerRepl ): List [DisResult ] =
636
+ val tool = AsmpTool ()
637
+ val clazz = DisassemblyClass (repl.classLoader)
638
+ tool(opts.flags)(opts.targets.map(clazz.bytes(_)))
639
+
640
+ // The flags are intended to resemble those used by javap
641
+ val helps = List (
642
+ " usage" -> " :asmp [opts] [path or class or -]..." ,
643
+ " -help" -> " Prints this help message" ,
644
+ " -verbose/-v" -> " Stack size, number of locals, method args" ,
645
+ " -private/-p" -> " Private classes and members" ,
646
+ " -package" -> " Package-private classes and members" ,
647
+ " -protected" -> " Protected classes and members" ,
648
+ " -public" -> " Public classes and members" ,
649
+ " -c" -> " Disassembled code" ,
650
+ " -s" -> " Internal type signatures" ,
651
+ " -filter" -> " Filter REPL machinery from output" ,
652
+ " -raw" -> " Don't post-process output from ASM" , // TODO for debugging
653
+ " -decls" -> " Declarations" ,
654
+ " -bridges" -> " Bridges" ,
655
+ " -synthetics" -> " Synthetics" ,
656
+ )
657
+
658
+ override def filters (target : String , opts : DisassemblerOptions ): List [String => String ] =
659
+ val commonFilters = super .filters(target, opts)
660
+ if opts.flags.contains(" -decls" ) then filterCommentsBlankLines :: commonFilters
661
+ else squashConsectiveBlankLines :: commonFilters // default filters
662
+
663
+ // A filter to compress consecutive blank lines into a single blank line
664
+ private def squashConsectiveBlankLines (s : String ) = s.replaceAll(" \n {3,}" , " \n\n " )
665
+
666
+ // A filter to remove all blank lines and lines beginning with "//"
667
+ private def filterCommentsBlankLines (s : String ): String =
668
+ val comment = raw " \s*// .* " .r
669
+ def isBlankLine (s : String ) = s.trim == " "
670
+ def isComment (s : String ) = comment.matches(s)
671
+ filteredLines(s, t => ! isComment(t) && ! isBlankLine(t))
672
+ end Asmp
673
+
674
+ object AsmpOptions extends DisassemblerOptionParser (Asmp .helps):
675
+ val defaultToolOptions = List (" -protected" , " -verbose" )
676
+
677
+ /** Implementation of the ASM-based disassembly tool. */
678
+ class AsmpTool extends DisassemblyTool :
679
+ import DisassemblyTool .*
680
+ import Disassembler .splitHashMember
681
+ import java .io .{PrintWriter , StringWriter }
682
+ import scala .tools .asm .{Attribute , ClassReader , Label , Opcodes }
683
+ import scala .tools .asm .util .{Textifier , TraceClassVisitor }
684
+ import dotty .tools .backend .jvm .ClassNode1
685
+
686
+ enum Mode :
687
+ case Verbose , Code , Signatures
688
+
689
+ /** A Textifier subclass to control the disassembly output based on flags.
690
+ * The visitor methods overriden here conditionally suppress their output
691
+ * based on the flags and targets supplied to the disassembly tool.
692
+ *
693
+ * The filtering performed falls into three categories:
694
+ * - operating mode: -verbose, -c, -s, etc.
695
+ * - access flags: -protected, -private, -public, etc.
696
+ * - member name: e.g. a target given as Klass#method
697
+ *
698
+ * This is all bypassed if the `-raw` flag is given.
699
+ */
700
+ class FilteringTextifier (mode : Mode , accessFilter : Int => Boolean , nameFilter : Option [String ])
701
+ extends Textifier (Opcodes .ASM9 ):
702
+ private def keep (access : Int , name : String ): Boolean =
703
+ accessFilter(access) && nameFilter.map(_ == name).getOrElse(true )
704
+
705
+ override def visitField (access : Int , name : String , descriptor : String , signature : String , value : Any ): Textifier =
706
+ if keep(access, name) then
707
+ super .visitField(access, name, descriptor, signature, value)
708
+ addNewTextifier(discard = (mode == Mode .Signatures ))
709
+ else
710
+ addNewTextifier(discard = true )
711
+
712
+ override def visitMethod (access: Int , name : String , descriptor : String , signature : String , exceptions : Array [String ]): Textifier =
713
+ if keep(access, name) then
714
+ super .visitMethod(access, name, descriptor, signature, exceptions)
715
+ addNewTextifier(discard = (mode == Mode .Signatures ))
716
+ else
717
+ addNewTextifier(discard = true )
718
+
719
+ override def visitInnerClass (name : String , outerName : String , innerName : String , access : Int ): Unit =
720
+ if mode == Mode .Verbose && keep(access, name) then
721
+ super .visitInnerClass(name, outerName, innerName, access)
722
+
723
+ override def visitClassAttribute (attribute : Attribute ): Unit =
724
+ if mode == Mode .Verbose && nameFilter.isEmpty then
725
+ super .visitClassAttribute(attribute)
726
+
727
+ override def visitClassAnnotation (descriptor : String , visible : Boolean ): Textifier =
728
+ // suppress ScalaSignature unless -raw given. Should we? TODO
729
+ if mode == Mode .Verbose && nameFilter.isEmpty && descriptor != " Lscala/reflect/ScalaSignature;" then
730
+ super .visitClassAnnotation(descriptor, visible)
731
+ else
732
+ addNewTextifier(discard = true )
733
+
734
+ override def visitSource (file : String , debug : String ): Unit =
735
+ if mode == Mode .Verbose && nameFilter.isEmpty then
736
+ super .visitSource(file, debug)
737
+
738
+ override def visitAnnotation (descriptor : String , visible : Boolean ): Textifier =
739
+ if mode == Mode .Verbose then
740
+ super .visitAnnotation(descriptor, visible)
741
+ else
742
+ addNewTextifier(discard = true )
743
+
744
+ override def visitLineNumber (line : Int , start : Label ): Unit =
745
+ if mode == Mode .Verbose then
746
+ super .visitLineNumber(line, start)
747
+
748
+ override def visitMaxs (maxStack : Int , maxLocals : Int ): Unit =
749
+ if mode == Mode .Verbose then
750
+ super .visitMaxs(maxStack, maxLocals)
751
+
752
+ override def visitLocalVariable (name : String , descriptor : String , signature : String , start : Label , end : Label , index : Int ): Unit =
753
+ if mode == Mode .Verbose then
754
+ super .visitLocalVariable(name, descriptor, signature, start, end, index)
755
+
756
+ private def isLabel (s : String ) = raw " \s*L\d+\s* " .r.matches(s)
757
+
758
+ // ugly hack to prevent orphaned label when local vars, max stack not displayed (e.g. in -c mode)
759
+ override def visitMethodEnd (): Unit = text.size match
760
+ case 0 =>
761
+ case n =>
762
+ if isLabel(text.get(n - 1 ).toString) then
763
+ try text.remove(n - 1 )
764
+ catch case _ : UnsupportedOperationException => ()
765
+
766
+ private def addNewTextifier (discard : Boolean = false ): Textifier =
767
+ val tx = FilteringTextifier (mode, accessFilter, nameFilter)
768
+ if ! discard then text.add(tx.getText())
769
+ tx
770
+ end FilteringTextifier
771
+
772
+ override def apply (options : Seq [String ])(inputs : Seq [Input ]): List [DisResult ] =
773
+ def parseMode (opts : Seq [String ]): Mode =
774
+ if opts.contains(" -c" ) then Mode .Code
775
+ else if opts.contains(" -s" ) || opts.contains(" -decls" ) then Mode .Signatures
776
+ else Mode .Verbose // default
777
+
778
+ def parseAccessLevel (opts : Seq [String ]): Int =
779
+ if opts.contains(" -public" ) then Opcodes .ACC_PUBLIC
780
+ else if opts.contains(" -protected" ) then Opcodes .ACC_PROTECTED
781
+ else if opts.contains(" -private" ) || opts.contains(" -p" ) then Opcodes .ACC_PRIVATE
782
+ else 0
783
+
784
+ def accessFilter (mode : Mode , accessLevel : Int , opts : Seq [String ]): Int => Boolean =
785
+ inline def contains (mask : Int ) = (a : Int ) => (a & mask) != 0
786
+ inline def excludes (mask : Int ) = (a : Int ) => (a & mask) == 0
787
+ val showSynthetics = opts.contains(" -synthetics" )
788
+ val showBridges = opts.contains(" -bridges" )
789
+ def accessible : Int => Boolean = accessLevel match
790
+ case Opcodes .ACC_PUBLIC => contains(Opcodes .ACC_PUBLIC )
791
+ case Opcodes .ACC_PROTECTED => contains(Opcodes .ACC_PUBLIC | Opcodes .ACC_PROTECTED )
792
+ case Opcodes .ACC_PRIVATE => _ => true
793
+ case _ /* package */ => excludes(Opcodes .ACC_PRIVATE )
794
+ def included (access : Int ): Boolean = mode match
795
+ case Mode .Verbose => true
796
+ case _ =>
797
+ val isBridge = contains(Opcodes .ACC_BRIDGE )(access)
798
+ val isSynthetic = contains(Opcodes .ACC_SYNTHETIC )(access)
799
+ if isSynthetic && showSynthetics then true // TODO do we have tests for -synthetics?
800
+ else if isBridge && showBridges then true // TODO do we have tests for -bridges?
801
+ else if isSynthetic || isBridge then false
802
+ else true
803
+ a => accessible(a) && included(a)
804
+
805
+ def runInput (input : Input ): DisResult = input match
806
+ case Input (target, _, Success (bytes)) =>
807
+ val sw = StringWriter ()
808
+ val pw = PrintWriter (sw)
809
+ val node = ClassNode1 ()
810
+
811
+ val tx =
812
+ if options.contains(" -raw" ) then
813
+ Textifier ()
814
+ else
815
+ val mode = parseMode(options)
816
+ val accessLevel = parseAccessLevel(options)
817
+ val nameFilter = splitHashMember(target).map(s => if s.isEmpty then " apply" else s)
818
+ FilteringTextifier (mode, accessFilter(mode, accessLevel, options), nameFilter)
819
+
820
+ ClassReader (bytes).accept(node, 0 )
821
+ node.accept(TraceClassVisitor (null , tx, pw))
822
+ pw.flush()
823
+ DisSuccess (target, sw.toString)
824
+ case Input (_, _, Failure (e)) =>
825
+ DisError (e.getMessage)
826
+ end runInput
827
+
828
+ inputs.map(runInput).toList
829
+ end apply
830
+ end AsmpTool
0 commit comments