/
GenASM.scala
3358 lines (2857 loc) · 140 KB
/
GenASM.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Martin Odersky
*/
package scala
package tools.nsc
package backend.jvm
import scala.collection.{ mutable, immutable }
import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer }
import scala.tools.nsc.backend.jvm.opt.InlineInfoAttribute
import scala.tools.nsc.symtab._
import scala.tools.asm
import asm.Label
import scala.annotation.tailrec
/**
* @author Iulian Dragos (version 1.0, FJBG-based implementation)
* @author Miguel Garcia (version 2.0, ASM-based implementation)
*
* Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf
*/
abstract class GenASM extends SubComponent with BytecodeWriters { self =>
import global._
import icodes._
import icodes.opcodes._
import definitions._
val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global)
import bCodeAsmCommon._
// Strangely I can't find this in the asm code
// 255, but reserving 1 for "this"
final val MaximumJvmParameters = 254
val phaseName = "jvm"
/** Create a new phase */
override def newPhase(p: Phase): Phase = new AsmPhase(p)
/** From the reference documentation of the Android SDK:
* The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`.
* Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`,
* which is an object implementing the `Parcelable.Creator` interface.
*/
private val androidFieldName = newTermName("CREATOR")
private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable")
private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator")
/** JVM code generation phase
*/
class AsmPhase(prev: Phase) extends ICodePhase(prev) {
def name = phaseName
override def erasedTypes = true
def apply(cls: IClass) = sys.error("no implementation")
// An AsmPhase starts and ends within a Run, thus the caches in question will get populated and cleared within a Run, too), SI-7422
javaNameCache.clear()
javaNameCache ++= List(
NothingClass -> binarynme.RuntimeNothing,
RuntimeNothingClass -> binarynme.RuntimeNothing,
NullClass -> binarynme.RuntimeNull,
RuntimeNullClass -> binarynme.RuntimeNull
)
// unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names.
reverseJavaName.clear()
reverseJavaName ++= List(
binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type.
binarynme.RuntimeNull.toString() -> RuntimeNullClass
)
// Lazy val; can't have eager vals in Phase constructors which may
// cause cycles before Global has finished initialization.
lazy val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo")
private def initBytecodeWriter(entryPoints: List[IClass]): BytecodeWriter = {
settings.outputDirs.getSingleOutput match {
case Some(f) if f hasExtension "jar" =>
// If no main class was specified, see if there's only one
// entry point among the classes going into the jar.
if (settings.mainClass.isDefault) {
entryPoints map (_.symbol fullName '.') match {
case Nil =>
log("No Main-Class designated or discovered.")
case name :: Nil =>
log("Unique entry point: setting Main-Class to " + name)
settings.mainClass.value = name
case names =>
log("No Main-Class due to multiple entry points:\n " + names.mkString("\n "))
}
}
else log("Main-Class was specified: " + settings.mainClass.value)
new DirectToJarfileWriter(f.file)
case _ => factoryNonJarBytecodeWriter()
}
}
private def isJavaEntryPoint(icls: IClass) = {
val sym = icls.symbol
def fail(msg: String, pos: Position = sym.pos) = {
reporter.warning(sym.pos,
sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" +
" Reason: " + msg
// TODO: make this next claim true, if possible
// by generating valid main methods as static in module classes
// not sure what the jvm allows here
// + " You can still run the program by calling it as " + sym.javaSimpleName + " instead."
)
false
}
def failNoForwarder(msg: String) = {
fail(msg + ", which means no static forwarder can be generated.\n")
}
val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil
val hasApproximate = possibles exists { m =>
m.info match {
case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass
case _ => false
}
}
// At this point it's a module with a main-looking method, so either succeed or warn that it isn't.
hasApproximate && {
// Before erasure so we can identify generic mains.
enteringErasure {
val companion = sym.linkedClassOfClass
if (hasJavaMainMethod(companion))
failNoForwarder("companion contains its own main method")
else if (companion.tpe.member(nme.main) != NoSymbol)
// this is only because forwarders aren't smart enough yet
failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
else if (companion.isTrait)
failNoForwarder("companion is a trait")
// Now either succeeed, or issue some additional warnings for things which look like
// attempts to be java main methods.
else (possibles exists isJavaMainMethod) || {
possibles exists { m =>
m.info match {
case PolyType(_, _) =>
fail("main methods cannot be generic.")
case MethodType(params, res) =>
if (res.typeSymbol :: params exists (_.isAbstractType))
fail("main methods cannot refer to type parameters or abstract types.", m.pos)
else
isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos)
case tp =>
fail("don't know what this is: " + tp, m.pos)
}
}
}
}
}
}
override def run() {
if (settings.debug)
inform("[running phase " + name + " on icode]")
if (settings.Xdce) {
val classes = icodes.classes.keys.toList // copy to avoid mutating the map while iterating
for (sym <- classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) {
log(s"Optimizer eliminated ${sym.fullNameString}")
deadCode.elidedClosures += sym
icodes.classes -= sym
}
}
// For predictably ordered error messages.
var sortedClasses = classes.values.toList sortBy (_.symbol.fullName)
// Warn when classes will overwrite one another on case-insensitive systems.
for ((_, v1 :: v2 :: _) <- sortedClasses groupBy (_.symbol.javaClassName.toString.toLowerCase)) {
reporter.warning(v1.symbol.pos,
s"Class ${v1.symbol.javaClassName} differs only in case from ${v2.symbol.javaClassName}. " +
"Such classes will overwrite one another on case-insensitive filesystems.")
}
debuglog(s"Created new bytecode generator for ${classes.size} classes.")
val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint)
val needsOutfile = bytecodeWriter.isInstanceOf[ClassBytecodeWriter]
val plainCodeGen = new JPlainBuilder( bytecodeWriter, needsOutfile)
val mirrorCodeGen = new JMirrorBuilder( bytecodeWriter, needsOutfile)
val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter, needsOutfile)
def emitFor(c: IClass) {
if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) {
if (c.symbol.companionClass == NoSymbol)
mirrorCodeGen genMirrorClass (c.symbol, c.cunit)
else
log(s"No mirror class for module with linked class: ${c.symbol.fullName}")
}
plainCodeGen genClass c
if (c.symbol hasAnnotation BeanInfoAttr) beanInfoCodeGen genBeanInfoClass c
}
while (!sortedClasses.isEmpty) {
val c = sortedClasses.head
try emitFor(c)
catch {
case e: FileConflictException =>
reporter.error(c.symbol.pos, s"error writing ${c.symbol}: ${e.getMessage}")
}
sortedClasses = sortedClasses.tail
classes -= c.symbol // GC opportunity
}
bytecodeWriter.close()
/* don't javaNameCache.clear() because that causes the following tests to fail:
* test/files/run/macro-repl-dontexpand.scala
* test/files/jvm/interpreter.scala
* TODO but why? what use could javaNameCache possibly see once GenASM is over?
*/
/* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification:
*
* (1) call the asm.util.CheckAdapter.verify() overload:
* public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw)
*
* (2) passing a custom ClassLoader to verify inter-dependent classes.
*
* Alternatively, an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool).
*/
} // end of AsmPhase.run()
} // end of class AsmPhase
var pickledBytes = 0 // statistics
val javaNameCache = perRunCaches.newAnyRefMap[Symbol, Name]()
// unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names.
val reverseJavaName = perRunCaches.newAnyRefMap[String, Symbol]()
private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _)
private def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0
private def isRemote(s: Symbol) = s hasAnnotation RemoteAttr
/**
* Return the Java modifiers for the given symbol.
* Java modifiers for classes:
* - public, abstract, final, strictfp (not used)
* for interfaces:
* - the same as for classes, without 'final'
* for fields:
* - public, private (*)
* - static, final
* for methods:
* - the same as for fields, plus:
* - abstract, synchronized (not used), strictfp (not used), native (not used)
*
* (*) protected cannot be used, since inner classes 'see' protected members,
* and they would fail verification after lifted.
*/
def javaFlags(sym: Symbol): Int = {
// constructors of module classes should be private
// PP: why are they only being marked private at this stage and not earlier?
val privateFlag =
sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner))
// Final: the only fields which can receive ACC_FINAL are eager vals.
// Neither vars nor lazy vals can, because:
//
// Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
// "Another problem is that the specification allows aggressive
// optimization of final fields. Within a thread, it is permissible to
// reorder reads of a final field with those modifications of a final
// field that do not take place in the constructor."
//
// A var or lazy val which is marked final still has meaning to the
// scala compiler. The word final is heavily overloaded unfortunately;
// for us it means "not overridable". At present you can't override
// vars regardless; this may change.
//
// The logic does not check .isFinal (which checks flags for the FINAL flag,
// and includes symbols marked lateFINAL) instead inspecting rawflags so
// we can exclude lateFINAL. Such symbols are eligible for inlining, but to
// avoid breaking proxy software which depends on subclassing, we do not
// emit ACC_FINAL.
// Nested objects won't receive ACC_FINAL in order to allow for their overriding.
val finalFlag = (
(((sym.rawflags & Flags.FINAL) != 0) || isTopLevelModule(sym))
&& !sym.enclClass.isInterface
&& !sym.isClassConstructor
&& !sym.isMutable // lazy vals and vars both
)
// Primitives are "abstract final" to prohibit instantiation
// without having to provide any implementations, but that is an
// illegal combination of modifiers at the bytecode level so
// suppress final if abstract if present.
import asm.Opcodes._
mkFlags(
if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0,
if (sym.isInterface) ACC_INTERFACE else 0,
if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0,
if (sym.isStaticMember) ACC_STATIC else 0,
if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
if (sym.isArtifact) ACC_SYNTHETIC else 0,
if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
if (sym.hasJavaEnumFlag) ACC_ENUM else 0,
if (sym.isVarargsMethod) ACC_VARARGS else 0,
if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0
)
}
def javaFieldFlags(sym: Symbol) = {
javaFlags(sym) | mkFlags(
if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0,
if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0,
if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL
)
}
def isTopLevelModule(sym: Symbol): Boolean =
exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass }
def isStaticModule(sym: Symbol): Boolean = {
sym.isModuleClass && !sym.isImplClass && !sym.isLifted
}
// -----------------------------------------------------------------------------------------
// finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM)
// Background:
// http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf
// http://comments.gmane.org/gmane.comp.java.vm.languages/2293
// https://issues.scala-lang.org/browse/SI-3872
// -----------------------------------------------------------------------------------------
/**
* Given an internal name (eg "java/lang/Integer") returns the class symbol for it.
*
* Better not to need this method (an example where control flow arrives here is welcome).
* This method is invoked only upon both (1) and (2) below happening:
* (1) providing an asm.ClassWriter with an internal name by other means than javaName()
* (2) forgetting to track the corresponding class-symbol in reverseJavaName.
*
* (The first item is already unlikely because we rely on javaName()
* to do the bookkeeping for entries that should go in innerClassBuffer.)
*
* (We could do completely without this method at the expense of computing stack-map-frames ourselves and
* invoking visitFrame(), but that would require another pass over all instructions.)
*
* Right now I can't think of any invocation of visitSomething() on MethodVisitor
* where we hand an internal name not backed by a reverseJavaName.
* However, I'm leaving this note just in case any such oversight is discovered.
*/
def inameToSymbol(iname: String): Symbol = {
val name = global.newTypeName(iname)
val res0 =
if (nme.isModuleName(name)) rootMirror.getModuleByName(name.dropModule)
else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested).
assert(res0 != NoSymbol)
val res = jsymbol(res0)
res
}
def jsymbol(sym: Symbol): Symbol = {
if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass
else if(sym.isModule) sym.moduleClass
else sym // we track only module-classes and plain-classes
}
private def superClasses(s: Symbol): List[Symbol] = {
assert(!s.isInterface)
s.superClass match {
case NoSymbol => List(s)
case sc => s :: superClasses(sc)
}
}
private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = {
assert(!(as contains NoSymbol))
assert(!(bs contains NoSymbol))
var chainA = as
var chainB = bs
var fcs: Symbol = NoSymbol
do {
if (chainB contains chainA.head) fcs = chainA.head
else if (chainA contains chainB.head) fcs = chainB.head
else {
chainA = chainA.tail
chainB = chainB.tail
}
} while(fcs == NoSymbol)
fcs
}
private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = {
assert(a.isClass)
assert(b.isClass)
val res = (a.isInterface, b.isInterface) match {
case (true, true) =>
global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents
case (true, false) =>
if(b isSubClass a) a else ObjectClass
case (false, true) =>
if(a isSubClass b) b else ObjectClass
case _ =>
firstCommonSuffix(superClasses(a), superClasses(b))
}
assert(res != NoSymbol)
res
}
/* The internal name of the least common ancestor of the types given by inameA and inameB.
It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow */
def getCommonSuperClass(inameA: String, inameB: String): String = {
val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA))
val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB))
// global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString()
// icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType
val lcaSym = jvmWiseLUB(a, b)
val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer.
val oldsym = reverseJavaName.put(lcaName, lcaSym)
assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption")
assert(lcaName != "scala/Any")
lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching.
}
class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {
override def getCommonSuperClass(iname1: String, iname2: String): String = {
GenASM.this.getCommonSuperClass(iname1, iname2)
}
}
// -----------------------------------------------------------------------------------------
// constants
// -----------------------------------------------------------------------------------------
private val classfileVersion: Int = settings.target.value match {
case "jvm-1.8" => asm.Opcodes.V1_8
}
private val majorVersion: Int = (classfileVersion & 0xFF)
private val emitStackMapFrame = (majorVersion >= 50)
private val extraProc: Int = mkFlags(
asm.ClassWriter.COMPUTE_MAXS,
if(emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0
)
val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object")
val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String")
/**
* We call many Java varargs methods from ASM library that expect Arra[asm.Type] as argument so
* we override default (compiler-generated) ClassTag so we can provide specialized newArray implementation.
*
* Examples of methods that should pick our definition are: JBuilder.javaType and JPlainBuilder.genMethod.
*/
private implicit val asmTypeTag: scala.reflect.ClassTag[asm.Type] = new scala.reflect.ClassTag[asm.Type] {
def runtimeClass: java.lang.Class[asm.Type] = classOf[asm.Type]
final override def newArray(len: Int): Array[asm.Type] = new Array[asm.Type](len)
}
/** basic functionality for class file building */
abstract class JBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) {
val EMPTY_STRING_ARRAY = Array.empty[String]
val mdesc_arglessvoid = "()V"
val CLASS_CONSTRUCTOR_NAME = "<clinit>"
val INSTANCE_CONSTRUCTOR_NAME = "<init>"
// -----------------------------------------------------------------------------------------
// factory methods
// -----------------------------------------------------------------------------------------
/**
* Returns a new ClassWriter for the class given by arguments.
*
* @param access the class's access flags. This parameter also indicates if the class is deprecated.
*
* @param name the internal name of the class.
*
* @param signature the signature of this class. May be <tt>null</tt> if
* the class is not a generic one, and does not extend or implement
* generic classes or interfaces.
*
* @param superName the internal of name of the super class. For interfaces,
* the super class is [[Object]]. May be <tt>null</tt>, but
* only for the [[Object]] class.
*
* @param interfaces the internal names of the class's interfaces (see
* {@link Type#getInternalName() getInternalName}). May be
* <tt>null</tt>.
*/
def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = {
val cw = new CClassWriter(extraProc)
cw.visit(classfileVersion,
access, name, signature,
superName, interfaces)
cw
}
def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = {
new asm.Attribute(name) {
override def write(classWriter: asm.ClassWriter, code: Array[Byte], codeLength: Int, maxStack: Int, maxLocals: Int): asm.ByteVector = {
val byteVector = new asm.ByteVector(len)
byteVector.putByteArray(b, offset, len)
byteVector
}
}
}
// -----------------------------------------------------------------------------------------
// utilities useful when emitting plain, mirror, and beaninfo classes.
// -----------------------------------------------------------------------------------------
def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) {
try {
val arr = jclass.toByteArray()
val outF: scala.tools.nsc.io.AbstractFile = {
if(needsOutfile) getFile(sym, jclassName, ".class") else null
}
bytecodeWriter.writeClass(label, jclassName, arr, outF)
} catch {
case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") =>
reporter.error(sym.pos,
s"Could not write class $jclassName because it exceeds JVM code size limits. ${e.getMessage}")
case e: java.io.IOException if e.getMessage != null && (e.getMessage contains "File name too long") =>
reporter.error(sym.pos, e.getMessage + "\n" +
"This can happen on some encrypted or legacy file systems. Please see SI-3623 for more details.")
}
}
/** Specialized array conversion to prevent calling
* java.lang.reflect.Array.newInstance via TraversableOnce.toArray
*/
def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a }
// -----------------------------------------------------------------------------------------
// Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances).
// These getters track behind the scenes the inner classes referred to in the class being emitted,
// so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()`
// (which also adds as member classes those inner classes that have been declared,
// thus also covering the case of inner classes declared but otherwise not referred).
// -----------------------------------------------------------------------------------------
val innerClassBuffer = mutable.LinkedHashSet[Symbol]()
/** For given symbol return a symbol corresponding to a class that should be declared as inner class.
*
* For example:
* class A {
* class B
* object C
* }
*
* then method will return:
* NoSymbol for A,
* the same symbol for A.B (corresponding to A$B class), and
* A$C$ symbol for A.C.
*/
def innerClassSymbolFor(s: Symbol): Symbol =
if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol
/** Return the name of this symbol that can be used on the Java platform. It removes spaces from names.
*
* Special handling:
* scala.Nothing erases to scala.runtime.Nothing$
* scala.Null erases to scala.runtime.Null$
*
* This is needed because they are not real classes, and they mean
* 'abrupt termination upon evaluation of that expression' or null respectively.
* This handling is done already in GenICode, but here we need to remove
* references from method signatures to these types, because such classes
* cannot exist in the classpath: the type checker will be very confused.
*/
def javaName(sym: Symbol): String = {
/*
* Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer
*
* Note: This method is called recursively thus making sure that we add complete chain
* of inner class all until root class.
*/
def collectInnerClass(s: Symbol): Unit = {
// TODO: some enteringFlatten { ... } which accounts for
// being nested in parameterized classes (if we're going to selectively flatten.)
val x = innerClassSymbolFor(s)
if(x ne NoSymbol) {
assert(x.isClass, "not an inner-class symbol")
// impl classes are considered top-level, see comment in BTypes
val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass
if (isInner) {
innerClassBuffer += x
collectInnerClass(x.rawowner)
}
}
}
collectInnerClass(sym)
val hasInternalName = sym.isClass || sym.isModuleNotMethod
val cachedJN = javaNameCache.getOrElseUpdate(sym, {
if (hasInternalName) { sym.javaBinaryName }
else { sym.javaSimpleName }
})
if(emitStackMapFrame && hasInternalName) {
val internalName = cachedJN.toString()
val trackedSym = jsymbol(sym)
reverseJavaName.get(internalName) match {
case None =>
reverseJavaName.put(internalName, trackedSym)
case Some(oldsym) =>
// TODO: `duplicateOk` seems pretty ad-hoc (a more aggressive version caused SI-9356 because it called oldSym.exists, which failed in the unpickler; see also SI-5031)
def duplicateOk = oldsym == NoSymbol || trackedSym == NoSymbol || (syntheticCoreClasses contains oldsym) || (oldsym.isModuleClass && (oldsym.sourceModule == trackedSym.sourceModule))
if (oldsym != trackedSym && !duplicateOk)
devWarning(s"""|Different class symbols have the same bytecode-level internal name:
| name: $internalName
| oldsym: ${oldsym.fullNameString}
| tracked: ${trackedSym.fullNameString}""".stripMargin)
}
}
cachedJN.toString
}
def descriptor(t: Type): String = { javaType(t).getDescriptor }
def descriptor(k: TypeKind): String = { javaType(k).getDescriptor }
def descriptor(s: Symbol): String = { javaType(s).getDescriptor }
def javaType(tk: TypeKind): asm.Type = {
if(tk.isValueType) {
if(tk.isIntSizedType) {
(tk: @unchecked) match {
case BOOL => asm.Type.BOOLEAN_TYPE
case BYTE => asm.Type.BYTE_TYPE
case SHORT => asm.Type.SHORT_TYPE
case CHAR => asm.Type.CHAR_TYPE
case INT => asm.Type.INT_TYPE
}
} else {
(tk: @unchecked) match {
case UNIT => asm.Type.VOID_TYPE
case LONG => asm.Type.LONG_TYPE
case FLOAT => asm.Type.FLOAT_TYPE
case DOUBLE => asm.Type.DOUBLE_TYPE
}
}
} else {
assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway)
(tk: @unchecked) match {
case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
case ARRAY(elem) => javaArrayType(javaType(elem))
}
}
}
def javaType(t: Type): asm.Type = javaType(toTypeKind(t))
def javaType(s: Symbol): asm.Type = {
if (s.isMethod) {
val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType)
asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _*)
} else { javaType(s.tpe) }
}
def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) }
def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) }
def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor, isMirror: Boolean = false) {
/* The outer name for this inner class. Note that it returns null
* when the inner class should not get an index in the constant pool.
* That means non-member classes (anonymous). See Section 4.7.5 in the JVMS.
*/
def outerName(innerSym: Symbol): String = {
if (isAnonymousOrLocalClass(innerSym))
null
else {
val outerName = javaName(innerSym.rawowner)
if (isTopLevelModule(innerSym.rawowner)) "" + TermName(outerName).dropModule
else outerName
}
}
def innerName(innerSym: Symbol): String = {
// phase travel necessary: after flatten, the name includes the name of outer classes.
// if some outer name contains $anon, a non-anon class is considered anon.
if (exitingPickler(innerSym.isAnonymousClass || innerSym.isAnonymousFunction)) null
else innerSym.rawname + innerSym.moduleSuffix
}
val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases
innerClassBuffer ++= {
val members = exitingPickler(memberClassesForInnerClassTable(csym))
// lambdalift makes all classes (also local, anonymous) members of their enclosing class
val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym))
val nested = {
// Classes nested in value classes are nested in the companion at this point. For InnerClass /
// EnclosingMethod, we use the value class as the outer class. So we remove nested classes
// from the companion that were originally nested in the value class.
if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass))
else allNested
}
// for the mirror class, we take the members of the companion module class (Java compat, see doc in BTypes.scala).
// for module classes, we filter out those members.
if (isMirror) members
else if (isTopLevelModule(csym)) nested diff members
else nested
}
if (!considerAsTopLevelImplementationArtifact(csym)) {
// If this is a top-level non-impl class, add members of the companion object. These are the
// classes for which we change the InnerClass entry to allow using them from Java.
// We exclude impl classes: if the classfile for the impl class exists on the classpath, a
// linkedClass symbol is found for which isTopLevelModule is true, so we end up searching
// members of that weird impl-class-module-class-symbol. that search probably cannot return
// any classes, but it's better to exclude it.
if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) {
// phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only
// sees member classes, not local classes that were lifted by lambdalift.
innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass))
}
// Classes nested in value classes are nested in the companion at this point. For InnerClass /
// EnclosingMethod we use the value class as enclosing class. Here we search nested classes
// in the companion that were originally nested in the value class, and we add them as nested
// in the value class.
if (linkedClass != NoSymbol && exitingPickler(csym.isDerivedValueClass)) {
val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass))
innerClassBuffer ++= moduleMemberClasses.filter(classOriginallyNestedInClass(_, csym))
}
}
val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures
if (allInners.nonEmpty) {
debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.")
// entries ready to be serialized into the classfile, used to detect duplicates.
val entries = mutable.Map.empty[String, String]
// sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler
for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ??
val flagsWithFinal: Int = mkFlags(
// See comment in BTypes, when is a class marked static in the InnerClass table.
if (isOriginallyStaticOwner(innerSym.originalOwner)) asm.Opcodes.ACC_STATIC else 0,
(if (innerSym.isJava) javaClassfileFlags(innerSym) else javaFlags(innerSym)) & ~asm.Opcodes.ACC_STATIC,
if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag
) & (BCodeAsmCommon.INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED)
val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding.
val jname = javaName(innerSym) // never null
val oname = outerName(innerSym) // null when method-enclosed
val iname = innerName(innerSym) // null for anonymous inner class
// Mimicking javap inner class output
debuglog(
if (oname == null || iname == null) "//class " + jname
else "//%s=class %s of class %s".format(iname, jname, oname)
)
assert(jname != null, "javaName is broken.") // documentation
val doAdd = entries.get(jname) match {
// TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute)
case Some(prevOName) =>
// this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State,
// i.e. for them it must be the case that oname == java/lang/Thread
assert(prevOName == oname, "duplicate")
false
case None => true
}
if(doAdd) {
entries += (jname -> oname)
jclass.visitInnerClass(jname, oname, iname, flags)
}
/*
* TODO assert (JVMS 4.7.6 The InnerClasses attribute)
* If a class file has a version number that is greater than or equal to 51.0, and
* has an InnerClasses attribute in its attributes table, then for all entries in the
* classes array of the InnerClasses attribute, the value of the
* outer_class_info_index item must be zero if the value of the
* inner_name_index item is zero.
*/
}
}
}
} // end of class JBuilder
/** functionality for building plain and mirror classes */
abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) {
def debugLevel = settings.debuginfo.indexOfChoice
val emitSource = debugLevel >= 1
val emitLines = debugLevel >= 2
val emitVars = debugLevel >= 3
// -----------------------------------------------------------------------------------------
// more constants
// -----------------------------------------------------------------------------------------
val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC
val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL
val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString
// -----------------------------------------------------------------------------------------
// Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only
// i.e., the pickle is contained in a custom annotation, see:
// (1) `addAnnotations()`,
// (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10
// (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5
// That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9)
// other than both ending up encoded as attributes (JVMS 4.7)
// (with the caveat that the "ScalaSig" attribute is associated to some classes,
// while the "Signature" attribute can be associated to classes, methods, and fields.)
// -----------------------------------------------------------------------------------------
val versionPickle = {
val vp = new PickleBuffer(new Array[Byte](16), -1, 0)
assert(vp.writeIndex == 0, vp)
vp writeNat PickleFormat.MajorVersion
vp writeNat PickleFormat.MinorVersion
vp writeNat 0
vp
}
def pickleMarkerLocal = {
createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex)
}
def pickleMarkerForeign = {
createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0)
}
/** Returns a ScalaSignature annotation if it must be added to this class, none otherwise.
* This annotation must be added to the class' annotations list when generating them.
*
* Depending on whether the returned option is defined, it adds to `jclass` one of:
* (a) the ScalaSig marker attribute
* (indicating that a scala-signature-annotation aka pickle is present in this class); or
* (b) the Scala marker attribute
* (indicating that a scala-signature-annotation aka pickle is to be found in another file).
*
*
* @param jclassName The class file that is being readied.
* @param sym The symbol for which the signature has been entered in the symData map.
* This is different than the symbol
* that is being generated in the case of a mirror class.
* @return An option that is:
* - defined and contains an AnnotationInfo of the ScalaSignature type,
* instantiated with the pickle signature for sym.
* - empty if the jclass/sym pair must not contain a pickle.
*
*/
def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = {
currentRun.symData get sym match {
case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) =>
val scalaAnnot = {
val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex))
AnnotationInfo(sigBytes.sigAnnot, Nil, List((nme.bytes, sigBytes)))
}
pickledBytes += pickle.writeIndex
currentRun.symData -= sym
currentRun.symData -= sym.companionSymbol
Some(scalaAnnot)
case _ =>
None
}
}
/**
* Quoting from JVMS 4.7.5 The Exceptions Attribute
* "The Exceptions attribute indicates which checked exceptions a method may throw.
* There may be at most one Exceptions attribute in each method_info structure."
*
* The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod()
* This method returns such list of internal names.
*/
def getExceptions(excs: List[AnnotationInfo]): List[String] =
for (ThrownException(exc) <- excs.distinct)
yield javaName(exc)
def getCurrentCUnit(): CompilationUnit
def getGenericSignature(sym: Symbol, owner: Symbol) = self.getGenericSignature(sym, owner, getCurrentCUnit())
def emitArgument(av: asm.AnnotationVisitor,
name: String,
arg: ClassfileAnnotArg) {
(arg: @unchecked) match {
case LiteralAnnotArg(const) =>
if(const.isNonUnitAnyVal) { av.visit(name, const.value) }
else {
const.tag match {
case StringTag =>
assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag
case ClazzTag => av.visit(name, javaType(const.typeValue))
case EnumTag =>
val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class.
val evalue = const.symbolValue.name.toString // value the actual enumeration value.
av.visitEnum(name, edesc, evalue)
}
}
case sb@ScalaSigBytes(bytes) =>
// see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files)
// also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure.
if (sb.fitsInOneString)
av.visit(name, strEncode(sb))
else {
val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) }
arrAnnotV.visitEnd()
}
// for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape.
case ArrayAnnotArg(args) =>
val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
for(arg <- args) { emitArgument(arrAnnotV, null, arg) }
arrAnnotV.visitEnd()
case NestedAnnotArg(annInfo) =>
val AnnotationInfo(typ, args, assocs) = annInfo
assert(args.isEmpty, args)
val desc = descriptor(typ) // the class descriptor of the nested annotation class
val nestedVisitor = av.visitAnnotation(name, desc)
emitAssocs(nestedVisitor, assocs)
}
}
def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) {
for ((name, value) <- assocs) {
emitArgument(av, name.toString(), value)
}
av.visitEnd()
}
def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) {
val annotationss = pannotss map (_ filter shouldEmitAnnotation)
if (annotationss forall (_.isEmpty)) return
for ((annots, idx) <- annotationss.zipWithIndex;
annot <- annots) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
emitAssocs(pannVisitor, assocs)
}
}
/** Adds a @remote annotation, actual use unknown.
*
* Invoked from genMethod() and addForwarder().
*/