mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-20 23:36:18 +00:00
Merge
This commit is contained in:
commit
0589616e2f
@ -26,6 +26,8 @@
|
||||
package java.lang.invoke;
|
||||
|
||||
import sun.invoke.util.VerifyType;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -336,18 +338,20 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
|
||||
void setFieldL(C obj, V x) { unsafe.putObject(obj, offset, x); }
|
||||
// cast (V) is OK here, since we wrap convertArguments around the MH.
|
||||
|
||||
static Object staticBase(MemberName field) {
|
||||
static Object staticBase(final MemberName field) {
|
||||
if (!field.isStatic()) return null;
|
||||
Class c = field.getDeclaringClass();
|
||||
java.lang.reflect.Field f;
|
||||
try {
|
||||
// FIXME: Should not have to create 'f' to get this value.
|
||||
f = c.getDeclaredField(field.getName());
|
||||
// Note: Previous line might invalidly throw SecurityException (7042829)
|
||||
return unsafe.staticFieldBase(f);
|
||||
} catch (NoSuchFieldException ee) {
|
||||
throw uncaughtException(ee);
|
||||
}
|
||||
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
||||
public Object run() {
|
||||
try {
|
||||
Class c = field.getDeclaringClass();
|
||||
// FIXME: Should not have to create 'f' to get this value.
|
||||
java.lang.reflect.Field f = c.getDeclaredField(field.getName());
|
||||
return unsafe.staticFieldBase(f);
|
||||
} catch (NoSuchFieldException ee) {
|
||||
throw uncaughtException(ee);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int getStaticI() { return unsafe.getInt(base, offset); }
|
||||
|
||||
@ -359,6 +359,12 @@ class MethodHandleNatives {
|
||||
required = Object[].class; // should have been an array
|
||||
code = 192; // checkcast
|
||||
break;
|
||||
case 191: // athrow
|
||||
// JVM is asking us to wrap an exception which happened during resolving
|
||||
if (required == BootstrapMethodError.class) {
|
||||
throw new BootstrapMethodError((Throwable) actual);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// disregard the identity of the actual object, if it is not a class:
|
||||
if (message == null) {
|
||||
@ -389,18 +395,7 @@ class MethodHandleNatives {
|
||||
Class<?> defc, String name, Object type) {
|
||||
try {
|
||||
Lookup lookup = IMPL_LOOKUP.in(callerClass);
|
||||
switch (refKind) {
|
||||
case REF_getField: return lookup.findGetter( defc, name, (Class<?>) type );
|
||||
case REF_getStatic: return lookup.findStaticGetter( defc, name, (Class<?>) type );
|
||||
case REF_putField: return lookup.findSetter( defc, name, (Class<?>) type );
|
||||
case REF_putStatic: return lookup.findStaticSetter( defc, name, (Class<?>) type );
|
||||
case REF_invokeVirtual: return lookup.findVirtual( defc, name, (MethodType) type );
|
||||
case REF_invokeStatic: return lookup.findStatic( defc, name, (MethodType) type );
|
||||
case REF_invokeSpecial: return lookup.findSpecial( defc, name, (MethodType) type, callerClass );
|
||||
case REF_newInvokeSpecial: return lookup.findConstructor( defc, (MethodType) type );
|
||||
case REF_invokeInterface: return lookup.findVirtual( defc, name, (MethodType) type );
|
||||
}
|
||||
throw new InternalError("bad MethodHandle constant "+name+" : "+type);
|
||||
return lookup.linkMethodHandleConstant(refKind, defc, name, type);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
Error err = new IncompatibleClassChangeError();
|
||||
err.initCause(ex);
|
||||
|
||||
@ -25,6 +25,9 @@
|
||||
|
||||
package java.lang.invoke;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
/**
|
||||
* This class consists exclusively of static names internal to the
|
||||
* method handle implementation.
|
||||
@ -35,7 +38,17 @@ package java.lang.invoke;
|
||||
|
||||
private MethodHandleStatics() { } // do not instantiate
|
||||
|
||||
static final boolean DEBUG_METHOD_HANDLE_NAMES = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES");
|
||||
static final boolean DEBUG_METHOD_HANDLE_NAMES;
|
||||
static {
|
||||
final Object[] values = { false };
|
||||
AccessController.doPrivileged(new PrivilegedAction<Void>() {
|
||||
public Void run() {
|
||||
values[0] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
DEBUG_METHOD_HANDLE_NAMES = (Boolean) values[0];
|
||||
}
|
||||
|
||||
/*non-public*/ static String getNameString(MethodHandle target, MethodType type) {
|
||||
if (type == null)
|
||||
|
||||
@ -35,6 +35,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import sun.reflect.Reflection;
|
||||
import static java.lang.invoke.MethodHandleStatics.*;
|
||||
import static java.lang.invoke.MethodHandleNatives.Constants.*;
|
||||
|
||||
/**
|
||||
* This class consists exclusively of static methods that operate on or return
|
||||
@ -579,9 +580,18 @@ public class MethodHandles {
|
||||
MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
|
||||
MemberName method = resolveOrFail(refc, name, type, true);
|
||||
checkSecurityManager(refc, method); // stack walk magic: do not refactor
|
||||
return accessStatic(refc, method);
|
||||
}
|
||||
private
|
||||
MethodHandle accessStatic(Class<?> refc, MemberName method) throws IllegalAccessException {
|
||||
checkMethod(refc, method, true);
|
||||
return MethodHandleImpl.findMethod(method, false, lookupClassOrNull());
|
||||
}
|
||||
private
|
||||
MethodHandle resolveStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
|
||||
MemberName method = resolveOrFail(refc, name, type, true);
|
||||
return accessStatic(refc, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a method handle for a virtual method.
|
||||
@ -624,6 +634,13 @@ public class MethodHandles {
|
||||
public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
|
||||
MemberName method = resolveOrFail(refc, name, type, false);
|
||||
checkSecurityManager(refc, method); // stack walk magic: do not refactor
|
||||
return accessVirtual(refc, method);
|
||||
}
|
||||
private MethodHandle resolveVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
|
||||
MemberName method = resolveOrFail(refc, name, type, false);
|
||||
return accessVirtual(refc, method);
|
||||
}
|
||||
private MethodHandle accessVirtual(Class<?> refc, MemberName method) throws IllegalAccessException {
|
||||
checkMethod(refc, method, false);
|
||||
MethodHandle mh = MethodHandleImpl.findMethod(method, true, lookupClassOrNull());
|
||||
return restrictProtectedReceiver(method, mh);
|
||||
@ -658,13 +675,21 @@ public class MethodHandles {
|
||||
public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
|
||||
String name = "<init>";
|
||||
MemberName ctor = resolveOrFail(refc, name, type, false, false, lookupClassOrNull());
|
||||
assert(ctor.isConstructor());
|
||||
checkSecurityManager(refc, ctor); // stack walk magic: do not refactor
|
||||
return accessConstructor(refc, ctor);
|
||||
}
|
||||
private MethodHandle accessConstructor(Class<?> refc, MemberName ctor) throws IllegalAccessException {
|
||||
assert(ctor.isConstructor());
|
||||
checkAccess(refc, ctor);
|
||||
MethodHandle rawMH = MethodHandleImpl.findMethod(ctor, false, lookupClassOrNull());
|
||||
MethodHandle allocMH = MethodHandleImpl.makeAllocator(rawMH);
|
||||
return fixVarargs(allocMH, rawMH);
|
||||
}
|
||||
private MethodHandle resolveConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
|
||||
String name = "<init>";
|
||||
MemberName ctor = resolveOrFail(refc, name, type, false, false, lookupClassOrNull());
|
||||
return accessConstructor(refc, ctor);
|
||||
}
|
||||
|
||||
/** Return a version of MH which matches matchMH w.r.t. isVarargsCollector. */
|
||||
private static MethodHandle fixVarargs(MethodHandle mh, MethodHandle matchMH) {
|
||||
@ -720,10 +745,20 @@ public class MethodHandles {
|
||||
checkSpecialCaller(specialCaller);
|
||||
MemberName method = resolveOrFail(refc, name, type, false, false, specialCaller);
|
||||
checkSecurityManager(refc, method); // stack walk magic: do not refactor
|
||||
return accessSpecial(refc, method, specialCaller);
|
||||
}
|
||||
private MethodHandle accessSpecial(Class<?> refc, MemberName method,
|
||||
Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
|
||||
checkMethod(refc, method, false);
|
||||
MethodHandle mh = MethodHandleImpl.findMethod(method, false, specialCaller);
|
||||
return restrictReceiver(method, mh, specialCaller);
|
||||
}
|
||||
private MethodHandle resolveSpecial(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
|
||||
Class<?> specialCaller = lookupClass();
|
||||
checkSpecialCaller(specialCaller);
|
||||
MemberName method = resolveOrFail(refc, name, type, false, false, specialCaller);
|
||||
return accessSpecial(refc, method, specialCaller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a method handle giving read access to a non-static field.
|
||||
@ -747,6 +782,10 @@ public class MethodHandles {
|
||||
checkSecurityManager(refc, field); // stack walk magic: do not refactor
|
||||
return makeAccessor(refc, field, false, false, 0);
|
||||
}
|
||||
private MethodHandle resolveGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
|
||||
MemberName field = resolveOrFail(refc, name, type, false);
|
||||
return makeAccessor(refc, field, false, false, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a method handle giving write access to a non-static field.
|
||||
@ -770,6 +809,10 @@ public class MethodHandles {
|
||||
checkSecurityManager(refc, field); // stack walk magic: do not refactor
|
||||
return makeAccessor(refc, field, false, true, 0);
|
||||
}
|
||||
private MethodHandle resolveSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
|
||||
MemberName field = resolveOrFail(refc, name, type, false);
|
||||
return makeAccessor(refc, field, false, true, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a method handle giving read access to a static field.
|
||||
@ -792,6 +835,10 @@ public class MethodHandles {
|
||||
checkSecurityManager(refc, field); // stack walk magic: do not refactor
|
||||
return makeAccessor(refc, field, false, false, 1);
|
||||
}
|
||||
private MethodHandle resolveStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
|
||||
MemberName field = resolveOrFail(refc, name, type, true);
|
||||
return makeAccessor(refc, field, false, false, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a method handle giving write access to a static field.
|
||||
@ -814,6 +861,10 @@ public class MethodHandles {
|
||||
checkSecurityManager(refc, field); // stack walk magic: do not refactor
|
||||
return makeAccessor(refc, field, false, true, 1);
|
||||
}
|
||||
private MethodHandle resolveStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
|
||||
MemberName field = resolveOrFail(refc, name, type, true);
|
||||
return makeAccessor(refc, field, false, true, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces an early-bound method handle for a non-static method.
|
||||
@ -1179,6 +1230,25 @@ return mh1;
|
||||
MethodHandle mh = MethodHandleImpl.accessField(field, isSetter, lookupClassOrNull());
|
||||
return restrictProtectedReceiver(field, mh);
|
||||
}
|
||||
|
||||
/** Hook called from the JVM (via MethodHandleNatives) to link MH constants:
|
||||
*/
|
||||
/*non-public*/
|
||||
MethodHandle linkMethodHandleConstant(int refKind, Class<?> defc, String name, Object type) throws ReflectiveOperationException {
|
||||
switch (refKind) {
|
||||
case REF_getField: return resolveGetter( defc, name, (Class<?>) type );
|
||||
case REF_getStatic: return resolveStaticGetter( defc, name, (Class<?>) type );
|
||||
case REF_putField: return resolveSetter( defc, name, (Class<?>) type );
|
||||
case REF_putStatic: return resolveStaticSetter( defc, name, (Class<?>) type );
|
||||
case REF_invokeVirtual: return resolveVirtual( defc, name, (MethodType) type );
|
||||
case REF_invokeStatic: return resolveStatic( defc, name, (MethodType) type );
|
||||
case REF_invokeSpecial: return resolveSpecial( defc, name, (MethodType) type );
|
||||
case REF_newInvokeSpecial: return resolveConstructor( defc, (MethodType) type );
|
||||
case REF_invokeInterface: return resolveVirtual( defc, name, (MethodType) type );
|
||||
}
|
||||
// oops
|
||||
throw new ReflectiveOperationException("bad MethodHandle constant #"+refKind+" "+name+" : "+type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -59,14 +59,14 @@ package java.lang.invoke;
|
||||
MethodHandle MH_strcat = MethodHandles.lookup()
|
||||
.findVirtual(String.class, "concat", MethodType.methodType(String.class, String.class));
|
||||
SwitchPoint spt = new SwitchPoint();
|
||||
assert(spt.isValid());
|
||||
assert(!spt.hasBeenInvalidated());
|
||||
// the following steps may be repeated to re-use the same switch point:
|
||||
MethodHandle worker1 = MH_strcat;
|
||||
MethodHandle worker2 = MethodHandles.permuteArguments(MH_strcat, MH_strcat.type(), 1, 0);
|
||||
MethodHandle worker = spt.guardWithTest(worker1, worker2);
|
||||
assertEquals("method", (String) worker.invokeExact("met", "hod"));
|
||||
SwitchPoint.invalidateAll(new SwitchPoint[]{ spt });
|
||||
assert(!spt.isValid());
|
||||
assert(spt.hasBeenInvalidated());
|
||||
assertEquals("hodmet", (String) worker.invokeExact("met", "hod"));
|
||||
* </pre></blockquote>
|
||||
* <p style="font-size:smaller;">
|
||||
@ -126,16 +126,30 @@ public class SwitchPoint {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this switch point is still valid.
|
||||
* <p>
|
||||
* Determines if this switch point has been invalidated yet.
|
||||
*
|
||||
* <p style="font-size:smaller;">
|
||||
* <em>Discussion:</em>
|
||||
* Because of the one-way nature of invalidation, once a switch point begins
|
||||
* to return true for {@code hasBeenInvalidated},
|
||||
* it will always do so in the future.
|
||||
* On the other hand, a valid switch point visible to other threads may
|
||||
* invalidated at any moment, due to a request by another thread.
|
||||
* <p style="font-size:smaller;">
|
||||
* Since invalidation is a global and immediate operation,
|
||||
* this query must be sequenced with any
|
||||
* other threads that could invalidate this switch point.
|
||||
* It may therefore be expensive.
|
||||
* @return true if this switch point has never been invalidated
|
||||
* the execution of this query, on a valid switchpoint,
|
||||
* must be internally sequenced with any
|
||||
* other threads that could cause invalidation.
|
||||
* This query may therefore be expensive.
|
||||
* The recommended way to build a boolean-valued method handle
|
||||
* which queries the invalidation state of a switch point {@code s} is
|
||||
* to call {@code s.guardWithTest} on
|
||||
* {@link MethodHandles#constant constant} true and false method handles.
|
||||
*
|
||||
* @return true if this switch point has been invalidated
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return (mcs.getTarget() == K_true);
|
||||
public boolean hasBeenInvalidated() {
|
||||
return (mcs.getTarget() != K_true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -29,6 +29,8 @@ import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -38,7 +40,17 @@ import java.util.List;
|
||||
public class ValueConversions {
|
||||
private static final Class<?> THIS_CLASS = ValueConversions.class;
|
||||
// Do not adjust this except for special platforms:
|
||||
private static final int MAX_ARITY = Integer.getInteger(THIS_CLASS.getName()+".MAX_ARITY", 255);
|
||||
private static final int MAX_ARITY;
|
||||
static {
|
||||
final Object[] values = { 255 };
|
||||
AccessController.doPrivileged(new PrivilegedAction<Void>() {
|
||||
public Void run() {
|
||||
values[0] = Integer.getInteger(THIS_CLASS.getName()+".MAX_ARITY", 255);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
MAX_ARITY = (Integer) values[0];
|
||||
}
|
||||
|
||||
private static final Lookup IMPL_LOOKUP = MethodHandles.lookup();
|
||||
|
||||
|
||||
@ -30,6 +30,10 @@
|
||||
* --verify-specifier-count=3
|
||||
* --expand-properties --classpath ${test.classes}
|
||||
* --java test.java.lang.invoke.InvokeDynamicPrintArgs --check-output
|
||||
* @run main/othervm
|
||||
* indify.Indify
|
||||
* --expand-properties --classpath ${test.classes}
|
||||
* --java test.java.lang.invoke.InvokeDynamicPrintArgs --security-manager
|
||||
*/
|
||||
|
||||
package test.java.lang.invoke;
|
||||
@ -45,7 +49,8 @@ import static java.lang.invoke.MethodType.*;
|
||||
|
||||
public class InvokeDynamicPrintArgs {
|
||||
public static void main(String... av) throws Throwable {
|
||||
if (av.length > 0) openBuf(); // --check-output mode
|
||||
if (av.length > 0 && av[0].equals("--check-output")) openBuf();
|
||||
if (av.length > 0 && av[0].equals("--security-manager")) setSM();
|
||||
System.out.println("Printing some argument lists, starting with a empty one:");
|
||||
INDY_nothing().invokeExact(); // BSM specifier #0 = {bsm}
|
||||
INDY_bar().invokeExact("bar arg", 1); // BSM specifier #1 = {bsm2, Void.class, "void type"}
|
||||
@ -55,6 +60,43 @@ public class InvokeDynamicPrintArgs {
|
||||
// Hence, BSM specifier count should be 3. See "--verify-specifier-count=3" above.
|
||||
System.out.println("Done printing argument lists.");
|
||||
closeBuf();
|
||||
checkConstantRefs();
|
||||
}
|
||||
|
||||
private static void checkConstantRefs() throws Throwable {
|
||||
// check some constant references:
|
||||
assertEquals(MT_bsm(), MH_bsm().type());
|
||||
assertEquals(MT_bsm2(), MH_bsm2().type());
|
||||
try {
|
||||
assertEquals(MT_bsm(), non_MH_bsm().type());
|
||||
// if SM is installed, must throw before this point
|
||||
assertEquals(false, System.getSecurityManager() != null);
|
||||
} catch (SecurityException ex) {
|
||||
// if SM is installed, must throw to this point
|
||||
assertEquals(true, System.getSecurityManager() != null);
|
||||
}
|
||||
}
|
||||
private static void assertEquals(Object exp, Object act) {
|
||||
if (exp == act || (exp != null && exp.equals(act))) return;
|
||||
throw new AssertionError("not equal: "+exp+", "+act);
|
||||
}
|
||||
|
||||
private static void setSM() {
|
||||
// Test for severe security manager interactions (7050328).
|
||||
class SM extends SecurityManager {
|
||||
public void checkPackageAccess(String pkg) {
|
||||
if (pkg.startsWith("test."))
|
||||
throw new SecurityException("checkPackageAccess "+pkg);
|
||||
}
|
||||
public void checkMemberAccess(Class<?> clazz, int which) {
|
||||
if (clazz == InvokeDynamicPrintArgs.class)
|
||||
throw new SecurityException("checkMemberAccess "+clazz.getName()+" #"+which);
|
||||
}
|
||||
// allow these others:
|
||||
public void checkPermission(java.security.Permission perm) {
|
||||
}
|
||||
}
|
||||
System.setSecurityManager(new SM());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -130,6 +172,9 @@ public class InvokeDynamicPrintArgs {
|
||||
shouldNotCallThis();
|
||||
return lookup().findStatic(lookup().lookupClass(), "bsm", MT_bsm());
|
||||
}
|
||||
private static MethodHandle non_MH_bsm() throws ReflectiveOperationException {
|
||||
return lookup().findStatic(lookup().lookupClass(), "bsm", MT_bsm());
|
||||
}
|
||||
|
||||
/* Example of a constant call site with user-data.
|
||||
* In this case, the user data is exactly the BSM data.
|
||||
|
||||
@ -477,14 +477,14 @@ assertEquals("Wilma, dear?", (String) worker2.invokeExact());
|
||||
MethodHandle MH_strcat = MethodHandles.lookup()
|
||||
.findVirtual(String.class, "concat", MethodType.methodType(String.class, String.class));
|
||||
SwitchPoint spt = new SwitchPoint();
|
||||
assert(spt.isValid());
|
||||
assert(!spt.hasBeenInvalidated());
|
||||
// the following steps may be repeated to re-use the same switch point:
|
||||
MethodHandle worker1 = MH_strcat;
|
||||
MethodHandle worker2 = MethodHandles.permuteArguments(MH_strcat, MH_strcat.type(), 1, 0);
|
||||
MethodHandle worker = spt.guardWithTest(worker1, worker2);
|
||||
assertEquals("method", (String) worker.invokeExact("met", "hod"));
|
||||
SwitchPoint.invalidateAll(new SwitchPoint[]{ spt });
|
||||
assert(!spt.isValid());
|
||||
assert(spt.hasBeenInvalidated());
|
||||
assertEquals("hodmet", (String) worker.invokeExact("met", "hod"));
|
||||
{}
|
||||
}}
|
||||
|
||||
@ -25,6 +25,10 @@
|
||||
|
||||
/* @test
|
||||
* @summary unit tests for recursive method handles
|
||||
* @run junit/othervm -DRicochetTest.MAX_ARITY=50 test.java.lang.invoke.RicochetTest
|
||||
*/
|
||||
/*
|
||||
* @ignore The following test creates an unreasonable number of adapters in -Xcomp mode (7049122)
|
||||
* @run junit/othervm -DRicochetTest.MAX_ARITY=255 test.java.lang.invoke.RicochetTest
|
||||
*/
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user