54
54
import java .lang .reflect .Constructor ;
55
55
import java .lang .reflect .Field ;
56
56
import java .lang .reflect .InaccessibleObjectException ;
57
+ import java .lang .reflect .InvocationTargetException ;
57
58
import java .lang .reflect .Method ;
59
+ import java .lang .reflect .Modifier ;
58
60
import java .util .List ;
59
61
import java .util .Map ;
60
62
import java .util .concurrent .ConcurrentHashMap ;
@@ -93,6 +95,41 @@ public final class InternalMethodHandles {
93
95
findVirtualOrDie (
94
96
BiFunction .class , "apply" , methodType (Object .class , Object .class , Object .class ));
95
97
98
+ /**
99
+ * A handle for {@link Method#invoke}, useful when we can neither use a fastclass or direct method
100
+ * handle.
101
+ */
102
+ static final MethodHandle METHOD_INVOKE_HANDLE =
103
+ findVirtualOrDie (
104
+ Method .class , "invoke" , methodType (Object .class , Object .class , Object [].class ));
105
+
106
+ /**
107
+ * A handle for {@link Method#invoke}, useful when we can neither use a fastclass or direct method
108
+ * handle.
109
+ */
110
+ static final MethodHandle CONSTRUCTOR_NEWINSTANCE_HANDLE =
111
+ findVirtualOrDie (Constructor .class , "newInstance" , methodType (Object .class , Object [].class ));
112
+
113
+ /**
114
+ * The maximum arity of a method handle that can be bound.
115
+ *
116
+ * <ul>
117
+ * <li>There is a hard limit imposed by the JVM of 255 parameters.
118
+ * <li>MethodHandles add another parameter to represent the 'direct bound handle'
119
+ * <li>A MethodHandle that is invoked from Java by `invokeExact` adds another parameter to
120
+ * represent the receiver `MethodHandle` of `invokeExact`.
121
+ * </ul>
122
+ *
123
+ * <p>This leaves us with 255 - 2 = 253 parameters as the maximum arity of a method handle that
124
+ * can be bound.
125
+ *
126
+ * <p>TODO(b/366058184): For now we use this to decide when to back off and have callers
127
+ * workaround generally by generating a fastclass. We should just enforce this limit on users
128
+ * basically no one should really need to use so many parameters in a constructor/method and for
129
+ * guice injectable methods the workarounds are generally quite simple anyway, so we can just make
130
+ * this an error.
131
+ */
132
+ private static final int MAX_BINDABLE_ARITY = 253 ;
96
133
97
134
/**
98
135
* Returns a method handle for the given method, or null if the method is not accessible.
@@ -102,6 +139,14 @@ public final class InternalMethodHandles {
102
139
*/
103
140
@ Nullable
104
141
static MethodHandle unreflect (Method method ) {
142
+ // account for the `this` param if any
143
+ int paramSize = Modifier .isStatic (method .getModifiers ()) ? 1 : 0 ;
144
+ for (var param : method .getParameterTypes ()) {
145
+ paramSize += param .isPrimitive () ? Type .getType (param ).getSize () : 1 ;
146
+ }
147
+ if (paramSize > MAX_BINDABLE_ARITY ) {
148
+ return null ;
149
+ }
105
150
try {
106
151
method .setAccessible (true );
107
152
} catch (SecurityException | InaccessibleObjectException e ) {
@@ -123,6 +168,13 @@ static MethodHandle unreflect(Method method) {
123
168
*/
124
169
@ Nullable
125
170
static MethodHandle unreflectConstructor (Constructor <?> ctor ) {
171
+ int paramSize = 1 ; // constructors always have a `this` param
172
+ for (var param : ctor .getParameterTypes ()) {
173
+ paramSize += param .isPrimitive () ? Type .getType (param ).getSize () : 1 ;
174
+ }
175
+ if (paramSize > MAX_BINDABLE_ARITY ) {
176
+ return null ;
177
+ }
126
178
try {
127
179
ctor .setAccessible (true );
128
180
} catch (SecurityException | InaccessibleObjectException e ) {
@@ -683,6 +735,12 @@ static MethodHandle tryStartConstruction(
683
735
return MethodHandles .foldArguments (guard , tryStartConstruction );
684
736
}
685
737
738
+ private static final MethodHandle TRY_START_CONSTRUCTION_HANDLE =
739
+ findVirtualOrDie (
740
+ InternalContext .class ,
741
+ "tryStartConstruction" ,
742
+ methodType (Object .class , int .class , Dependency .class ));
743
+
686
744
/**
687
745
* Drops the return value from a method handle.
688
746
*
@@ -705,11 +763,24 @@ private static boolean isNull(Object result) {
705
763
return result == null ;
706
764
}
707
765
708
- private static final MethodHandle TRY_START_CONSTRUCTION_HANDLE =
709
- findVirtualOrDie (
710
- InternalContext .class ,
711
- "tryStartConstruction" ,
712
- methodType (Object .class , int .class , Dependency .class ));
766
+ static MethodHandle catchInvocationTargetExceptionAndRethrowCause (MethodHandle handle ) {
767
+ return MethodHandles .catchException (
768
+ handle ,
769
+ InvocationTargetException .class ,
770
+ CATCH_INVOCATION_TARGET_EXCEPTION_AND_RETHROW_CAUSE_HANDLE );
771
+ }
772
+
773
+ private static final MethodHandle CATCH_INVOCATION_TARGET_EXCEPTION_AND_RETHROW_CAUSE_HANDLE =
774
+ findStaticOrDie (
775
+ InternalMethodHandles .class ,
776
+ "doCatchInvocationTargetExceptionAndRethrowCause" ,
777
+ methodType (Object .class , InvocationTargetException .class ));
778
+
779
+ @ Keep
780
+ private static Object doCatchInvocationTargetExceptionAndRethrowCause (InvocationTargetException e )
781
+ throws Throwable {
782
+ throw e .getCause ();
783
+ }
713
784
714
785
/**
715
786
* Generates a provider instance that delegates to the given factory.
@@ -1264,18 +1335,45 @@ static MethodHandle buildImmutableSetFactory(Iterable<MethodHandle> elementHandl
1264
1335
var builder =
1265
1336
MethodHandles .insertArguments (
1266
1337
IMMUTABLE_SET_BUILDER_OF_SIZE_HANDLE , 0 , elementHandlesList .size ());
1267
- // Add any InternalContext arguments to the builder.
1268
- builder = MethodHandles .dropArguments (builder , 0 , InternalContext .class );
1269
- for (var handle : elementHandlesList ) {
1270
- // builder = builder.add(<handle>(ctx))
1271
- builder =
1272
- MethodHandles .foldArguments (
1273
- MethodHandles .filterArguments (IMMUTABLE_SET_BUILDER_ADD_HANDLE , 1 , handle ), builder );
1274
- }
1338
+
1339
+ builder = MethodHandles .foldArguments (doAddToImmutableSet (elementHandlesList ), builder );
1275
1340
return MethodHandles .filterReturnValue (builder , IMMUTABLE_SET_BUILDER_BUILD_HANDLE )
1276
1341
.asType (FACTORY_TYPE );
1277
1342
}
1278
1343
1344
+ /**
1345
+ * A helper to generate calls to add to the ImmutableSet builder.
1346
+ *
1347
+ * <p>Returns a handle of type (ImmutableSet.Builder, InternalContext) -> ImmutableSet.Builder
1348
+ */
1349
+ private static MethodHandle doAddToImmutableSet (ImmutableList <MethodHandle > elementHandlesList ) {
1350
+ // We don't want to 'fold' too deep as it can lead to a stack overflow. So we have a special
1351
+ // case for small lists.
1352
+ if (elementHandlesList .size () < 32 ) {
1353
+ var builder =
1354
+ MethodHandles .dropArguments (
1355
+ MethodHandles .identity (ImmutableSet .Builder .class ), 1 , InternalContext .class );
1356
+ for (var handle : elementHandlesList ) {
1357
+ // builder = builder.add(<handle>(ctx))
1358
+ builder =
1359
+ MethodHandles .foldArguments (
1360
+ MethodHandles .filterArguments (IMMUTABLE_SET_BUILDER_ADD_HANDLE , 1 , handle ),
1361
+ dropReturn (builder ));
1362
+ }
1363
+ return builder ;
1364
+ } else {
1365
+ // Otherwise we split and recurse, this basically ensures that none of the lambda forms are
1366
+ // too big and the 'folds' are not too deep.
1367
+ int half = elementHandlesList .size () / 2 ;
1368
+ var left = elementHandlesList .subList (0 , half );
1369
+ var right = elementHandlesList .subList (half , elementHandlesList .size ());
1370
+ // We are basically creating 2 methods in a chain that add to the builder
1371
+ // We do this by calling `doAddToImmutableSet` recursively.
1372
+ return MethodHandles .foldArguments (
1373
+ doAddToImmutableSet (right ), dropReturn (doAddToImmutableSet (left )));
1374
+ }
1375
+ }
1376
+
1279
1377
private static final MethodHandle IMMUTABLE_SET_OF_HANDLE =
1280
1378
InternalMethodHandles .findStaticOrDie (
1281
1379
ImmutableSet .class , "of" , methodType (ImmutableSet .class , Object .class ));
@@ -1327,31 +1425,51 @@ static <T> MethodHandle buildImmutableMapFactory(List<Map.Entry<T, MethodHandle>
1327
1425
var builder =
1328
1426
MethodHandles .insertArguments (
1329
1427
IMMUTABLE_MAP_BUILDER_WITH_EXPECTED_SIZE_HANDLE , 0 , entries .size ());
1330
- builder = MethodHandles .dropArguments (builder , 0 , InternalContext .class );
1331
- for (Map .Entry <T , MethodHandle > entry : entries ) {
1332
- // `put` has the signature `put(Builder, K, V)->Builder` (the first parameter is 'this').
1333
- // Insert the 'constant' key to get this signature:
1334
- // (Builder, V)->Builder
1335
- var put = MethodHandles .insertArguments (IMMUTABLE_MAP_BUILDER_PUT_HANDLE , 1 , entry .getKey ());
1336
- // Construct the value, by calling the factory method handle to supply the first argument (the
1337
- // value). Because the entry is a MethodHandle with signature `(InternalContext)->V` we need
1338
- // to cast the return type to `Object` to match the signature of `put`.
1339
- // (Builder, InternalContext)->Builder
1340
- put = MethodHandles .filterArguments (put , 1 , entry .getValue ());
1341
- // Fold works by invoking the 'builder' and then passing it to the first argument of the
1342
- // `put` method, and then passing the arguments to `builder` to put as well. Like this:
1343
- // Builder fold(InternalContext ctx) {
1344
- // bar builder = <builder-handle>(ctx);
1345
- // return <put-handle>(builder, ctx);
1346
- // }
1347
- // (InternalContext)->Builder
1348
- // Now, that has the same signture as builder, so assign back and loop.
1349
- builder = MethodHandles .foldArguments (put , builder );
1350
- }
1428
+ builder = MethodHandles .foldArguments (doPutEntries (entries ), builder );
1351
1429
return MethodHandles .filterReturnValue (builder , IMMUTABLE_MAP_BUILDER_BUILD_OR_THROW_HANDLE )
1352
1430
.asType (FACTORY_TYPE );
1353
1431
}
1354
1432
1433
+ /** Returns a handle of type (ImmutableMap.Builder, InternalContext) -> ImmutableMap.Builder */
1434
+ private static <T > MethodHandle doPutEntries (List <Map .Entry <T , MethodHandle >> entries ) {
1435
+ int size = entries .size ();
1436
+ checkArgument (size > 0 , "entries must not be empty" );
1437
+ if (size < 32 ) {
1438
+ MethodHandle builder = null ;
1439
+ for (Map .Entry <T , MethodHandle > entry : entries ) {
1440
+ // `put` has the signature `put(Builder, K, V)->Builder` (the first parameter is 'this').
1441
+ // Insert the 'constant' key to get this signature:
1442
+ // (Builder, V)->Builder
1443
+ var put =
1444
+ MethodHandles .insertArguments (IMMUTABLE_MAP_BUILDER_PUT_HANDLE , 1 , entry .getKey ());
1445
+ // Construct the value, by calling the factory method handle to supply the first argument
1446
+ // (the
1447
+ // value). Because the entry is a MethodHandle with signature `(InternalContext)->V` we
1448
+ // need
1449
+ // to cast the return type to `Object` to match the signature of `put`.
1450
+ // (Builder, InternalContext)->Builder
1451
+ put = MethodHandles .filterArguments (put , 1 , entry .getValue ());
1452
+ // Fold works by invoking the 'builder' and then passing it to the first argument of the
1453
+ // `put` method, and then passing the arguments to `builder` to put as well. Like this:
1454
+ // Builder fold(InternalContext ctx) {
1455
+ // bar builder = <builder-handle>(ctx);
1456
+ // return <put-handle>(builder, ctx);
1457
+ // }
1458
+ // (InternalContext)->Builder
1459
+ // Now, that has the same signture as builder, so assign back and loop.
1460
+ builder = builder == null ? put : MethodHandles .foldArguments (put , dropReturn (builder ));
1461
+ }
1462
+ return builder ;
1463
+ } else {
1464
+ // Otherwise we split and recurse, this basically ensures that none of the lambda forms are
1465
+ // too big and the 'folds' are not too deep.
1466
+ int half = size / 2 ;
1467
+ return MethodHandles .foldArguments (
1468
+ doPutEntries (entries .subList (half , size )),
1469
+ dropReturn (doPutEntries (entries .subList (0 , half ))));
1470
+ }
1471
+ }
1472
+
1355
1473
private static final MethodHandle IMMUTABLE_MAP_OF_HANDLE =
1356
1474
InternalMethodHandles .findStaticOrDie (
1357
1475
ImmutableMap .class , "of" , methodType (ImmutableMap .class , Object .class , Object .class ));
0 commit comments