Skip to content

Commit

Permalink
Merge pull request #10520 from hangshao0/v0.22.0
Browse files Browse the repository at this point in the history
(v0.22.0) Hidden class implementation 4
  • Loading branch information
gacholio committed Sep 3, 2020
2 parents 5bb6fc7 + 7900e25 commit 2d2f8b8
Show file tree
Hide file tree
Showing 27 changed files with 374 additions and 86 deletions.
48 changes: 40 additions & 8 deletions jcl/src/java.base/share/classes/java/lang/Class.java
Expand Up @@ -3428,8 +3428,12 @@ public String getSimpleName() {
String simpleName = baseType.getSimpleNameImpl();
String fullName = baseType.getName();
if (simpleName == null) {
Class<?> parent = baseType.getEnclosingObjectClass();
// either a base class, or anonymous class
/**
* It is a base class, an anonymous class, or a hidden class.
* Call getEnclosingClass() instead of getEnclosingObjectClass() to check getDeclaringClass() first. Hidden class test expects
* NoClassDefFoundError from getDeclaringClass().
*/
Class<?> parent = baseType.getEnclosingClass();
if (parent != null) {
simpleName = ""; //$NON-NLS-1$
} else {
Expand Down Expand Up @@ -3493,12 +3497,6 @@ else if (!fullName.endsWith(simpleName)) {
* @see #isLocalClass()
*/
public String getCanonicalName() {
/*[IF Java15]*/
if (isHidden()) {
/* Canonical name is always null for hidden classes. */
return null;
}
/*[ENDIF] Java15 */
int arrayCount = 0;
Class<?> baseType = this;
if (isArray()) {
Expand All @@ -3507,6 +3505,12 @@ public String getCanonicalName() {
arrayCount++;
}
}
/*[IF Java15]*/
if (baseType.isHidden()) {
/* Canonical name is always null for hidden classes. */
return null;
}
/*[ENDIF] Java15 */
if (baseType.getEnclosingObjectClass() != null) {
// local or anonymous class
return null;
Expand Down Expand Up @@ -4665,6 +4669,20 @@ public Class<?> componentType() {
* @return Optional with a nominal descriptor of Class instance
*/
public Optional<ClassDesc> describeConstable() {
/*[IF Java15]*/
Class<?> clazz = this;
if (isArray()) {
clazz = getComponentType();
while (clazz.isArray()) {
clazz = clazz.getComponentType();
}
}
if (clazz.isHidden()) {
/* It is always an empty Optional for hidden classes. */
return Optional.empty();
}
/*[ENDIF] Java15 */

ClassDesc classDescriptor = ClassDesc.ofDescriptor(this.descriptorString());
return Optional.of(classDescriptor);
}
Expand Down Expand Up @@ -4699,6 +4717,20 @@ public String descriptorString() {
}
}
String name = this.getName().replace('.', '/');
/*[IF Java15]*/
Class<?> clazz = this;
if (isArray()) {
clazz = getComponentType();
while (clazz.isArray()) {
clazz = clazz.getComponentType();
}
}
if (clazz.isHidden()) {
/* descriptor String of hidden class is something like: "Lpackage/ClassName.romaddress;". */
int index = name.lastIndexOf('/');
name = name.substring(0, index)+ '.' + name.substring(index + 1,name.length());
}
/*[ENDIF] Java15 */
if (this.isArray()) {
return name;
}
Expand Down
Expand Up @@ -86,6 +86,8 @@ static boolean refKindIsConstructor(byte kind) {
static Object classData(Class<?> c) {
return JLA.classData(c);
}

native static void checkClassBytes(byte[] bytes);
/*[ENDIF] Java15 */
}
/*[ENDIF] Java11 */
Expand Up @@ -51,6 +51,7 @@
import java.util.AbstractList;
import java.util.Iterator;
import java.util.ArrayList;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import jdk.internal.reflect.CallerSensitive;
import java.lang.invoke.VarHandle.AccessMode;
Expand All @@ -67,6 +68,8 @@
/*[ENDIF] Java12 */
import java.security.ProtectionDomain;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
/*[ELSE] Sidecar19-SE-OpenJ9
import java.lang.reflect.Module;
/*[ENDIF] Sidecar19-SE-OpenJ9*/
Expand Down Expand Up @@ -1765,7 +1768,13 @@ public MethodHandle unreflectSetter(Field field) throws IllegalAccessException {
/* https://github.com/eclipse/openj9/issues/3175
* Setters are allowed on instance final instance fields if they have been set accessible.
*/
if (Modifier.isFinal(modifiers) && (!field.isAccessible() || Modifier.isStatic(modifiers))) {
if (Modifier.isFinal(modifiers) &&
(!field.isAccessible() || Modifier.isStatic(modifiers)
/*[IF Java15]
|| declaringClass.isHidden()
/*[ENDIF] Java15 */
)
) {
/*[MSG "K05cf", "illegal setter on final field"]*/
throw new IllegalAccessException(Msg.getString("K05cf")); //$NON-NLS-1$
}
Expand Down Expand Up @@ -2368,21 +2377,58 @@ private ClassDefiner classDefiner(byte[] bytes, ClassOption... classOptions) thr
throw new IllegalAccessException();
}

int flags = 0;
for (ClassOption opt : classOptions) {
flags |= opt.toFlag();
}
MethodHandleNatives.checkClassBytes(bytes);
String targetClassName = getClassName(bytes);
return makeHiddenClassDefiner(targetClassName, bytes, flags);

}

private String getClassName(byte[] bytes) {
String targetClassName;
ClassReader cr;
try {
cr = new ClassReader(bytes);

int thisClassIndex = cr.readUnsignedShort(cr.header + 2);
char[] buffer = new char[cr.getMaxStringLength()];
Object thisClass = cr.readConst(thisClassIndex, buffer);
if (!(thisClass instanceof Type)) {
throw new ClassFormatError();
}
Type type = (Type)thisClass;
if (!type.getDescriptor().startsWith("L")) {
throw new ClassFormatError();
}
targetClassName = type.getClassName();
} catch (ArrayIndexOutOfBoundsException e) {
/*[MSG "K065Y2", "The class byte array is corrupted"]*/
throw new ClassFormatError(com.ibm.oti.util.Msg.getString("K065Y2")); //$NON-NLS-1$
} catch (RuntimeException e) {
ClassFormatError error = new ClassFormatError();
error.initCause(e);
throw error;
}

String targetClassName = cr.getClassName().replace('/', '.');
int flags = 0;
for (ClassOption opt : classOptions) {
flags |= opt.toFlag();
int accessFlag = cr.getAccess();
if (Opcodes.ACC_MODULE == (accessFlag & Opcodes.ACC_MODULE)) {
/* Must be a class or interface, ACC_MODULE cannot be there. */
throw new IllegalArgumentException();
}

return makeHiddenClassDefiner(targetClassName, bytes, flags);

String pkgName = lookupClass().getPackageName();
int index = targetClassName.lastIndexOf('.');
String pkgName1 = "";
if (0 < index) {
pkgName1 = targetClassName.substring(0, index);
}
if (!pkgName1.equals(pkgName)) {
throw new IllegalArgumentException();
}
return targetClassName;
}

ClassDefiner makeHiddenClassDefiner(String name, byte[] template) {
Expand Down
16 changes: 16 additions & 0 deletions jcl/src/java.base/share/classes/java/lang/invoke/MethodType.java
Expand Up @@ -1150,6 +1150,7 @@ static Class<?> unwrapPrimitive(Class<?> wrapperClass){
return wrapperClass;
}

/* The string returned by this method should be in sync with Class.descriptorString() */
static String getBytecodeStringName(Class<?> c){
if (c.isPrimitive()) {
if (c == int.class) {
Expand All @@ -1172,7 +1173,22 @@ static String getBytecodeStringName(Class<?> c){
return "S"; //$NON-NLS-1$
}
}
Class<?> clazz = c;
if (c.isArray()) {
clazz = c.getComponentType();
while (clazz.isArray()) {
clazz = clazz.getComponentType();
}
}
String name = c.getName().replace('.', '/');
/*[IF Java15]*/
if (clazz.isHidden()) {
/* keep the last "." before romaddress for hidden classes */
int index = name.lastIndexOf('/');
name = name.substring(0, index) + '.' + name.substring(index + 1,name.length());
}
/*[ENDIF] Java15 */

if (c.isArray()) {
return name;
}
Expand Down
3 changes: 2 additions & 1 deletion runtime/bcutil/BuildResult.hpp
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2001, 2019 IBM Corp. and others
* Copyright (c) 2001, 2020 IBM Corp. and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which accompanies this
Expand Down Expand Up @@ -48,6 +48,7 @@ enum BuildResult {
InvalidAnnotation = BCT_ERR_INVALID_ANNOTATION,
LineNumberTableDecompressFailed = BCT_ERR_LINE_NUMBER_TABLE_DECOMPRESS_FAILED,
InvalidBytecodeSize = BCT_ERR_INVALID_BYTECODE_SIZE,
InvalidClassType = BCT_ERR_INVALID_CLASS_TYPE,
};

#endif /* BUILDRESULT_HPP_ */
70 changes: 57 additions & 13 deletions runtime/bcutil/ClassFileOracle.cpp
Expand Up @@ -227,6 +227,11 @@ ClassFileOracle::ClassFileOracle(BufferManager *bufferManager, J9CfrClassFile *c
if (OK == _buildResult) {
walkAttributes();
}

if (_context->isClassHidden()) {
checkHiddenClass();
}

if (OK == _buildResult) {
walkInterfaces();
}
Expand Down Expand Up @@ -511,21 +516,28 @@ ClassFileOracle::walkAttributes()
}
#if JAVA_SPEC_VERSION >= 11
case CFR_ATTRIBUTE_NestMembers:
_nestMembers = (J9CfrAttributeNestMembers *)attrib;
_nestMembersCount = _nestMembers->numberOfClasses;
/* The classRefs are never resolved & therefore do not need to
* be kept in the constant pool.
*/
for (U_16 i = 0; i < _nestMembersCount; i++) {
U_16 classNameIndex = UTF8_INDEX_FROM_CLASS_INDEX(_classFile->constantPool, _nestMembers->classes[i]);
markConstantUTF8AsReferenced(classNameIndex);
/* ignore CFR_ATTRIBUTE_NestMembers for hidden classes, as the nest members never know the name of hidden classes */
if (!_context->isClassHidden()) {
_nestMembers = (J9CfrAttributeNestMembers *)attrib;
_nestMembersCount = _nestMembers->numberOfClasses;
/* The classRefs are never resolved & therefore do not need to
* be kept in the constant pool.
*/
for (U_16 i = 0; i < _nestMembersCount; i++) {
U_16 classNameIndex = UTF8_INDEX_FROM_CLASS_INDEX(_classFile->constantPool, _nestMembers->classes[i]);
markConstantUTF8AsReferenced(classNameIndex);
}
}
break;

case CFR_ATTRIBUTE_NestHost: {
U_16 hostClassIndex = ((J9CfrAttributeNestHost *)attrib)->hostClassIndex;
_nestHost = UTF8_INDEX_FROM_CLASS_INDEX(_classFile->constantPool, hostClassIndex);
markConstantUTF8AsReferenced(_nestHost);
/* Ignore CFR_ATTRIBUTE_NestHost for hidden classes, as the nest host of a hidden class is not decided by CFR_ATTRIBUTE_NestHost.
* The nesthost of a hidden class is its host class if ClassOption.NESTMATE is used or itself if ClassOption.NESTMATE is not used. */
if (!_context->isClassHidden()) {
U_16 hostClassIndex = ((J9CfrAttributeNestHost *)attrib)->hostClassIndex;
_nestHost = UTF8_INDEX_FROM_CLASS_INDEX(_classFile->constantPool, hostClassIndex);
markConstantUTF8AsReferenced(_nestHost);
}
break;
}
#endif /* JAVA_SPEC_VERSION >= 11 */
Expand All @@ -544,6 +556,38 @@ ClassFileOracle::walkAttributes()
}
}

void
ClassFileOracle::checkHiddenClass()
{
ROMClassVerbosePhase v(_context, ClassFileAttributesAnalysis);
/* Hidden Class cannot be a record or enum. */
U_16 superClassNameIndex = getSuperClassNameIndex();
bool isEnum = false;

/**
* See test case jdk/java/lang/invoke/defineHiddenClass/BasicTest.emptyHiddenClass().
* A normal Enum cannot be defined as a hidden class. But an empty enum class that does not
* define constants of its type can still be defined as a hidden class.
* So when setting isEnum, add a check for field count.
*/
if (0 != superClassNameIndex) {
isEnum = J9_ARE_ALL_BITS_SET(_classFile->accessFlags, CFR_ACC_ENUM) &&
J9UTF8_DATA_EQUALS(getUTF8Data(superClassNameIndex), getUTF8Length(superClassNameIndex), "java/lang/Enum", LITERAL_STRLEN("java/lang/Enum")) &&
(getFieldsCount() > 0);
}
if (_isRecord || isEnum) {
PORT_ACCESS_FROM_PORT(_context->portLibrary());
char msg[] = "Hidden Class cannot be a record or enum";
UDATA len = sizeof(msg);
char *error = (char *) j9mem_allocate_memory(len, J9MEM_CATEGORY_CLASSES);
if (NULL != error) {
strcpy(error, msg);
_context->recordCFRError((U_8*)error);
}
_buildResult = InvalidClassType;
}
}

void
ClassFileOracle::walkRecordComponents(J9CfrAttributeRecord *attrib)
{
Expand Down Expand Up @@ -1320,9 +1364,9 @@ ClassFileOracle::walkMethodCodeAttributeAttributes(U_16 methodIndex)
/* Verify that each LVTT entry has a matching local variable. Since there is no guaranteed order
* for code attributes, this check must be performed after all attributes are processed.
*
* According to the JVM spec: Each entry in the local_variable_type_table array ...
* According to the JVM spec: "Each entry in the local_variable_type_table array ...
* indicates the index into the local variable array of the current frame at which
* that local variable can be found.
* that local variable can be found."
*
* While multiple LocalVariableTypeTable attributes may exist according to the spec, upon observation
* it is the common case for 'javac' to generate only one attribute per method. To take advantage of this
Expand Down
1 change: 1 addition & 0 deletions runtime/bcutil/ClassFileOracle.hpp
Expand Up @@ -1084,6 +1084,7 @@ class RecordComponentIterator
void walkHeader();
void walkFields();
void walkAttributes();
void checkHiddenClass();
void walkInterfaces();
void walkMethods();
void walkRecordComponents(J9CfrAttributeRecord *attrib);
Expand Down
1 change: 1 addition & 0 deletions runtime/bcutil/ROMClassCreationContext.cpp
Expand Up @@ -157,6 +157,7 @@ ROMClassCreationContext::buildResultString(BuildResult result)
case UnknownAnnotation: return "UnknownAnnotation";
case ClassNameMismatch: return "ClassNameMismatch";
case InvalidAnnotation: return "InvalidAnnotation";
case InvalidClassType: return "InvalidClassType";
default: return "Unknown";
}
}
Expand Down
20 changes: 20 additions & 0 deletions runtime/bcutil/cfreader.c
Expand Up @@ -2731,6 +2731,11 @@ j9bcutil_readClassFileBytes(J9PortLibrary *portLib,
return -1;
}

if (J9_ARE_ANY_BITS_SET(flags, BCT_BasicCheckOnly)) {
Trc_BCU_j9bcutil_readClassFileBytes_Basic_Check_Exit(0);
return 0;
}

if (J9_ARE_ANY_BITS_SET(findClassFlags, J9_FINDCLASS_FLAG_UNSAFE)) {
flags |= BCT_Unsafe;
}
Expand Down Expand Up @@ -3631,3 +3636,18 @@ sortMethodIndex(J9CfrConstantPoolInfo* constantPool, J9CfrMethod *list, IDATA st
}
}
}

#if JAVA_SPEC_VERSION >= 15
I_32
checkClassBytes(J9VMThread *currentThread, U_8* classBytes, UDATA classBytesLength, U_8* segment, U_32 segmentLength)
{
I_32 rc = 0;
U_32 cfrFlags = BCT_JavaMaxMajorVersionShifted | BCT_AnyPreviewVersion | BCT_BasicCheckOnly;
PORT_ACCESS_FROM_VMC(currentThread);
if (NULL != classBytes) {
rc = j9bcutil_readClassFileBytes(PORTLIB, NULL, classBytes, classBytesLength, segment, segmentLength, cfrFlags, NULL, NULL, 0, 0);
}
return rc;
}
#endif /* JAVA_SPEC_VERSION >= 15 */

1 change: 1 addition & 0 deletions runtime/bcutil/defineclass.c
Expand Up @@ -895,6 +895,7 @@ createROMClassFromClassFile(J9VMThread *currentThread, J9LoadROMClassData *loadD
* Error messages are contents of vm->dynamicLoadBuffers->classFileError if anything is assigned
* otherwise just the classname.
*/
case BCT_ERR_INVALID_CLASS_TYPE:
case BCT_ERR_CLASS_NAME_MISMATCH:
exceptionNumber = J9VMCONSTANTPOOL_JAVALANGNOCLASSDEFFOUNDERROR;
/* FALLTHROUGH */
Expand Down

0 comments on commit 2d2f8b8

Please sign in to comment.