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