/
Implementation.java
1430 lines (1231 loc) · 62.2 KB
/
Implementation.java
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
package net.bytebuddy.implementation;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.method.ParameterList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.generic.GenericTypeDescription;
import net.bytebuddy.description.type.generic.GenericTypeList;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.ModifierResolver;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.dynamic.scaffold.TypeWriter;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.utility.RandomString;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.*;
/**
* An implementation is responsible for implementing methods of a dynamically created type as byte code. An
* implementation is applied in two stages:
* <ol>
* <li>The implementation is able to prepare an instrumented type by adding fields and/or helper methods that are
* required for the methods implemented by this implementation. Furthermore,
* {@link LoadedTypeInitializer}s and byte code for the type initializer can be registered for the instrumented
* type.</li>
* <li>Any implementation is required to supply a byte code appender that is responsible for providing the byte code
* to the instrumented methods that were delegated to this implementation. This byte code appender is also
* be responsible for providing implementations for the methods added in step <i>1</i>.</li>
* </ol>
* <p> </p>
* An implementation should provide meaningful implementations of both {@link java.lang.Object#equals(Object)}
* and {@link Object#hashCode()} if it wants to avoid to be used twice within the creation of a dynamic type. For two
* equal implementations only one will be applied on the creation of a dynamic type.
*/
public interface Implementation {
/**
* During the preparation phase of an implementation, implementations are eligible to adding fields or methods
* to the currently instrumented type. All methods that are added by this implementation are required to be
* implemented by the {@link net.bytebuddy.implementation.bytecode.ByteCodeAppender} that is emitted
* on the call to
* {@link Implementation#appender(Implementation.Target)}
* call. On this method call, loaded type initializers can also be added to the instrumented type.
*
* @param instrumentedType The instrumented type that is the basis of the ongoing instrumentation.
* @return The instrumented type with any applied changes, if any.
*/
InstrumentedType prepare(InstrumentedType instrumentedType);
/**
* Creates a byte code appender that determines the implementation of the instrumented type's methods.
*
* @param implementationTarget The target of the current implementation.
* @return A byte code appender for implementing methods delegated to this implementation. This byte code appender
* is also responsible for handling methods that were added by this implementation on the call to
* {@link Implementation#prepare(InstrumentedType)}.
*/
ByteCodeAppender appender(Target implementationTarget);
/**
* Represents an type-specific method invocation on the current instrumented type which is not legal from outside
* the type such as a super method or default method invocation. Legal instances of special method invocations must
* be equal to one another if they represent the same invocation target.
*/
interface SpecialMethodInvocation extends StackManipulation {
/**
* Returns the method that represents this special method invocation. This method can be different even for
* equal special method invocations, dependant on the method that was used to request such an invocation by the
* means of a {@link Implementation.Target}.
*
* @return The method description that describes this instances invocation target.
*/
MethodDescription getMethodDescription();
/**
* Returns the target type the represented method is invoked on.
*
* @return The type the represented method is invoked on.
*/
TypeDescription getTypeDescription();
/**
* An abstract base implementation of a valid special method invocation.
*/
abstract class AbstractBase implements SpecialMethodInvocation {
@Override
public boolean isValid() {
return true;
}
@Override
public int hashCode() {
return 31 * getMethodDescription().asToken().hashCode() + getTypeDescription().hashCode();
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof SpecialMethodInvocation)) return false;
SpecialMethodInvocation specialMethodInvocation = (SpecialMethodInvocation) other;
return getMethodDescription().asToken().equals(specialMethodInvocation.getMethodDescription().asToken())
&& getTypeDescription().equals(((SpecialMethodInvocation) other).getTypeDescription());
}
}
/**
* A canonical implementation of an illegal {@link Implementation.SpecialMethodInvocation}.
*/
enum Illegal implements SpecialMethodInvocation {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public boolean isValid() {
return false;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
throw new IllegalStateException("Cannot implement an undefined method");
}
@Override
public MethodDescription getMethodDescription() {
throw new IllegalStateException("An illegal special method invocation must not be applied");
}
@Override
public TypeDescription getTypeDescription() {
throw new IllegalStateException("An illegal special method invocation must not be applied");
}
@Override
public String toString() {
return "Implementation.SpecialMethodInvocation.Illegal." + name();
}
}
/**
* A canonical implementation of a {@link Implementation.SpecialMethodInvocation}.
*/
class Simple extends SpecialMethodInvocation.AbstractBase {
/**
* The method description that is represented by this legal special method invocation.
*/
private final MethodDescription methodDescription;
/**
* The type description that is represented by this legal special method invocation.
*/
private final TypeDescription typeDescription;
/**
* A stack manipulation representing the method's invocation on the type description.
*/
private final StackManipulation stackManipulation;
/**
* Creates a new legal special method invocation.
*
* @param methodDescription The method that represents the special method invocation.
* @param typeDescription The type on which the method should be invoked on by an {@code INVOKESPECIAL}
* invocation.
* @param stackManipulation The stack manipulation that represents this special method invocation.
*/
protected Simple(MethodDescription methodDescription, TypeDescription typeDescription, StackManipulation stackManipulation) {
this.methodDescription = methodDescription;
this.typeDescription = typeDescription;
this.stackManipulation = stackManipulation;
}
/**
* Creates a special method invocation for a given invocation target.
*
* @param methodDescription The method that represents the special method invocation.
* @param typeDescription The type on which the method should be invoked on by an {@code INVOKESPECIAL}
* invocation.
* @return A special method invocation representing a legal invocation if the method can be invoked
* specially on the target type or an illegal invocation if this is not possible.
*/
public static SpecialMethodInvocation of(MethodDescription methodDescription, TypeDescription typeDescription) {
StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).special(typeDescription);
return stackManipulation.isValid()
? new Simple(methodDescription, typeDescription, stackManipulation)
: SpecialMethodInvocation.Illegal.INSTANCE;
}
@Override
public MethodDescription getMethodDescription() {
return methodDescription;
}
@Override
public TypeDescription getTypeDescription() {
return typeDescription;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
return stackManipulation.apply(methodVisitor, implementationContext);
}
@Override
public String toString() {
return "Instrumentation.SpecialMethodInvocation.Simple{" +
"typeDescription=" + typeDescription +
", methodDescription=" + methodDescription +
'}';
}
}
}
/**
* The target of an implementation. Implementation targets must be immutable and can be queried without altering
* the implementation result. An implementation target provides information on the type that is to be created
* where it is the implementation's responsibility to cache expensive computations, especially such computations
* that require reflective look-up.
*/
interface Target {
/**
* Returns a description of the instrumented type.
*
* @return A description of the instrumented type.
*/
TypeDescription getTypeDescription();
/**
* Identifies the origin type of an implementation. The origin type describes the type that is subject to
* any form of enhancement. If a subclass of a given type is generated, the base type of this subclass
* describes the origin type. If a given type is redefined or rebased, the origin type is described by the
* instrumented type itself.
*
* @return The origin type of this implementation.
*/
TypeDescription getOriginType();
/**
* Creates a special method invocation for invoking the super method of the given method.
*
* @param methodToken A token of the method that is to be invoked as a super method.
* @return The corresponding special method invocation which might be illegal if the requested invocation is
* not legal.
*/
SpecialMethodInvocation invokeSuper(MethodDescription.Token methodToken);
/**
* Creates a special method invocation for invoking a default method.
*
* @param targetType The interface on which the default method is to be invoked.
* @param methodToken A token that uniquely describes the method to invoke.
* @return The corresponding special method invocation which might be illegal if the requested invocation is
* not legal.
*/
SpecialMethodInvocation invokeDefault(TypeDescription targetType, MethodDescription.Token methodToken);
/**
* Invokes a dominant method, i.e. if the method token can be invoked as a super method invocation, this invocation is considered dominant.
* Alternatively, a method invocation is attempted on an interface type as a default method invocation only if this invocation is not ambiguous
* for several interfaces.
*
* @param methodToken The method token representing the method to be invoked.
* @return A special method invocation for a method representing the method token.
*/
SpecialMethodInvocation invokeDominant(MethodDescription.Token methodToken);
/**
* A factory for creating an {@link Implementation.Target}.
*/
interface Factory {
/**
* Creates an implementation target.
*
* @param instrumentedType The instrumented type.
* @param methodGraph A method graph of the instrumented type.
* @return An implementation target for the instrumented type.
*/
Target make(TypeDescription instrumentedType, MethodGraph.Linked methodGraph);
}
/**
* An abstract base implementation for an {@link Implementation.Target}.
*/
abstract class AbstractBase implements Target {
/**
* The instrumented type.
*/
protected final TypeDescription instrumentedType;
/**
* The instrumented type's method graph.
*/
protected final MethodGraph.Linked methodGraph;
/**
* Creates a new implementation target.
*
* @param instrumentedType The instrumented type.
* @param methodGraph The instrumented type's method graph.
*/
protected AbstractBase(TypeDescription instrumentedType, MethodGraph.Linked methodGraph) {
this.instrumentedType = instrumentedType;
this.methodGraph = methodGraph;
}
@Override
public TypeDescription getTypeDescription() {
return instrumentedType;
}
@Override
public Implementation.SpecialMethodInvocation invokeDefault(TypeDescription targetType, MethodDescription.Token methodToken) {
MethodGraph.Node node = methodGraph.getInterfaceGraph(targetType).locate(methodToken);
return node.getSort().isUnique()
? SpecialMethodInvocation.Simple.of(node.getRepresentative(), targetType)
: Implementation.SpecialMethodInvocation.Illegal.INSTANCE;
}
@Override
public SpecialMethodInvocation invokeDominant(MethodDescription.Token methodToken) {
SpecialMethodInvocation specialMethodInvocation = invokeSuper(methodToken);
if (!specialMethodInvocation.isValid()) {
Iterator<TypeDescription> iterator = instrumentedType.getInterfaces().asRawTypes().iterator();
while (!specialMethodInvocation.isValid() && iterator.hasNext()) {
specialMethodInvocation = invokeDefault(iterator.next(), methodToken);
}
while (iterator.hasNext()) {
if (invokeDefault(iterator.next(), methodToken).isValid()) {
return SpecialMethodInvocation.Illegal.INSTANCE;
}
}
}
return specialMethodInvocation;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
AbstractBase that = (AbstractBase) other;
return instrumentedType.equals(that.instrumentedType)
&& methodGraph.equals(that.methodGraph);
}
@Override
public int hashCode() {
int result = instrumentedType.hashCode();
result = 31 * result + methodGraph.hashCode();
return result;
}
}
}
/**
* The context for an implementation application. An implementation context represents a mutable data structure
* where any registration is irrevocable. Calling methods on an implementation context should be considered equally
* sensitive as calling a {@link org.objectweb.asm.MethodVisitor}. As such, an implementation context and a
* {@link org.objectweb.asm.MethodVisitor} are complementary for creating an new Java type.
*/
interface Context {
/**
* Registers an auxiliary type as required for the current implementation. Registering a type will cause the
* creation of this type even if this type is not effectively used for the current implementation.
*
* @param auxiliaryType The auxiliary type that is required for the current implementation.
* @return A description of the registered auxiliary type.
*/
TypeDescription register(AuxiliaryType auxiliaryType);
/**
* Caches a single value by storing it in form of a {@code private}, {@code final} and {@code static} field.
* By caching values, expensive instance creations can be avoided and object identity can be preserved.
* The field is initiated in a generated class's static initializer.
*
* @param fieldValue A stack manipulation for creating the value that is to be cached in a {@code static} field.
* After executing the stack manipulation, exactly one value must be put onto the operand
* stack which is assignable to the given {@code fieldType}.
* @param fieldType The type of the field for storing the cached value. This field's type determines the value
* that is put onto the operand stack by this method's returned stack manipulation.
* @return A description of a field that was defined on the instrumented type which contains the given value.
*/
FieldDescription cache(StackManipulation fieldValue, TypeDescription fieldType);
/**
* Represents an extractable view of an {@link Implementation.Context} which
* allows the retrieval of any registered auxiliary type.
*/
interface ExtractableView extends Context {
/**
* A default modifier for a field that serves as a cache.
*/
int FIELD_CACHE_MODIFIER = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC;
/**
* Returns any {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType} that was registered
* with this {@link Implementation.Context}.
*
* @return A list of all manifested registered auxiliary types.
*/
List<DynamicType> getRegisteredAuxiliaryTypes();
/**
* Writes any information that was registered with an {@link Implementation.Context}
* to the provided class visitor. This contains any fields for value caching, any accessor method and it
* writes the type initializer. The type initializer must therefore never be written manually.
*
* @param classVisitor The class visitor to which the extractable view is to be written.
* @param methodPool A method pool which is queried for any user code to add to the type initializer.
* @param injectedCode Potential code that is to be injected into the type initializer.
*/
void drain(ClassVisitor classVisitor, TypeWriter.MethodPool methodPool, InjectedCode injectedCode);
/**
* When draining an implementation context, a type initializer might be written to the created class
* file. If any code must be explicitly invoked from within the type initializer, this can be achieved
* by providing a code injection by this instance. The injected code is added after the class is set up but
* before any user code is run from within the type initializer.
*/
interface InjectedCode {
/**
* Returns a byte code appender for appending the injected code.
*
* @return A byte code appender for appending the injected code.
*/
ByteCodeAppender getByteCodeAppender();
/**
* Checks if there is actually code defined to be injected.
*
* @return {@code true} if code is to be injected.
*/
boolean isDefined();
/**
* A canonical implementation of non-applicable injected code.
*/
enum None implements InjectedCode {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public ByteCodeAppender getByteCodeAppender() {
throw new IllegalStateException();
}
@Override
public boolean isDefined() {
return false;
}
@Override
public String toString() {
return "Implementation.Context.ExtractableView.InjectedCode.None." + name();
}
}
}
}
/**
* A default implementation of an {@link Implementation.Context.ExtractableView}
* which serves as its own {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType.MethodAccessorFactory}.
*/
class Default implements Implementation.Context.ExtractableView, AuxiliaryType.MethodAccessorFactory {
/**
* The name suffix to be appended to an accessor method.
*/
public static final String ACCESSOR_METHOD_SUFFIX = "accessor";
/**
* The name prefix to be prepended to a field storing a cached value.
*/
public static final String FIELD_CACHE_PREFIX = "cachedValue";
/**
* The instrumented type that this instance represents.
*/
private final TypeDescription instrumentedType;
/**
* The type initializer of the created instrumented type.
*/
private final InstrumentedType.TypeInitializer typeInitializer;
/**
* The class file version that the instrumented type is written in.
*/
private final ClassFileVersion classFileVersion;
/**
* The naming strategy for naming auxiliary types that are registered.
*/
private final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
/**
* A mapping of special method invocations to their accessor methods that each invoke their mapped invocation.
*/
private final Map<Implementation.SpecialMethodInvocation, MethodDescription.InDefinedShape> registeredAccessorMethods;
/**
* The registered getters.
*/
private final Map<FieldDescription, MethodDescription.InDefinedShape> registeredGetters;
/**
* The registered setters.
*/
private final Map<FieldDescription, MethodDescription.InDefinedShape> registeredSetters;
/**
* A map of accessor methods to a method pool entry that represents their implementation.
*/
private final List<TypeWriter.MethodPool.Record> accessorMethods;
/**
* A map of registered auxiliary types to their dynamic type representation.
*/
private final Map<AuxiliaryType, DynamicType> auxiliaryTypes;
/**
* A map of already registered field caches to their field representation.
*/
private final Map<FieldCacheEntry, FieldDescription> registeredFieldCacheEntries;
/**
* An instance for supporting the creation of random values.
*/
private final RandomString randomString;
/**
* Signals if this type extension delegate is still capable of registering field cache entries. Such entries
* must be explicitly initialized in the instrumented type's type initializer such that no entries can be
* registered after the type initializer was written.
*/
private boolean canRegisterFieldCache;
/**
* Creates a new implementation context.
*
* @param instrumentedType The description of the type that is currently subject of creation.
* @param auxiliaryTypeNamingStrategy The naming strategy for naming an auxiliary type.
* @param typeInitializer The type initializer of the created instrumented type.
* @param classFileVersion The class file version of the created class.
*/
public Default(TypeDescription instrumentedType,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
InstrumentedType.TypeInitializer typeInitializer,
ClassFileVersion classFileVersion) {
this.instrumentedType = instrumentedType;
this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
this.typeInitializer = typeInitializer;
this.classFileVersion = classFileVersion;
registeredAccessorMethods = new HashMap<Implementation.SpecialMethodInvocation, MethodDescription.InDefinedShape>();
registeredGetters = new HashMap<FieldDescription, MethodDescription.InDefinedShape>();
registeredSetters = new HashMap<FieldDescription, MethodDescription.InDefinedShape>();
accessorMethods = new LinkedList<TypeWriter.MethodPool.Record>();
auxiliaryTypes = new HashMap<AuxiliaryType, DynamicType>();
registeredFieldCacheEntries = new HashMap<FieldCacheEntry, FieldDescription>();
randomString = new RandomString();
canRegisterFieldCache = true;
}
@Override
public MethodDescription.InDefinedShape registerAccessorFor(Implementation.SpecialMethodInvocation specialMethodInvocation) {
MethodDescription.InDefinedShape accessorMethod = registeredAccessorMethods.get(specialMethodInvocation);
if (accessorMethod == null) {
accessorMethod = new AccessorMethod(instrumentedType, specialMethodInvocation.getMethodDescription(), randomString.nextString());
registeredAccessorMethods.put(specialMethodInvocation, accessorMethod);
accessorMethods.add(new AccessorMethodDelegation(accessorMethod, specialMethodInvocation));
}
return accessorMethod;
}
@Override
public MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription) {
MethodDescription.InDefinedShape accessorMethod = registeredGetters.get(fieldDescription);
if (accessorMethod == null) {
accessorMethod = new FieldGetter(instrumentedType, fieldDescription, randomString.nextString());
registeredGetters.put(fieldDescription, accessorMethod);
accessorMethods.add(new FieldGetterDelegation(accessorMethod, fieldDescription));
}
return accessorMethod;
}
@Override
public MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription) {
MethodDescription.InDefinedShape accessorMethod = registeredSetters.get(fieldDescription);
if (accessorMethod == null) {
accessorMethod = new FieldSetter(instrumentedType, fieldDescription, randomString.nextString());
registeredSetters.put(fieldDescription, accessorMethod);
accessorMethods.add(new FieldSetterDelegation(accessorMethod, fieldDescription));
}
return accessorMethod;
}
@Override
public TypeDescription register(AuxiliaryType auxiliaryType) {
DynamicType dynamicType = auxiliaryTypes.get(auxiliaryType);
if (dynamicType == null) {
dynamicType = auxiliaryType.make(auxiliaryTypeNamingStrategy.name(auxiliaryType, instrumentedType), classFileVersion, this);
auxiliaryTypes.put(auxiliaryType, dynamicType);
}
return dynamicType.getTypeDescription();
}
@Override
public List<DynamicType> getRegisteredAuxiliaryTypes() {
return new ArrayList<DynamicType>(auxiliaryTypes.values());
}
@Override
public FieldDescription cache(StackManipulation fieldValue, TypeDescription fieldType) {
FieldCacheEntry fieldCacheEntry = new FieldCacheEntry(fieldValue, fieldType);
FieldDescription fieldCache = registeredFieldCacheEntries.get(fieldCacheEntry);
if (fieldCache != null) {
return fieldCache;
}
validateFieldCacheAccessibility();
fieldCache = new CacheValueField(instrumentedType, fieldType, randomString.nextString());
registeredFieldCacheEntries.put(fieldCacheEntry, fieldCache);
return fieldCache;
}
/**
* Validates that the field cache is still accessible. Once the type initializer of a class is written, no
* additional field caches can be defined. See
* {@link Implementation.Context.Default#canRegisterFieldCache} for a more
* detailed explanation of this validation.
*/
private void validateFieldCacheAccessibility() {
if (!canRegisterFieldCache) {
throw new IllegalStateException("A field cache cannot be registered during or after the creation of a " +
"type initializer - instead, the field cache should be registered in the method that requires " +
"the cached value");
}
}
@Override
public void drain(ClassVisitor classVisitor, TypeWriter.MethodPool methodPool, InjectedCode injectedCode) {
canRegisterFieldCache = false;
InstrumentedType.TypeInitializer typeInitializer = this.typeInitializer;
for (Map.Entry<FieldCacheEntry, FieldDescription> entry : registeredFieldCacheEntries.entrySet()) {
classVisitor.visitField(entry.getValue().getModifiers(),
entry.getValue().getInternalName(),
entry.getValue().getDescriptor(),
entry.getValue().getGenericSignature(),
FieldDescription.NO_DEFAULT_VALUE).visitEnd();
typeInitializer = typeInitializer.expandWith(new ByteCodeAppender.Simple(entry.getKey().storeIn(entry.getValue())));
}
if (injectedCode.isDefined()) {
typeInitializer = typeInitializer.expandWith(injectedCode.getByteCodeAppender());
}
MethodDescription typeInitializerMethod = new MethodDescription.Latent.TypeInitializer(instrumentedType);
TypeWriter.MethodPool.Record initializerRecord = methodPool.target(typeInitializerMethod);
if (initializerRecord.getSort().isImplemented() && typeInitializer.isDefined()) {
initializerRecord = initializerRecord.prepend(typeInitializer);
} else if (typeInitializer.isDefined()) {
initializerRecord = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(typeInitializerMethod, typeInitializer.withReturn());
}
initializerRecord.apply(classVisitor, this);
for (TypeWriter.MethodPool.Record record : accessorMethods) {
record.apply(classVisitor, this);
}
}
@Override
public String toString() {
return "Implementation.Context.Default{" +
"instrumentedType=" + instrumentedType +
", typeInitializer=" + typeInitializer +
", classFileVersion=" + classFileVersion +
", auxiliaryTypeNamingStrategy=" + auxiliaryTypeNamingStrategy +
", registeredAccessorMethods=" + registeredAccessorMethods +
", registeredGetters=" + registeredGetters +
", registeredSetters=" + registeredSetters +
", accessorMethods=" + accessorMethods +
", auxiliaryTypes=" + auxiliaryTypes +
", registeredFieldCacheEntries=" + registeredFieldCacheEntries +
", randomString=" + randomString +
", canRegisterFieldCache=" + canRegisterFieldCache +
'}';
}
/**
* A description of a field that stores a cached value.
*/
protected static class CacheValueField extends FieldDescription.InDefinedShape.AbstractBase {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The type of the cache's field.
*/
private final TypeDescription fieldType;
/**
* The suffix to use for the cache field's name.
*/
private final String suffix;
/**
* Creates a new cache value field.
*
* @param instrumentedType The instrumented type.
* @param fieldType The type of the cache's field.
* @param suffix The suffix to use for the cache field's name.
*/
protected CacheValueField(TypeDescription instrumentedType, TypeDescription fieldType, String suffix) {
this.instrumentedType = instrumentedType;
this.fieldType = fieldType;
this.suffix = suffix;
}
@Override
public GenericTypeDescription getType() {
return fieldType;
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
public int getModifiers() {
return FIELD_CACHE_MODIFIER;
}
@Override
public String getName() {
return String.format("%s$%s", FIELD_CACHE_PREFIX, suffix);
}
}
/**
* A field cache entry for uniquely identifying a cached field. A cached field is described by the stack
* manipulation that loads the field's value onto the operand stack and the type of the field.
*/
protected static class FieldCacheEntry implements StackManipulation {
/**
* The field value that is represented by this field cache entry.
*/
private final StackManipulation fieldValue;
/**
* The field type that is represented by this field cache entry.
*/
private final TypeDescription fieldType;
/**
* Creates a new field cache entry.
*
* @param fieldValue The field value that is represented by this field cache entry.
* @param fieldType The field type that is represented by this field cache entry.
*/
protected FieldCacheEntry(StackManipulation fieldValue, TypeDescription fieldType) {
this.fieldValue = fieldValue;
this.fieldType = fieldType;
}
/**
* Returns a stack manipulation where the represented value is stored in the given field.
*
* @param fieldDescription A static field in which the value is to be stored.
* @return A stack manipulation that represents this storage.
*/
public StackManipulation storeIn(FieldDescription fieldDescription) {
return new Compound(this, FieldAccess.forField(fieldDescription).putter());
}
/**
* Returns the field type that is represented by this field cache entry.
*
* @return The field type that is represented by this field cache entry.
*/
public TypeDescription getFieldType() {
return fieldType;
}
@Override
public boolean isValid() {
return fieldValue.isValid();
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
return fieldValue.apply(methodVisitor, implementationContext);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& fieldType.equals(((FieldCacheEntry) other).fieldType)
&& fieldValue.equals(((FieldCacheEntry) other).fieldValue);
}
@Override
public int hashCode() {
return 31 * fieldValue.hashCode() + fieldType.hashCode();
}
@Override
public String toString() {
return "Implementation.Context.Default.FieldCacheEntry{" +
"fieldValue=" + fieldValue +
", fieldType=" + fieldType +
'}';
}
}
/**
* A description of an accessor method to access another method from outside the instrumented type.
*/
protected static class AccessorMethod extends MethodDescription.InDefinedShape.AbstractBase {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The method that is being accessed.
*/
private final MethodDescription methodDescription;
/**
* The suffix to append to the accessor method's name.
*/
private final String suffix;
/**
* Creates a new accessor method.
*
* @param instrumentedType The instrumented type.
* @param methodDescription The method that is being accessed.
* @param suffix The suffix to append to the accessor method's name.
*/
protected AccessorMethod(TypeDescription instrumentedType, MethodDescription methodDescription, String suffix) {
this.instrumentedType = instrumentedType;
this.methodDescription = methodDescription;
this.suffix = suffix;
}
@Override
public GenericTypeDescription getReturnType() {
return methodDescription.getReturnType().asRawType();
}
@Override
public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
return new ParameterList.Explicit.ForTypes(this, methodDescription.getParameters().asTypeList().asRawTypes());
}
@Override
public GenericTypeList getExceptionTypes() {
return methodDescription.getExceptionTypes().asRawTypes().asGenericTypes();
}
@Override
public Object getDefaultValue() {
return MethodDescription.NO_DEFAULT_VALUE;
}
@Override
public GenericTypeList getTypeVariables() {
return new GenericTypeList.Empty();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
public int getModifiers() {
return methodDescription.isStatic()
? ACCESSOR_METHOD_MODIFIER | Opcodes.ACC_STATIC
: ACCESSOR_METHOD_MODIFIER;
}
@Override
public String getInternalName() {
return String.format("%s$%s$%s", methodDescription.getInternalName(), ACCESSOR_METHOD_SUFFIX, suffix);
}
}
/**
* A description of a field getter method.
*/
protected static class FieldGetter extends MethodDescription.InDefinedShape.AbstractBase {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The field for which a getter is described.
*/
private final FieldDescription fieldDescription;
/**
* The name suffix for the field getter method.
*/
private final String suffix;
/**
* Creates a new field getter.
*
* @param instrumentedType The instrumented type.
* @param fieldDescription The field for which a getter is described.
* @param suffix The name suffix for the field getter method.
*/
protected FieldGetter(TypeDescription instrumentedType, FieldDescription fieldDescription, String suffix) {
this.instrumentedType = instrumentedType;
this.fieldDescription = fieldDescription;
this.suffix = suffix;
}
@Override
public GenericTypeDescription getReturnType() {
return fieldDescription.getType().asRawType();
}
@Override
public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
return new ParameterList.Empty();
}
@Override
public GenericTypeList getExceptionTypes() {
return new GenericTypeList.Empty();
}
@Override
public Object getDefaultValue() {
return MethodDescription.NO_DEFAULT_VALUE;
}
@Override
public GenericTypeList getTypeVariables() {
return new GenericTypeList.Empty();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
public int getModifiers() {
return fieldDescription.isStatic()
? ACCESSOR_METHOD_MODIFIER | Opcodes.ACC_STATIC
: ACCESSOR_METHOD_MODIFIER;