@@ -9,8 +9,10 @@ import java.nio.charset.StandardCharsets
9
9
import javax .annotation .Nullable
10
10
11
11
import scala .annotation .tailrec
12
+ import scala .concurrent .Await
12
13
import scala .concurrent .ExecutionContext
13
14
import scala .concurrent .Future
15
+ import scala .concurrent .duration .Duration
14
16
import scala .util .Failure
15
17
import scala .util .Properties
16
18
import scala .util .Success
@@ -73,6 +75,9 @@ object DecoderResponse {
73
75
def failed (uri : String , e : Throwable ): DecoderResponse =
74
76
failed(uri.toString(), getAllMessages(e))
75
77
78
+ def failed (uri : URI , errorMsg : String , e : Throwable ): DecoderResponse =
79
+ failed(uri, s " $errorMsg\n ${getAllMessages(e)}" )
80
+
76
81
def failed (uri : URI , e : Throwable ): DecoderResponse =
77
82
failed(uri, getAllMessages(e))
78
83
@@ -84,6 +89,7 @@ final class FileDecoderProvider(
84
89
workspace : AbsolutePath ,
85
90
compilers : Compilers ,
86
91
buildTargets : BuildTargets ,
92
+ uriMapper : URIMapper ,
87
93
userConfig : () => UserConfiguration ,
88
94
shellRunner : ShellRunner ,
89
95
fileSystemSemanticdbs : FileSystemSemanticdbs ,
@@ -126,6 +132,9 @@ final class FileDecoderProvider(
126
132
* metalsDecode:file:///somePath/someFile.scala.cfr
127
133
* metalsDecode:file:///somePath/someFile.class.cfr
128
134
*
135
+ * Auto-CFR:
136
+ * metalsfs:/metalslibraries/jar/someFile.jar/somePackage/someFile.class
137
+ *
129
138
* semanticdb:
130
139
* metalsDecode:file:///somePath/someFile.java.semanticdb-compact
131
140
* metalsDecode:file:///somePath/someFile.java.semanticdb-detailed
@@ -145,6 +154,7 @@ final class FileDecoderProvider(
145
154
*
146
155
* jar:
147
156
* metalsDecode:jar:file:///somePath/someFile-sources.jar!/somePackage/someFile.java
157
+ * jar:file:///somePath/someFile-sources.jar!/somePackage/someFile.java.semanticdb-compact
148
158
*
149
159
* build target:
150
160
* metalsDecode:file:///workspacePath/buildTargetName.metals-buildtarget
@@ -162,11 +172,13 @@ final class FileDecoderProvider(
162
172
case " file" => decodeMetalsFile(uri)
163
173
case " metalsDecode" =>
164
174
decodedFileContents(uri.getSchemeSpecificPart())
175
+ case " metalsfs" =>
176
+ decodedFileContents(uriMapper.convertToLocal(uriAsStr))
165
177
case _ =>
166
178
Future .successful(
167
179
DecoderResponse .failed(
168
180
uri,
169
- s " Unexpected scheme ${uri.getScheme()}"
181
+ s " Unexpected scheme ${uri.getScheme()} in $uri "
170
182
)
171
183
)
172
184
}
@@ -246,7 +258,10 @@ final class FileDecoderProvider(
246
258
)
247
259
case _ =>
248
260
Future .successful(
249
- DecoderResponse .failed(uri, " Unsupported extension" )
261
+ DecoderResponse .failed(
262
+ uri,
263
+ s " Unsupported extension $additionalExtension in $uri"
264
+ )
250
265
)
251
266
}
252
267
}
@@ -304,7 +319,7 @@ final class FileDecoderProvider(
304
319
path : AbsolutePath ,
305
320
format : Format
306
321
): DecoderResponse = {
307
- if (path.isScalaOrJava)
322
+ if (path.isScalaOrJava || path.isClassfile )
308
323
interactiveSemanticdbs
309
324
.textDocument(path)
310
325
.documentIncludingStale
@@ -316,7 +331,8 @@ final class FileDecoderProvider(
316
331
.fold(identity, identity)
317
332
)
318
333
else if (path.isSemanticdb) decodeFromSemanticDBFile(path, format)
319
- else DecoderResponse .failed(path.toURI, " Unsupported extension" )
334
+ else
335
+ DecoderResponse .failed(path.toURI, s " Unsupported extension ${path.toURI}" )
320
336
}
321
337
322
338
private def isScala3 (path : AbsolutePath ): Boolean = {
@@ -340,7 +356,7 @@ final class FileDecoderProvider(
340
356
)
341
357
)
342
358
} else if (path.isTasty) {
343
- findPathInfoForClassesPathFile (path) match {
359
+ findPathInfoForTastyPathFile (path) match {
344
360
case Some (pathInfo) => decodeFromTastyFile(pathInfo)
345
361
case None =>
346
362
Future .successful(
@@ -370,15 +386,25 @@ final class FileDecoderProvider(
370
386
PathInfo (metadata.targetId, metadata.classDir.resolve(relativePath))
371
387
})
372
388
373
- private def findPathInfoForClassesPathFile (
389
+ private def findPathInfoForTastyPathFile (
374
390
path : AbsolutePath
375
391
): Option [PathInfo ] = {
376
- val pathInfos = for {
377
- targetId <- buildTargets.allBuildTargetIds
378
- classDir <- buildTargets.targetClassDirectories(targetId)
379
- classPath = classDir.toAbsolutePath
380
- if (path.isInside(classPath))
381
- } yield PathInfo (targetId, path)
392
+ val pathInfos = if (path.isJarFileSystem) {
393
+ // should only ever exist in workspace jars
394
+ for {
395
+ targetId <- buildTargets.allBuildTargetIds
396
+ targetJars <- buildTargets.targetJarClasspath(targetId)
397
+ jarPath <- path.jarPath
398
+ if (targetJars.contains(jarPath))
399
+ } yield PathInfo (targetId, path)
400
+ } else {
401
+ for {
402
+ targetId <- buildTargets.allBuildTargetIds
403
+ classDir <- buildTargets.targetClassDirectories(targetId)
404
+ classPath = classDir.toAbsolutePath
405
+ if (path.isInside(classPath))
406
+ } yield PathInfo (targetId, path)
407
+ }
382
408
pathInfos.toList.headOption
383
409
}
384
410
@@ -511,17 +537,27 @@ final class FileDecoderProvider(
511
537
verbose : Boolean
512
538
)(path : AbsolutePath ): Future [DecoderResponse ] = {
513
539
try {
514
- val defaultArgs = List (" -private" )
515
- val args = if (verbose) " -verbose" :: defaultArgs else defaultArgs
516
540
val sbOut = new StringBuilder ()
517
541
val sbErr = new StringBuilder ()
542
+ val (classpath, parent, filename) = if (path.isJarFileSystem) {
543
+ val parent = workspace
544
+ val className = path.toString.stripPrefix(" /" ).stripSuffix(" .class" )
545
+ (
546
+ path.jarPath.toList.flatMap(cp => List (" -cp" , cp.toString)),
547
+ parent,
548
+ className
549
+ )
550
+ } else
551
+ (List .empty, path.parent, path.filename)
552
+ val defaultArgs = classpath ::: List (" -private" )
553
+ val args = if (verbose) " -verbose" :: defaultArgs else defaultArgs
518
554
shellRunner
519
555
.run(
520
556
" Decode using javap" ,
521
557
JavaBinary (userConfig().javaHome, " javap" ) :: args ::: List (
522
- path. filename
558
+ filename
523
559
),
524
- path. parent,
560
+ parent,
525
561
redirectErrorOutput = false ,
526
562
Map .empty,
527
563
s => {
@@ -543,30 +579,45 @@ final class FileDecoderProvider(
543
579
})
544
580
} catch {
545
581
case NonFatal (e) =>
546
- scribe.error(e.toString() )
582
+ scribe.error(s " $e ${e.getStackTrace.mkString( " \n at " )} " )
547
583
Future .successful(DecoderResponse .failed(path.toURI, e))
548
584
}
549
585
}
550
586
551
- private def decodeCFRFromClassFile (
587
+ def decodeCFRAndWait (path : AbsolutePath ): DecoderResponse = {
588
+ val result = decodeCFRFromClassFile(path)
589
+ Await .result(result, Duration (" 10min" ))
590
+ }
591
+
592
+ def decodeCFRFromClassFile (
552
593
path : AbsolutePath
553
594
): Future [DecoderResponse ] = {
554
595
val cfrDependency = Dependency .of(" org.benf" , " cfr" , " 0.151" )
555
596
val cfrMain = " org.benf.cfr.reader.Main"
556
597
598
+ // find the build target so we can use the full classpath - needed for a better decompile
599
+ // class file can be in classes output dir or could be in a jar on the build's classpath
557
600
val buildTarget = buildTargets
558
601
.inferBuildTarget(path)
559
602
.orElse(
560
603
buildTargets.allScala
561
604
.find(buildTarget =>
562
- path.isInside(buildTarget.classDirectory.toAbsolutePath)
605
+ path.isInside(
606
+ buildTarget.classDirectory.toAbsolutePath
607
+ ) || path.jarPath
608
+ .map(jar => buildTarget.fullClasspath.contains(jar.toNIO))
609
+ .getOrElse(false )
563
610
)
564
611
.map(_.id)
565
612
)
566
613
.orElse(
567
614
buildTargets.allJava
568
615
.find(buildTarget =>
569
- path.isInside(buildTarget.classDirectory.toAbsolutePath)
616
+ path.isInside(
617
+ buildTarget.classDirectory.toAbsolutePath
618
+ ) || path.jarPath
619
+ .map(jar => buildTarget.fullClasspath.contains(jar.toNIO))
620
+ .getOrElse(false )
570
621
)
571
622
.map(_.id)
572
623
)
@@ -597,9 +648,16 @@ final class FileDecoderProvider(
597
648
(classesPath, className)
598
649
})
599
650
.getOrElse({
600
- val parent = path.parent
601
- val className = path.filename
602
- (parent, className)
651
+ if (path.isJarFileSystem) {
652
+ // if the file is in a jar then use the workspace as the working dir and the fully qualified name as the class
653
+ val parent = workspace
654
+ val className = path.toString.stripPrefix(" /" )
655
+ (parent, className)
656
+ } else {
657
+ val parent = path.parent
658
+ val className = path.filename
659
+ (parent, className)
660
+ }
603
661
})
604
662
605
663
val args = extraClassPath :::
@@ -637,15 +695,21 @@ final class FileDecoderProvider(
637
695
if (sbOut.isEmpty && sbErr.nonEmpty)
638
696
DecoderResponse .failed(
639
697
path.toURI,
640
- s " $ cfrDependency\n $cfrMain\n $parent\n $args\n ${sbErr.toString}"
698
+ s " buildTarget: $buildTarget \n CFRJar: $ cfrDependency\n CFRMain: $cfrMain\n Parent: $parent\n Args: $args\n Error: ${sbErr.toString}"
641
699
)
642
700
else
643
701
DecoderResponse .success(path.toURI, sbOut.toString)
644
702
})
645
703
} catch {
646
704
case NonFatal (e) =>
647
705
scribe.error(e.toString())
648
- Future .successful(DecoderResponse .failed(path.toURI, e))
706
+ Future .successful(
707
+ DecoderResponse .failed(
708
+ path.toURI,
709
+ s " buildTarget: $buildTarget\n CFRJar: $cfrDependency\n CFRMain: $cfrMain\n Parent: $parent\n Args: $args\n Error: ${sbErr.toString}" ,
710
+ e
711
+ )
712
+ )
649
713
}
650
714
}
651
715
0 commit comments