@@ -627,3 +627,206 @@ object JavapTask:
627627 // introduced in JDK7 as internal API
628628 val taskClassName = " com.sun.tools.javap.JavapTask"
629629end 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