Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions src/main/java/com/yworks/yguard/obf/classfile/AttrInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class AttrInfo implements ClassConstants
* The length of the attribute in bytes.
*/
protected int u4attrLength;
private byte info[];
protected byte[] info;

/**
* The Owner.
Expand Down Expand Up @@ -173,14 +173,17 @@ else if (ATTR_SourceDebugExtension.equals(attrName)) {
else if (ATTR_Record.equals(attrName)) {
ai = new RecordAttrInfo(cf, attrNameIndex, attrLength);
}
else if (ATTR_PermittedSubclasses.equals(attrName)) {
ai = new PermittedSubclassesAttrInfo(cf, attrNameIndex, attrLength);
}
else {
if ( attrLength > 0 ) {
Logger.getInstance().warning( "Unrecognized attribute '" + attrName + "' in " + Conversion.toJavaClass( cf.getName() ) );
}
ai = new AttrInfo( cf, attrNameIndex, attrLength );
else if (ATTR_PermittedSubclasses.equals(attrName)) {
ai = new PermittedSubclassesAttrInfo(cf, attrNameIndex, attrLength);
}
else if (ATTR_LoadableDescriptors.equals(attrName)) {
ai = new LoadableDescriptorsAttrInfo(cf, attrNameIndex, attrLength);
}
else {
if ( attrLength > 0 ) {
Logger.getInstance().warning( "Unrecognized attribute '" + attrName + "' in " + Conversion.toJavaClass( cf.getName() ) );
}
ai = new AttrInfo( cf, attrNameIndex, attrLength );
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public interface ClassConstants
/**
* The constant MAJOR_VERSION.
*/
public static final int MAJOR_VERSION = 0x41;
// Java 25 class file major version.
public static final int MAJOR_VERSION = 0x45;

/**
* The constant ACC_PUBLIC.
Expand All @@ -53,6 +54,11 @@ public interface ClassConstants
* The constant ACC_SUPER.
*/
public static final int ACC_SUPER = 0x0020;

/**
* The constant ACC_IDENTITY.
*/
public static final int ACC_IDENTITY = 0x0020;
/**
* The constant ACC_SYNCHRONIZED.
*/
Expand Down Expand Up @@ -326,6 +332,9 @@ public interface ClassConstants
// new in java 17
public static final String ATTR_PermittedSubclasses = "PermittedSubclasses";

// new in java 25 (preview)
public static final String ATTR_LoadableDescriptors = "LoadableDescriptors";

/**
* The constant REF_getField.
*/
Expand Down Expand Up @@ -396,6 +405,7 @@ public interface ClassConstants
ATTR_ModuleMainClass,
ATTR_NestHost,
ATTR_NestMembers,
ATTR_LoadableDescriptors,
};

/**
Expand Down Expand Up @@ -425,5 +435,6 @@ public interface ClassConstants
ATTR_NestMembers,
ATTR_PermittedSubclasses,
ATTR_Record,
ATTR_LoadableDescriptors,
};
}
24 changes: 24 additions & 0 deletions src/main/java/com/yworks/yguard/obf/classfile/ClassFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,8 @@ public void remap(NameMapper nm, boolean replaceClassNameStrings, PrintWriter lo
}
} else if (attrInfo instanceof SignatureAttrInfo){
remapSignature(nm, (SignatureAttrInfo) attrInfo);
} else if (attrInfo instanceof LoadableDescriptorsAttrInfo) {
remapLoadableDescriptors(nm, (LoadableDescriptorsAttrInfo) attrInfo);
} else if (attrInfo instanceof SourceFileAttrInfo) {
SourceFileAttrInfo source = (SourceFileAttrInfo) attrInfo;
CpInfo cpInfo = getCpEntry(source.getSourceFileIndex());
Expand Down Expand Up @@ -1731,6 +1733,28 @@ private void remapSignature(NameMapper nm, SignatureAttrInfo signature){
}
}
}

private void remapLoadableDescriptors(NameMapper nm, LoadableDescriptorsAttrInfo attr) {
if (!attr.isParsed()) {
return;
}

final int[] indices = attr.getDescriptorIndices();
for (int i = 0; i < indices.length; i++) {
final CpInfo cpInfo = getCpEntry(indices[i]);
if (!(cpInfo instanceof Utf8CpInfo)) {
continue;
}

final Utf8CpInfo utf = (Utf8CpInfo) cpInfo;
final String orig = utf.getString();
final String remap = nm.mapDescriptor(orig);
if (!orig.equals(remap)) {
final int remapIndex = constantPool.remapUtf8To(remap, indices[i]);
attr.setDescriptorIndex(i, remapIndex);
}
}
}

private int remapNT(Utf8CpInfo refUtf, String remapRef, Utf8CpInfo descUtf, String remapDesc, NameAndTypeCpInfo nameTypeInfo, int nameAndTypeIndex){
// If a remap is required, make a new N&T (increment ref count on 'name' and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.yworks.yguard.obf.classfile;

import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.IOException;

/**
* Representation of the (preview) LoadableDescriptors attribute.
*
* The attribute is treated as a list of constant pool indices to descriptor
* strings. If the attribute payload does not match the expected structure,
* the bytes are preserved unchanged.
*/
public class LoadableDescriptorsAttrInfo extends AttrInfo {
private int[] u2descriptorIndices = new int[0];
private boolean parsed;

LoadableDescriptorsAttrInfo(final ClassFile cf, final int attrNameIndex, final int attrLength) {
super(cf, attrNameIndex, attrLength);
}

protected String getAttrName() {
return ATTR_LoadableDescriptors;
}

int[] getDescriptorIndices() {
return u2descriptorIndices;
}

void setDescriptorIndex(final int i, final int cpIndex) {
u2descriptorIndices[i] = cpIndex;
}

boolean isParsed() {
return parsed;
}

protected void markUtf8RefsInInfo(final ConstantPool pool) {
if (!parsed) {
return;
}
for (int i = 0; i < u2descriptorIndices.length; i++) {
pool.incRefCount(u2descriptorIndices[i]);
}
}

protected void readInfo(final DataInput din) throws IOException {
// Always preserve original bytes.
info = new byte[u4attrLength];
din.readFully(info);

// Best-effort parse; if it doesn't match exactly, keep bytes only.
parsed = false;
u2descriptorIndices = new int[0];
try {
final DataInputStream dis = new DataInputStream(new ByteArrayInputStream(info));
final int count = dis.readUnsignedShort();
if (u4attrLength != 2 + 2 * count) {
return;
}
final int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = dis.readUnsignedShort();
}
if (dis.available() != 0) {
return;
}
u2descriptorIndices = indices;
parsed = true;
} catch (RuntimeException ignored) {
// Keep raw bytes.
}
}

