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