提交 f5fa3f45 编写于 作者: R rfield

8008688: Make MethodHandleInfo public

Summary: A major overhaul to MethodHandleInfo and method handles in general.
Reviewed-by: vlivanov, twisti
Contributed-by: john.r.rose@oracle.com
上级 78c16ca8
......@@ -124,7 +124,7 @@ import static sun.invoke.util.Wrapper.isWrapperType;
this.samMethodType = samMethodType;
this.implMethod = implMethod;
this.implInfo = new MethodHandleInfo(implMethod);
this.implInfo = caller.revealDirect(implMethod);
// @@@ Temporary work-around pending resolution of 8005119
this.implKind = (implInfo.getReferenceKind() == MethodHandleInfo.REF_invokeSpecial)
? MethodHandleInfo.REF_invokeVirtual
......
/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.invoke;
import java.security.*;
import java.lang.reflect.*;
import java.lang.invoke.MethodHandleNatives.Constants;
import java.lang.invoke.MethodHandles.Lookup;
import static java.lang.invoke.MethodHandleStatics.*;
/*
* Auxiliary to MethodHandleInfo, wants to nest in MethodHandleInfo but must be non-public.
*/
/*non-public*/
final
class InfoFromMemberName implements MethodHandleInfo {
private final MemberName member;
private final int referenceKind;
InfoFromMemberName(Lookup lookup, MemberName member, byte referenceKind) {
assert(member.isResolved() || member.isMethodHandleInvoke());
assert(member.referenceKindIsConsistentWith(referenceKind));
this.member = member;
this.referenceKind = referenceKind;
}
@Override
public Class<?> getDeclaringClass() {
return member.getDeclaringClass();
}
@Override
public String getName() {
return member.getName();
}
@Override
public MethodType getMethodType() {
return member.getMethodOrFieldType();
}
@Override
public int getModifiers() {
return member.getModifiers();
}
@Override
public int getReferenceKind() {
return referenceKind;
}
@Override
public String toString() {
return MethodHandleInfo.toString(getReferenceKind(), getDeclaringClass(), getName(), getMethodType());
}
@Override
public <T extends Member> T reflectAs(Class<T> expected, Lookup lookup) {
if (member.isMethodHandleInvoke() && !member.isVarargs()) {
// This member is an instance of a signature-polymorphic method, which cannot be reflected
// A method handle invoker can come in either of two forms:
// A generic placeholder (present in the source code, and varargs)
// and a signature-polymorphic instance (synthetic and not varargs).
// For more information see comments on {@link MethodHandleNatives#linkMethod}.
throw new IllegalArgumentException("cannot reflect signature polymorphic method");
}
Member mem = AccessController.doPrivileged(new PrivilegedAction<Member>() {
public Member run() {
try {
return reflectUnchecked();
} catch (ReflectiveOperationException ex) {
throw new IllegalArgumentException(ex);
}
}
});
try {
Class<?> defc = getDeclaringClass();
byte refKind = (byte) getReferenceKind();
lookup.checkAccess(refKind, defc, convertToMemberName(refKind, mem));
} catch (IllegalAccessException ex) {
throw new IllegalArgumentException(ex);
}
return expected.cast(mem);
}
private Member reflectUnchecked() throws ReflectiveOperationException {
byte refKind = (byte) getReferenceKind();
Class<?> defc = getDeclaringClass();
boolean isPublic = Modifier.isPublic(getModifiers());
if (MethodHandleNatives.refKindIsMethod(refKind)) {
if (isPublic)
return defc.getMethod(getName(), getMethodType().parameterArray());
else
return defc.getDeclaredMethod(getName(), getMethodType().parameterArray());
} else if (MethodHandleNatives.refKindIsConstructor(refKind)) {
if (isPublic)
return defc.getConstructor(getMethodType().parameterArray());
else
return defc.getDeclaredConstructor(getMethodType().parameterArray());
} else if (MethodHandleNatives.refKindIsField(refKind)) {
if (isPublic)
return defc.getField(getName());
else
return defc.getDeclaredField(getName());
} else {
throw new IllegalArgumentException("referenceKind="+refKind);
}
}
private static MemberName convertToMemberName(byte refKind, Member mem) throws IllegalAccessException {
if (mem instanceof Method) {
boolean wantSpecial = (refKind == REF_invokeSpecial);
return new MemberName((Method) mem, wantSpecial);
} else if (mem instanceof Constructor) {
return new MemberName((Constructor) mem);
} else if (mem instanceof Field) {
boolean isSetter = (refKind == REF_putField || refKind == REF_putStatic);
return new MemberName((Field) mem, isSetter);
}
throw new InternalError(mem.getClass().getName());
}
}
......@@ -87,6 +87,7 @@ class Invokers {
lform = invokeForm(mtype, true, MethodTypeForm.LF_EX_INVOKER);
invoker = SimpleMethodHandle.make(invokerType, lform);
}
invoker = invoker.withInternalMemberName(MemberName.makeMethodHandleInvoke("invokeExact", mtype));
assert(checkInvoker(invoker));
exactInvoker = invoker;
return invoker;
......@@ -110,6 +111,7 @@ class Invokers {
lform = invokeForm(mtype, true, MethodTypeForm.LF_GEN_INVOKER);
invoker = SimpleMethodHandle.make(invokerType, lform);
}
invoker = invoker.withInternalMemberName(MemberName.makeMethodHandleInvoke("invoke", mtype));
assert(checkInvoker(invoker));
generalInvoker = invoker;
return invoker;
......
......@@ -320,14 +320,18 @@ import java.util.Objects;
/** Utility method to query if this member is a method handle invocation (invoke or invokeExact). */
public boolean isMethodHandleInvoke() {
final int bits = Modifier.NATIVE | Modifier.FINAL;
final int bits = MH_INVOKE_MODS;
final int negs = Modifier.STATIC;
if (testFlags(bits | negs, bits) &&
clazz == MethodHandle.class) {
return name.equals("invoke") || name.equals("invokeExact");
return isMethodHandleInvokeName(name);
}
return false;
}
public static boolean isMethodHandleInvokeName(String name) {
return name.equals("invoke") || name.equals("invokeExact");
}
private static final int MH_INVOKE_MODS = Modifier.NATIVE | Modifier.FINAL | Modifier.PUBLIC;
/** Utility method to query the modifier flags of this member. */
public boolean isStatic() {
......@@ -482,12 +486,27 @@ import java.util.Objects;
m.getClass(); // NPE check
// fill in vmtarget, vmindex while we have m in hand:
MethodHandleNatives.init(this, m);
if (clazz == null) { // MHN.init failed
if (m.getDeclaringClass() == MethodHandle.class &&
isMethodHandleInvokeName(m.getName())) {
// The JVM did not reify this signature-polymorphic instance.
// Need a special case here.
// See comments on MethodHandleNatives.linkMethod.
MethodType type = MethodType.methodType(m.getReturnType(), m.getParameterTypes());
int flags = flagsMods(IS_METHOD, m.getModifiers(), REF_invokeVirtual);
init(MethodHandle.class, m.getName(), type, flags);
if (isMethodHandleInvoke())
return;
}
throw new LinkageError(m.toString());
}
assert(isResolved() && this.clazz != null);
this.name = m.getName();
if (this.type == null)
this.type = new Object[] { m.getReturnType(), m.getParameterTypes() };
if (wantSpecial) {
assert(!isAbstract()) : this;
if (isAbstract())
throw new AbstractMethodError(this.toString());
if (getReferenceKind() == REF_invokeVirtual)
changeReferenceKind(REF_invokeSpecial, REF_invokeVirtual);
else if (getReferenceKind() == REF_invokeInterface)
......@@ -562,6 +581,22 @@ import java.util.Objects;
initResolved(true);
}
/**
* Create a name for a signature-polymorphic invoker.
* This is a placeholder for a signature-polymorphic instance
* (of MH.invokeExact, etc.) that the JVM does not reify.
* See comments on {@link MethodHandleNatives#linkMethod}.
*/
static MemberName makeMethodHandleInvoke(String name, MethodType type) {
return makeMethodHandleInvoke(name, type, MH_INVOKE_MODS | SYNTHETIC);
}
static MemberName makeMethodHandleInvoke(String name, MethodType type, int mods) {
MemberName mem = new MemberName(MethodHandle.class, name, type, REF_invokeVirtual);
mem.flags |= mods; // it's not resolved, but add these modifiers anyway
assert(mem.isMethodHandleInvoke()) : mem;
return mem;
}
// bare-bones constructor; the JVM will fill it in
MemberName() { }
......
......@@ -1284,6 +1284,11 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
return null; // DMH returns DMH.member
}
/*non-public*/
MethodHandle withInternalMemberName(MemberName member) {
return MethodHandleImpl.makeWrappedMember(this, member);
}
/*non-public*/
boolean isInvokeSpecial() {
return false; // DMH.Special returns true
......@@ -1356,7 +1361,7 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
MethodHandle rebind() {
// Bind 'this' into a new invoker, of the known class BMH.
MethodType type2 = type();
LambdaForm form2 = reinvokerForm(type2.basicType());
LambdaForm form2 = reinvokerForm(this);
// form2 = lambda (bmh, arg*) { thismh = bmh[0]; invokeBasic(thismh, arg*) }
return BoundMethodHandle.bindSingle(type2, form2, this);
}
......@@ -1369,23 +1374,38 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
/** Create a LF which simply reinvokes a target of the given basic type.
* The target MH must override {@link #reinvokerTarget} to provide the target.
*/
static LambdaForm reinvokerForm(MethodType mtype) {
mtype = mtype.basicType();
static LambdaForm reinvokerForm(MethodHandle target) {
MethodType mtype = target.type().basicType();
LambdaForm reinvoker = mtype.form().cachedLambdaForm(MethodTypeForm.LF_REINVOKE);
if (reinvoker != null) return reinvoker;
MethodHandle MH_invokeBasic = MethodHandles.basicInvoker(mtype);
if (mtype.parameterSlotCount() >= MethodType.MAX_MH_ARITY)
return makeReinvokerForm(target.type(), target); // cannot cache this
reinvoker = makeReinvokerForm(mtype, null);
return mtype.form().setCachedLambdaForm(MethodTypeForm.LF_REINVOKE, reinvoker);
}
private static LambdaForm makeReinvokerForm(MethodType mtype, MethodHandle customTargetOrNull) {
boolean customized = (customTargetOrNull != null);
MethodHandle MH_invokeBasic = customized ? null : MethodHandles.basicInvoker(mtype);
final int THIS_BMH = 0;
final int ARG_BASE = 1;
final int ARG_LIMIT = ARG_BASE + mtype.parameterCount();
int nameCursor = ARG_LIMIT;
final int NEXT_MH = nameCursor++;
final int NEXT_MH = customized ? -1 : nameCursor++;
final int REINVOKE = nameCursor++;
LambdaForm.Name[] names = LambdaForm.arguments(nameCursor - ARG_LIMIT, mtype.invokerType());
names[NEXT_MH] = new LambdaForm.Name(NF_reinvokerTarget, names[THIS_BMH]);
Object[] targetArgs = Arrays.copyOfRange(names, THIS_BMH, ARG_LIMIT, Object[].class);
targetArgs[0] = names[NEXT_MH]; // overwrite this MH with next MH
names[REINVOKE] = new LambdaForm.Name(MH_invokeBasic, targetArgs);
return mtype.form().setCachedLambdaForm(MethodTypeForm.LF_REINVOKE, new LambdaForm("BMH.reinvoke", ARG_LIMIT, names));
Object[] targetArgs;
MethodHandle targetMH;
if (customized) {
targetArgs = Arrays.copyOfRange(names, ARG_BASE, ARG_LIMIT, Object[].class);
targetMH = customTargetOrNull;
} else {
names[NEXT_MH] = new LambdaForm.Name(NF_reinvokerTarget, names[THIS_BMH]);
targetArgs = Arrays.copyOfRange(names, THIS_BMH, ARG_LIMIT, Object[].class);
targetArgs[0] = names[NEXT_MH]; // overwrite this MH with next MH
targetMH = MethodHandles.basicInvoker(mtype);
}
names[REINVOKE] = new LambdaForm.Name(targetMH, targetArgs);
return new LambdaForm("BMH.reinvoke", ARG_LIMIT, names);
}
private static final LambdaForm.NamedFunction NF_reinvokerTarget;
......
......@@ -317,7 +317,7 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
private MethodHandle cache;
AsVarargsCollector(MethodHandle target, MethodType type, Class<?> arrayType) {
super(type, reinvokerForm(type));
super(type, reinvokerForm(target));
this.target = target;
this.arrayType = arrayType;
this.cache = target.asCollector(arrayType, 0);
......@@ -778,16 +778,27 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
}
static <T extends Throwable> Empty throwException(T t) throws T { throw t; }
static MethodHandle FAKE_METHOD_HANDLE_INVOKE;
static
MethodHandle fakeMethodHandleInvoke(MemberName method) {
MethodType type = method.getInvocationType();
assert(type.equals(MethodType.methodType(Object.class, Object[].class)));
MethodHandle mh = FAKE_METHOD_HANDLE_INVOKE;
static MethodHandle[] FAKE_METHOD_HANDLE_INVOKE = new MethodHandle[2];
static MethodHandle fakeMethodHandleInvoke(MemberName method) {
int idx;
assert(method.isMethodHandleInvoke());
switch (method.getName()) {
case "invoke": idx = 0; break;
case "invokeExact": idx = 1; break;
default: throw new InternalError(method.getName());
}
MethodHandle mh = FAKE_METHOD_HANDLE_INVOKE[idx];
if (mh != null) return mh;
mh = throwException(type.insertParameterTypes(0, UnsupportedOperationException.class));
MethodType type = MethodType.methodType(Object.class, UnsupportedOperationException.class,
MethodHandle.class, Object[].class);
mh = throwException(type);
mh = mh.bindTo(new UnsupportedOperationException("cannot reflectively invoke MethodHandle"));
FAKE_METHOD_HANDLE_INVOKE = mh;
if (!method.getInvocationType().equals(mh.type()))
throw new InternalError(method.toString());
mh = mh.withInternalMemberName(method);
mh = mh.asVarargsCollector(Object[].class);
assert(method.isVarargs());
FAKE_METHOD_HANDLE_INVOKE[idx] = mh;
return mh;
}
......@@ -821,7 +832,7 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
MethodHandle vamh = prepareForInvoker(mh);
// Cache the result of makeInjectedInvoker once per argument class.
MethodHandle bccInvoker = CV_makeInjectedInvoker.get(hostClass);
return restoreToType(bccInvoker.bindTo(vamh), mh.type());
return restoreToType(bccInvoker.bindTo(vamh), mh.type(), mh.internalMemberName());
}
private static MethodHandle makeInjectedInvoker(Class<?> hostClass) {
......@@ -876,8 +887,11 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
}
// Undo the adapter effect of prepareForInvoker:
private static MethodHandle restoreToType(MethodHandle vamh, MethodType type) {
return vamh.asCollector(Object[].class, type.parameterCount()).asType(type);
private static MethodHandle restoreToType(MethodHandle vamh, MethodType type, MemberName member) {
MethodHandle mh = vamh.asCollector(Object[].class, type.parameterCount());
mh = mh.asType(type);
mh = mh.withInternalMemberName(member);
return mh;
}
private static final MethodHandle MH_checkCallerClass;
......@@ -939,4 +953,41 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
}
}
}
/** This subclass allows a wrapped method handle to be re-associated with an arbitrary member name. */
static class WrappedMember extends MethodHandle {
private final MethodHandle target;
private final MemberName member;
private WrappedMember(MethodHandle target, MethodType type, MemberName member) {
super(type, reinvokerForm(target));
this.target = target;
this.member = member;
}
@Override
MethodHandle reinvokerTarget() {
return target;
}
@Override
MemberName internalMemberName() {
return member;
}
@Override
boolean isInvokeSpecial() {
return target.isInvokeSpecial();
}
@Override
MethodHandle viewAsType(MethodType newType) {
return new WrappedMember(target, newType, member);
}
}
static MethodHandle makeWrappedMember(MethodHandle target, MemberName member) {
if (member.equals(target.internalMemberName()))
return target;
return new WrappedMember(target, target.type(), member);
}
}
......@@ -24,80 +24,246 @@
*/
package java.lang.invoke;
import java.lang.reflect.*;
import java.util.*;
import java.lang.invoke.MethodHandleNatives.Constants;
import java.lang.invoke.MethodHandles.Lookup;
import static java.lang.invoke.MethodHandleStatics.*;
/**
* Cracking (reflecting) method handles back into their constituent symbolic parts.
* A symbolic reference obtained by cracking a method handle into its consitutent symbolic parts.
* To crack a direct method handle, call {@link Lookup#revealDirect Lookup.revealDirect}.
* <p>
* A <em>direct method handle</em> represents a method, constructor, or field without
* any intervening argument bindings or other transformations.
* The method, constructor, or field referred to by a direct method handle is called
* its <em>underlying member</em>.
* Direct method handles may be obtained in any of these ways:
* <ul>
* <li>By executing an {@code ldc} instruction on a {@code CONSTANT_MethodHandle} constant.
* (See the Java Virtual Machine Specification, sections 4.4.8 and 5.4.3.)
* <li>By calling one of the <a href="MethodHandles.Lookup.html#lookups">Lookup Factory Methods</a>,
* such as {@link Lookup#findVirtual Lookup.findVirtual},
* to resolve a symbolic reference into a method handle.
* A symbolic reference consists of a class, name string, and type.
* <li>By calling the factory method {@link Lookup#unreflect Lookup.unreflect}
* or {@link Lookup#unreflectSpecial Lookup.unreflectSpecial}
* to convert a {@link Method} into a method handle.
* <li>By calling the factory method {@link Lookup#unreflectConstructor Lookup.unreflectConstructor}
* to convert a {@link Constructor} into a method handle.
* <li>By calling the factory method {@link Lookup#unreflectGetter Lookup.unreflectGetter}
* or {@link Lookup#unreflectSetter Lookup.unreflectSetter}
* to convert a {@link Field} into a method handle.
* </ul>
* In all of these cases, it is possible to crack the resulting direct method handle
* to recover a symbolic reference for the underlying method, constructor, or field.
* Cracking must be done via a {@code Lookup} object equivalent to that which created
* the target method handle, or which has enough access permissions to recreate
* an equivalent method handle.
*
* <h1><a name="refkinds"></a>Reference kinds</h1>
* The <a href="MethodHandles.Lookup.html#lookups">Lookup Factory Methods</a>
* correspond to all major use cases for methods, constructors, and fields.
* These use cases may be distinguished using small integers as follows:
* <table border=1 cellpadding=5 summary="reference kinds">
* <tr><th>reference kind</th><th>descriptive name</th><th>scope</th><th>member</th><th>behavior</th></tr>
* <tr>
* <td>{@code 1}</td><td>{@code REF_getField}</td><td>{@code class}</td>
* <td>{@code FT f;}</td><td>{@code (T) this.f;}</td>
* </tr>
* <tr>
* <td>{@code 2}</td><td>{@code REF_getStatic}</td><td>{@code class} or {@code interface}</td>
* <td>{@code static}<br>{@code FT f;}</td><td>{@code (T) C.f;}</td>
* </tr>
* <tr>
* <td>{@code 3}</td><td>{@code REF_putField}</td><td>{@code class}</td>
* <td>{@code FT f;}</td><td>{@code this.f = x;}</td>
* </tr>
* <tr>
* <td>{@code 4}</td><td>{@code REF_putStatic}</td><td>{@code class}</td>
* <td>{@code static}<br>{@code FT f;}</td><td>{@code C.f = arg;}</td>
* </tr>
* <tr>
* <td>{@code 5}</td><td>{@code REF_invokeVirtual}</td><td>{@code class}</td>
* <td>{@code T m(A*);}</td><td>{@code (T) this.m(arg*);}</td>
* </tr>
* <tr>
* <td>{@code 6}</td><td>{@code REF_invokeStatic}</td><td>{@code class} or {@code interface}</td>
* <td>{@code static}<br>{@code T m(A*);}</td><td>{@code (T) C.m(arg*);}</td>
* </tr>
* <tr>
* <td>{@code 7}</td><td>{@code REF_invokeSpecial}</td><td>{@code class} or {@code interface}</td>
* <td>{@code T m(A*);}</td><td>{@code (T) super.m(arg*);}</td>
* </tr>
* <tr>
* <td>{@code 8}</td><td>{@code REF_newInvokeSpecial}</td><td>{@code class}</td>
* <td>{@code C(A*);}</td><td>{@code new C(arg*);}</td>
* </tr>
* <tr>
* <td>{@code 9}</td><td>{@code REF_invokeInterface}</td><td>{@code interface}</td>
* <td>{@code T m(A*);}</td><td>{@code (T) this.m(arg*);}</td>
* </tr>
* </table>
* @since 1.8
*/
final class MethodHandleInfo {
public static final int
REF_getField = Constants.REF_getField,
REF_getStatic = Constants.REF_getStatic,
REF_putField = Constants.REF_putField,
REF_putStatic = Constants.REF_putStatic,
REF_invokeVirtual = Constants.REF_invokeVirtual,
REF_invokeStatic = Constants.REF_invokeStatic,
REF_invokeSpecial = Constants.REF_invokeSpecial,
REF_newInvokeSpecial = Constants.REF_newInvokeSpecial,
REF_invokeInterface = Constants.REF_invokeInterface;
public
interface MethodHandleInfo {
/**
* A direct method handle reference kind,
* as defined in the <a href="MethodHandleInfo.html#refkinds">table above</a>.
*/
public static final int
REF_getField = Constants.REF_getField,
REF_getStatic = Constants.REF_getStatic,
REF_putField = Constants.REF_putField,
REF_putStatic = Constants.REF_putStatic,
REF_invokeVirtual = Constants.REF_invokeVirtual,
REF_invokeStatic = Constants.REF_invokeStatic,
REF_invokeSpecial = Constants.REF_invokeSpecial,
REF_newInvokeSpecial = Constants.REF_newInvokeSpecial,
REF_invokeInterface = Constants.REF_invokeInterface;
/**
* Returns the reference kind of the cracked method handle, which in turn
* determines whether the method handle's underlying member was a constructor, method, or field.
* See the <a href="MethodHandleInfo.html#refkinds">table above</a> for definitions.
* @return the integer code for the kind of reference used to access the underlying member
*/
public int getReferenceKind();
private final Class<?> declaringClass;
private final String name;
private final MethodType methodType;
private final int referenceKind;
/**
* Returns the class in which the cracked method handle's underlying member was defined.
* @return the declaring class of the underlying member
*/
public Class<?> getDeclaringClass();
public MethodHandleInfo(MethodHandle mh) {
MemberName mn = mh.internalMemberName();
if (mn == null) throw new IllegalArgumentException("not a direct method handle");
this.declaringClass = mn.getDeclaringClass();
this.name = mn.getName();
this.methodType = mn.getMethodOrFieldType();
byte refKind = mn.getReferenceKind();
if (refKind == REF_invokeSpecial && !mh.isInvokeSpecial())
// Devirtualized method invocation is usually formally virtual.
refKind = REF_invokeVirtual;
this.referenceKind = refKind;
}
/**
* Returns the name of the cracked method handle's underlying member.
* This is {@code "&lt;init&gt;"} if the underlying member was a constructor,
* else it is a simple method name or field name.
* @return the simple name of the underlying member
*/
public String getName();
public Class<?> getDeclaringClass() {
return declaringClass;
}
/**
* Returns the nominal type of the cracked symbolic reference, expressed as a method type.
* If the reference is to a constructor, the return type will be {@code void}.
* If it is to a non-static method, the method type will not mention the {@code this} parameter.
* If it is to a field and the requested access is to read the field,
* the method type will have no parameters and return the field type.
* If it is to a field and the requested access is to write the field,
* the method type will have one parameter of the field type and return {@code void}.
* <p>
* Note that original direct method handle may include a leading {@code this} parameter,
* or (in the case of a constructor) will replace the {@code void} return type
* with the constructed class.
* The nominal type does not include any {@code this} parameter,
* and (in the case of a constructor) will return {@code void}.
* @return the type of the underlying member, expressed as a method type
*/
public MethodType getMethodType();
public String getName() {
return name;
}
// Utility methods.
// NOTE: class/name/type and reference kind constitute a symbolic reference
// member and modifiers are an add-on, derived from Core Reflection (or the equivalent)
public MethodType getMethodType() {
return methodType;
}
/**
* Reflects the underlying member as a method, constructor, or field object.
* If the underlying member is public, it is reflected as if by
* {@code getMethod}, {@code getConstructor}, or {@code getField}.
* Otherwise, it is reflected as if by
* {@code getDeclaredMethod}, {@code getDeclaredConstructor}, or {@code getDeclaredField}.
* The underlying member must be accessible to the given lookup object.
* @param <T> the desired type of the result, either {@link Member} or a subtype
* @param expected a class object representing the desired result type {@code T}
* @param lookup the lookup object that created this MethodHandleInfo, or one with equivalent access privileges
* @return a reference to the method, constructor, or field object
* @exception ClassCastException if the member is not of the expected type
* @exception NullPointerException if either argument is {@code null}
* @exception IllegalArgumentException if the underlying member is not accessible to the given lookup object
*/
public <T extends Member> T reflectAs(Class<T> expected, Lookup lookup);
public int getModifiers() {
return -1; //TODO
}
/**
* Returns the access modifiers of the underlying member.
* @return the Java language modifiers for underlying member,
* or -1 if the member cannot be accessed
* @see Modifier
* @see reflectAs
*/
public int getModifiers();
public int getReferenceKind() {
return referenceKind;
}
/**
* Determines if the underlying member was a variable arity method or constructor.
* Such members are represented by method handles that are varargs collectors.
* @implSpec
* This produces a result equivalent to:
* <pre>{@code
* getReferenceKind() >= REF_invokeVirtual && Modifier.isTransient(getModifiers())
* }</pre>
*
*
* @return {@code true} if and only if the underlying member was declared with variable arity.
*/
// spelling derived from java.lang.reflect.Executable, not MethodHandle.isVarargsCollector
public default boolean isVarArgs() {
// fields are never varargs:
if (MethodHandleNatives.refKindIsField((byte) getReferenceKind()))
return false;
// not in the public API: Modifier.VARARGS
final int ACC_VARARGS = 0x00000080; // from JVMS 4.6 (Table 4.20)
assert(ACC_VARARGS == Modifier.TRANSIENT);
return Modifier.isTransient(getModifiers());
}
static String getReferenceKindString(int referenceKind) {
switch (referenceKind) {
case REF_getField: return "getfield";
case REF_getStatic: return "getstatic";
case REF_putField: return "putfield";
case REF_putStatic: return "putstatic";
case REF_invokeVirtual: return "invokevirtual";
case REF_invokeStatic: return "invokestatic";
case REF_invokeSpecial: return "invokespecial";
case REF_newInvokeSpecial: return "newinvokespecial";
case REF_invokeInterface: return "invokeinterface";
default: return "UNKNOWN_REFENCE_KIND" + "[" + referenceKind + "]";
}
/**
* Returns the descriptive name of the given reference kind,
* as defined in the <a href="MethodHandleInfo.html#refkinds">table above</a>.
* The conventional prefix "REF_" is omitted.
* @param referenceKind an integer code for a kind of reference used to access a class member
* @return a mixed-case string such as {@code "getField"}
* @exception IllegalArgumentException if the argument is not a valid
* <a href="MethodHandleInfo.html#refkinds">reference kind number</a>
*/
public static String referenceKindToString(int referenceKind) {
if (!MethodHandleNatives.refKindIsValid(referenceKind))
throw newIllegalArgumentException("invalid reference kind", referenceKind);
return MethodHandleNatives.refKindName((byte)referenceKind);
}
@Override
public String toString() {
return String.format("%s %s.%s:%s", getReferenceKindString(referenceKind),
declaringClass.getName(), name, methodType);
/**
* Returns a string representation for a {@code MethodHandleInfo},
* given the four parts of its symbolic reference.
* This is defined to be of the form {@code "RK C.N:MT"}, where {@code RK} is the
* {@linkplain #referenceKindToString reference kind string} for {@code kind},
* {@code C} is the {@linkplain java.lang.Class#getName name} of {@code defc}
* {@code N} is the {@code name}, and
* {@code MT} is the {@code type}.
* These four values may be obtained from the
* {@linkplain #getReferenceKind reference kind},
* {@linkplain #getDeclaringClass declaring class},
* {@linkplain #getName member name},
* and {@linkplain #getMethodType method type}
* of a {@code MethodHandleInfo} object.
*
* @implSpec
* This produces a result equivalent to:
* <pre>{@code
* String.format("%s %s.%s:%s", referenceKindToString(kind), defc.getName(), name, type)
* }</pre>
*
* @param kind the {@linkplain #getReferenceKind reference kind} part of the symbolic reference
* @param defc the {@linkplain #getDeclaringClass declaring class} part of the symbolic reference
* @param name the {@linkplain #getName member name} part of the symbolic reference
* @param type the {@linkplain #getMethodType method type} part of the symbolic reference
* @return a string of the form {@code "RK C.N:MT"}
* @exception IllegalArgumentException if the first argument is not a valid
* <a href="MethodHandleInfo.html#refkinds">reference kind number</a>
* @exception NullPointerException if any reference argument is {@code null}
*/
public static String toString(int kind, Class<?> defc, String name, MethodType type) {
Objects.requireNonNull(name); Objects.requireNonNull(type);
return String.format("%s %s.%s:%s", referenceKindToString(kind), defc.getName(), name, type);
}
}
......@@ -205,6 +205,9 @@ class MethodHandleNatives {
static boolean refKindIsMethod(byte refKind) {
return !refKindIsField(refKind) && (refKind != REF_newInvokeSpecial);
}
static boolean refKindIsConstructor(byte refKind) {
return (refKind == REF_newInvokeSpecial);
}
static boolean refKindHasReceiver(byte refKind) {
assert(refKindIsValid(refKind));
return (refKind & 1) != 0;
......@@ -313,7 +316,65 @@ class MethodHandleNatives {
* The method assumes the following arguments on the stack:
* 0: the method handle being invoked
* 1-N: the arguments to the method handle invocation
* N+1: an implicitly added type argument (the given MethodType)
* N+1: an optional, implicitly added argument (typically the given MethodType)
* <p>
* The nominal method at such a call site is an instance of
* a signature-polymorphic method (see @PolymorphicSignature).
* Such method instances are user-visible entities which are
* "split" from the generic placeholder method in {@code MethodHandle}.
* (Note that the placeholder method is not identical with any of
* its instances. If invoked reflectively, is guaranteed to throw an
* {@code UnsupportedOperationException}.)
* If the signature-polymorphic method instance is ever reified,
* it appears as a "copy" of the original placeholder
* (a native final member of {@code MethodHandle}) except
* that its type descriptor has shape required by the instance,
* and the method instance is <em>not</em> varargs.
* The method instance is also marked synthetic, since the
* method (by definition) does not appear in Java source code.
* <p>
* The JVM is allowed to reify this method as instance metadata.
* For example, {@code invokeBasic} is always reified.
* But the JVM may instead call {@code linkMethod}.
* If the result is an * ordered pair of a {@code (method, appendix)},
* the method gets all the arguments (0..N inclusive)
* plus the appendix (N+1), and uses the appendix to complete the call.
* In this way, one reusable method (called a "linker method")
* can perform the function of any number of polymorphic instance
* methods.
* <p>
* Linker methods are allowed to be weakly typed, with any or
* all references rewritten to {@code Object} and any primitives
* (except {@code long}/{@code float}/{@code double})
* rewritten to {@code int}.
* A linker method is trusted to return a strongly typed result,
* according to the specific method type descriptor of the
* signature-polymorphic instance it is emulating.
* This can involve (as necessary) a dynamic check using
* data extracted from the appendix argument.
* <p>
* The JVM does not inspect the appendix, other than to pass
* it verbatim to the linker method at every call.
* This means that the JDK runtime has wide latitude
* for choosing the shape of each linker method and its
* corresponding appendix.
* Linker methods should be generated from {@code LambdaForm}s
* so that they do not become visible on stack traces.
* <p>
* The {@code linkMethod} call is free to omit the appendix
* (returning null) and instead emulate the required function
* completely in the linker method.
* As a corner case, if N==255, no appendix is possible.
* In this case, the method returned must be custom-generated to
* to perform any needed type checking.
* <p>
* If the JVM does not reify a method at a call site, but instead
* calls {@code linkMethod}, the corresponding call represented
* in the bytecodes may mention a valid method which is not
* representable with a {@code MemberName}.
* Therefore, use cases for {@code linkMethod} tend to correspond to
* special cases in reflective code such as {@code findVirtual}
* or {@code revealDirect}.
*/
static MemberName linkMethod(Class<?> callerClass, int refKind,
Class<?> defc, String name, Object type,
......
......@@ -26,8 +26,6 @@
package java.lang.invoke;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
......@@ -54,6 +52,7 @@ import sun.security.util.SecurityConstants;
* </ul>
* <p>
* @author John Rose, JSR 292 EG
* @since 1.7
*/
public class MethodHandles {
......@@ -96,6 +95,38 @@ public class MethodHandles {
return Lookup.PUBLIC_LOOKUP;
}
/**
* Performs an unchecked "crack" of a direct method handle.
* The result is as if the user had obtained a lookup object capable enough
* to crack the target method handle, called
* {@link java.lang.invoke.MethodHandles.Lookup#revealDirect Lookup.revealDirect}
* on the target to obtain its symbolic reference, and then called
* {@link java.lang.invoke.MethodHandleInfo#reflectAs MethodHandleInfo.reflectAs}
* to resolve the symbolic reference to a member.
* <p>
* If there is a security manager, its {@code checkPermission} method
* is called with a {@code ReflectPermission("suppressAccessChecks")} permission.
* @param <T> the desired type of the result, either {@link Member} or a subtype
* @param target a direct method handle to crack into symbolic reference components
* @param expected a class object representing the desired result type {@code T}
* @return a reference to the method, constructor, or field object
* @exception SecurityException if the caller is not privileged to call {@code setAccessible}
* @exception NullPointerException if either argument is {@code null}
* @exception IllegalArgumentException if the target is not a direct method handle
* @exception ClassCastException if the member is not of the expected type
* @since 1.8
*/
public static <T extends Member> T
reflectAs(Class<T> expected, MethodHandle target) {
SecurityManager smgr = System.getSecurityManager();
if (smgr != null) smgr.checkPermission(ACCESS_PERMISSION);
Lookup lookup = Lookup.IMPL_LOOKUP; // use maximally privileged lookup
return lookup.revealDirect(target).reflectAs(expected, lookup);
}
// Copied from AccessibleObject, as used by Method.setAccessible, etc.:
static final private java.security.Permission ACCESS_PERMISSION =
new ReflectPermission("suppressAccessChecks");
/**
* A <em>lookup object</em> is a factory for creating method handles,
* when the creation requires access checking.
......@@ -647,6 +678,7 @@ public class MethodHandles {
return invoker(type);
if ("invokeExact".equals(name))
return exactInvoker(type);
assert(!MemberName.isMethodHandleInvokeName(name));
return null;
}
......@@ -892,6 +924,10 @@ return mh1;
* @throws NullPointerException if the argument is null
*/
public MethodHandle unreflect(Method m) throws IllegalAccessException {
if (m.getDeclaringClass() == MethodHandle.class) {
MethodHandle mh = unreflectForMH(m);
if (mh != null) return mh;
}
MemberName method = new MemberName(m);
byte refKind = method.getReferenceKind();
if (refKind == REF_invokeSpecial)
......@@ -900,6 +936,12 @@ return mh1;
Lookup lookup = m.isAccessible() ? IMPL_LOOKUP : this;
return lookup.getDirectMethod(refKind, method.getDeclaringClass(), method, findBoundCallerClass(method));
}
private MethodHandle unreflectForMH(Method m) {
// these names require special lookups because they throw UnsupportedOperationException
if (MemberName.isMethodHandleInvokeName(m.getName()))
return MethodHandleImpl.fakeMethodHandleInvoke(new MemberName(m));
return null;
}
/**
* Produces a method handle for a reflected method.
......@@ -1004,6 +1046,46 @@ return mh1;
return unreflectField(f, true);
}
/**
* Cracks a direct method handle created by this lookup object or a similar one.
* Security and access checks are performed to ensure that this lookup object
* is capable of reproducing the target method handle.
* This means that the cracking may fail if target is a direct method handle
* but was created by an unrelated lookup object.
* @param target a direct method handle to crack into symbolic reference components
* @return a symbolic reference which can be used to reconstruct this method handle from this lookup object
* @exception SecurityException if a security manager is present and it
* <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
* @throws IllegalArgumentException if the target is not a direct method handle or if access checking fails
* @exception NullPointerException if the target is {@code null}
* @since 1.8
*/
public MethodHandleInfo revealDirect(MethodHandle target) {
MemberName member = target.internalMemberName();
if (member == null || (!member.isResolved() && !member.isMethodHandleInvoke()))
throw newIllegalArgumentException("not a direct method handle");
Class<?> defc = member.getDeclaringClass();
byte refKind = member.getReferenceKind();
assert(MethodHandleNatives.refKindIsValid(refKind));
if (refKind == REF_invokeSpecial && !target.isInvokeSpecial())
// Devirtualized method invocation is usually formally virtual.
// To avoid creating extra MemberName objects for this common case,
// we encode this extra degree of freedom using MH.isInvokeSpecial.
refKind = REF_invokeVirtual;
if (refKind == REF_invokeVirtual && defc.isInterface())
// Symbolic reference is through interface but resolves to Object method (toString, etc.)
refKind = REF_invokeInterface;
// Check SM permissions and member access before cracking.
try {
checkSecurityManager(defc, member);
checkAccess(refKind, defc, member);
} catch (IllegalAccessException ex) {
throw new IllegalArgumentException(ex);
}
// Produce the handle to the results.
return new InfoFromMemberName(this, member, refKind);
}
/// Helper methods, all package-private.
MemberName resolveOrFail(byte refKind, Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
......@@ -1201,12 +1283,12 @@ return mh1;
private MethodHandle getDirectMethodCommon(byte refKind, Class<?> refc, MemberName method,
boolean doRestrict, Class<?> callerClass) throws IllegalAccessException {
checkMethod(refKind, refc, method);
if (method.isMethodHandleInvoke())
return fakeMethodHandleInvoke(method);
assert(!method.isMethodHandleInvoke());
Class<?> refcAsSuper;
if (refKind == REF_invokeSpecial &&
refc != lookupClass() &&
!refc.isInterface() &&
refc != (refcAsSuper = lookupClass().getSuperclass()) &&
refc.isAssignableFrom(lookupClass())) {
assert(!method.getName().equals("<init>")); // not this code path
......@@ -1234,9 +1316,6 @@ return mh1;
mh = restrictReceiver(method, mh, lookupClass());
return mh;
}
private MethodHandle fakeMethodHandleInvoke(MemberName method) {
return throwException(method.getReturnType(), UnsupportedOperationException.class);
}
private MethodHandle maybeBindCaller(MemberName method, MethodHandle mh,
Class<?> callerClass)
throws IllegalAccessException {
......
......@@ -225,7 +225,7 @@ public final class SerializedLambda implements Serializable {
@Override
public String toString() {
String implKind=MethodHandleInfo.getReferenceKindString(implMethodKind);
String implKind=MethodHandleInfo.referenceKindToString(implMethodKind);
return String.format("SerializedLambda[%s=%s, %s=%s.%s:%s, " +
"%s=%s %s.%s:%s, %s=%s, %s=%d]",
"capturingClass", capturingClass,
......
......@@ -35,20 +35,9 @@ import java.util.*;
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*;
import static java.lang.invoke.MethodHandleInfo.*;
public class Test7087570 {
// XXX may remove the following constant declarations when MethodHandleInfo is made public
private static final int
REF_getField = 1,
REF_getStatic = 2,
REF_putField = 3,
REF_putStatic = 4,
REF_invokeVirtual = 5,
REF_invokeStatic = 6,
REF_invokeSpecial = 7,
REF_newInvokeSpecial = 8,
REF_invokeInterface = 9,
REF_LIMIT = 10;
private static final TestMethodData[] TESTS = new TestMethodData[] {
// field accessors
......@@ -87,17 +76,17 @@ public class Test7087570 {
}
private static void doTest(MethodHandle mh, TestMethodData testMethod) {
Object mhi = newMethodHandleInfo(mh);
MethodHandleInfo mhi = LOOKUP.revealDirect(mh);
System.out.printf("%s.%s: %s, nominal refKind: %s, actual refKind: %s\n",
testMethod.clazz.getName(), testMethod.name, testMethod.methodType,
REF_KIND_NAMES[testMethod.referenceKind],
REF_KIND_NAMES[getReferenceKind(mhi)]);
assertEquals(testMethod.name, getName(mhi));
assertEquals(testMethod.methodType, getMethodType(mhi));
assertEquals(testMethod.declaringClass, getDeclaringClass(mhi));
referenceKindToString(testMethod.referenceKind),
referenceKindToString(mhi.getReferenceKind()));
assertEquals(testMethod.name, mhi.getName());
assertEquals(testMethod.methodType, mhi.getMethodType());
assertEquals(testMethod.declaringClass, mhi.getDeclaringClass());
assertEquals(testMethod.referenceKind == REF_invokeSpecial, isInvokeSpecial(mh));
assertRefKindEquals(testMethod.referenceKind, getReferenceKind(mhi));
assertRefKindEquals(testMethod.referenceKind, mhi.getReferenceKind());
}
private static void testWithLookup() throws Throwable {
......@@ -122,50 +111,8 @@ public class Test7087570 {
return methodType(void.class, clazz);
}
private static final String[] REF_KIND_NAMES = {
"MH::invokeBasic",
"REF_getField", "REF_getStatic", "REF_putField", "REF_putStatic",
"REF_invokeVirtual", "REF_invokeStatic", "REF_invokeSpecial",
"REF_newInvokeSpecial", "REF_invokeInterface"
};
private static final Lookup LOOKUP = lookup();
// XXX may remove the following reflective logic when MethodHandleInfo is made public
private static final MethodHandle MH_IS_INVOKESPECIAL;
private static final MethodHandle MHI_CONSTRUCTOR;
private static final MethodHandle MHI_GET_NAME;
private static final MethodHandle MHI_GET_METHOD_TYPE;
private static final MethodHandle MHI_GET_DECLARING_CLASS;
private static final MethodHandle MHI_GET_REFERENCE_KIND;
static {
try {
// This is white box testing. Use reflection to grab private implementation bits.
String magicName = "IMPL_LOOKUP";
Field magicLookup = MethodHandles.Lookup.class.getDeclaredField(magicName);
// This unit test will fail if a security manager is installed.
magicLookup.setAccessible(true);
// Forbidden fruit...
Lookup directInvokeLookup = (Lookup) magicLookup.get(null);
Class<?> mhiClass = Class.forName("java.lang.invoke.MethodHandleInfo", false, MethodHandle.class.getClassLoader());
MH_IS_INVOKESPECIAL = directInvokeLookup
.findVirtual(MethodHandle.class, "isInvokeSpecial", methodType(boolean.class));
MHI_CONSTRUCTOR = directInvokeLookup
.findConstructor(mhiClass, methodType(void.class, MethodHandle.class));
MHI_GET_NAME = directInvokeLookup
.findVirtual(mhiClass, "getName", methodType(String.class));
MHI_GET_METHOD_TYPE = directInvokeLookup
.findVirtual(mhiClass, "getMethodType", methodType(MethodType.class));
MHI_GET_DECLARING_CLASS = directInvokeLookup
.findVirtual(mhiClass, "getDeclaringClass", methodType(Class.class));
MHI_GET_REFERENCE_KIND = directInvokeLookup
.findVirtual(mhiClass, "getReferenceKind", methodType(int.class));
} catch (ReflectiveOperationException ex) {
throw new Error(ex);
}
}
private static class TestMethodData {
final Class<?> clazz;
final String name;
......@@ -208,7 +155,9 @@ public class Test7087570 {
return LOOKUP.findStatic(testMethod.clazz, testMethod.name, testMethod.methodType);
case REF_invokeSpecial:
Class<?> thisClass = LOOKUP.lookupClass();
return LOOKUP.findSpecial(testMethod.clazz, testMethod.name, testMethod.methodType, thisClass);
MethodHandle smh = LOOKUP.findSpecial(testMethod.clazz, testMethod.name, testMethod.methodType, thisClass);
noteInvokeSpecial(smh);
return smh;
case REF_newInvokeSpecial:
return LOOKUP.findConstructor(testMethod.clazz, testMethod.methodType);
default:
......@@ -238,7 +187,9 @@ public class Test7087570 {
case REF_invokeSpecial: {
Method m = testMethod.clazz.getDeclaredMethod(testMethod.name, testMethod.methodType.parameterArray());
Class<?> thisClass = LOOKUP.lookupClass();
return LOOKUP.unreflectSpecial(m, thisClass);
MethodHandle smh = LOOKUP.unreflectSpecial(m, thisClass);
noteInvokeSpecial(smh);
return smh;
}
case REF_newInvokeSpecial: {
Constructor c = testMethod.clazz.getDeclaredConstructor(testMethod.methodType.parameterArray());
......@@ -249,59 +200,20 @@ public class Test7087570 {
}
}
private static Object newMethodHandleInfo(MethodHandle mh) {
try {
return MHI_CONSTRUCTOR.invoke(mh);
} catch (Throwable ex) {
throw new Error(ex);
}
private static List<MethodHandle> specialMethodHandles = new ArrayList<>();
private static void noteInvokeSpecial(MethodHandle mh) {
specialMethodHandles.add(mh);
assert(isInvokeSpecial(mh));
}
private static boolean isInvokeSpecial(MethodHandle mh) {
try {
return (boolean) MH_IS_INVOKESPECIAL.invokeExact(mh);
} catch (Throwable ex) {
throw new Error(ex);
}
}
private static String getName(Object mhi) {
try {
return (String) MHI_GET_NAME.invoke(mhi);
} catch (Throwable ex) {
throw new Error(ex);
}
}
private static MethodType getMethodType(Object mhi) {
try {
return (MethodType) MHI_GET_METHOD_TYPE.invoke(mhi);
} catch (Throwable ex) {
throw new Error(ex);
}
}
private static Class<?> getDeclaringClass(Object mhi) {
try {
return (Class<?>) MHI_GET_DECLARING_CLASS.invoke(mhi);
} catch (Throwable ex) {
throw new Error(ex);
}
}
private static int getReferenceKind(Object mhi) {
try {
return (int) MHI_GET_REFERENCE_KIND.invoke(mhi);
} catch (Throwable ex) {
throw new Error(ex);
}
return specialMethodHandles.contains(mh);
}
private static void assertRefKindEquals(int expect, int observed) {
if (expect == observed) return;
String msg = "expected " + REF_KIND_NAMES[(int) expect] +
" but observed " + REF_KIND_NAMES[(int) observed];
String msg = "expected " + referenceKindToString(expect) +
" but observed " + referenceKindToString(observed);
System.out.println("FAILED: " + msg);
throw new AssertionError(msg);
}
......
此差异已折叠。
/*
* security policy used by the test process
* must allow file reads so that jtreg itself can run
*/
grant {
// standard test activation permissions
permission java.io.FilePermission "*", "read";
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册