public void writeInfo(final DataOutput dout) throws IOException {
if (!parsed) {
super.writeInfo(dout);
return;
}

dout.writeShort(u2descriptorIndices.length);
for (int i = 0; i < u2descriptorIndices.length; i++) {
dout.writeShort(u2descriptorIndices[i]);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.yworks.yguard.obf.classfile;

import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;

import static org.junit.Assert.*;

public class ClassFileVersionTest {
@Test
public void acceptsMajorVersion69() throws Exception {
final ClassFile cf = ClassFile.create(new DataInputStream(new ByteArrayInputStream(minimalClassWithLoadableDescriptors(69))));
assertEquals("pkg/Test", cf.getName());
}

@Test
public void remapsLoadableDescriptors() throws Exception {
final ClassFile cf = ClassFile.create(new DataInputStream(new ByteArrayInputStream(minimalClassWithLoadableDescriptors(69))));

final StringWriter sw = new StringWriter();
cf.remap(new TestNameMapper(), false, new PrintWriter(sw));

LoadableDescriptorsAttrInfo attr = null;
for (AttrInfo ai : cf.getAttributes()) {
if (ai instanceof LoadableDescriptorsAttrInfo) {
attr = (LoadableDescriptorsAttrInfo) ai;
break;
}
}
assertNotNull(attr);
assertTrue(attr.isParsed());

final int idx = attr.getDescriptorIndices()[0];
assertTrue(cf.getCpEntry(idx) instanceof Utf8CpInfo);
assertEquals("Lpkg/New;", ((Utf8CpInfo) cf.getCpEntry(idx)).getString());
}

private static byte[] minimalClassWithLoadableDescriptors(final int major) throws Exception {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream dout = new DataOutputStream(baos);

dout.writeInt(0xCAFEBABE);
dout.writeShort(0);
dout.writeShort(major);

// constant_pool_count
dout.writeShort(7);

// #1 Utf8 pkg/Test
writeUtf8(dout, "pkg/Test");

// #2 Class #1
dout.writeByte(7);
dout.writeShort(1);

// #3 Utf8 java/lang/Object
writeUtf8(dout, "java/lang/Object");

// #4 Class #3
dout.writeByte(7);
dout.writeShort(3);

// #5 Utf8 LoadableDescriptors
writeUtf8(dout, "LoadableDescriptors");

// #6 Utf8 Lpkg/Old;
writeUtf8(dout, "Lpkg/Old;");

// access_flags (public + super)
dout.writeShort(0x0021);
// this_class, super_class
dout.writeShort(2);
dout.writeShort(4);

// interfaces
dout.writeShort(0);

// fields
dout.writeShort(0);

// methods
dout.writeShort(0);

// attributes_count
dout.writeShort(1);

// attribute_info: LoadableDescriptors
dout.writeShort(5);
dout.writeInt(4);
dout.writeShort(1);
dout.writeShort(6);

dout.flush();
return baos.toByteArray();
}

private static void writeUtf8(final DataOutputStream dout, final String s) throws Exception {
dout.writeByte(1);
dout.writeUTF(s);
}

private static final class TestNameMapper implements NameMapper, ClassConstants {
public String[] getAttrsToKeep(final String className) {
return REQUIRED_ATTRS;
}

public String mapClass(final String className) {
return className;
}

public String mapMethod(final String className, final String methodName, final String descriptor) {
return methodName;
}

public String mapAnnotationField(final String className, final String annotationFieldName) {
return annotationFieldName;
}

public String mapField(final String className, final String fieldName) {
return fieldName;
}

public String mapDescriptor(final String descriptor) {
if ("Lpkg/Old;".equals(descriptor)) {
return "Lpkg/New;";
}
return descriptor;
}

public String mapSignature(final String signature) {
return signature;
}

public String mapSourceFile(final String className, final String sourceFile) {
return sourceFile;
}

public boolean mapLineNumberTable(
final String className,
final String methodName,
final String methodSignature,
final LineNumberTableAttrInfo info
) {
return true;
}

public String mapLocalVariable(
final String thisClassName,
final String methodName,
final String descriptor,
final String string
) {
return string;
}

public String mapPackage(final String packageName) {
return packageName;
}
}
}