Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix renaming of classes in presence of generic signatures and nested classes. #299

Merged
merged 3 commits into from May 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 69 additions & 25 deletions src/main/javassist/bytecode/SignatureAttribute.java
Expand Up @@ -19,6 +19,7 @@
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -110,41 +111,84 @@ static String renameClass(String desc, String oldname, String newname) {
}

static String renameClass(String desc, Map<String,String> map) {
if (map == null)
if (map == null || map.isEmpty())
return desc;

StringBuilder newdesc = new StringBuilder();
int head = 0;
int i = 0;
for (;;) {
int j = desc.indexOf('L', i);
final int j = desc.indexOf('L', i);
if (j < 0)
break;

int k = desc.indexOf(';', j);
if (k < 0)
break;

int l = desc.indexOf('<', j);
int classEndIndex;
char classEndChar;
if (l < 0 || k < l) {
classEndIndex = k;
classEndChar = ';';
} else {
classEndIndex = l;
classEndChar = '<';
StringBuilder nameBuf = new StringBuilder();
StringBuilder genericParamBuf = new StringBuilder();
final ArrayList<StringBuilder> nameBufs = new ArrayList<>();
final ArrayList<StringBuilder> genericParamBufs = new ArrayList<>();
int k = j;
char c;
try {
while ((c = desc.charAt(++k)) != ';') {
if (c == '<') {
genericParamBuf.append(c);
int level = 1;
while (level > 0) {
c = desc.charAt(++k);
genericParamBuf.append(c);
if (c == '<') ++level;
else if (c == '>') --level;
}
} else if (c == '.') {
nameBufs.add(nameBuf);
genericParamBufs.add(genericParamBuf);
nameBuf = new StringBuilder();
genericParamBuf = new StringBuilder();
} else {
nameBuf.append(c);
}
}
}
i = classEndIndex + 1;

String name = desc.substring(j + 1, classEndIndex);
String name2 = map.get(name);
if (name2 != null) {
newdesc.append(desc.substring(head, j));
newdesc.append('L');
newdesc.append(name2);
newdesc.append(classEndChar);
head = i;
catch (IndexOutOfBoundsException e) { break; }

nameBufs.add(nameBuf);
genericParamBufs.add(genericParamBuf);
i = k + 1;

String name = String.join("$", nameBufs.toArray(new StringBuilder[0]));
String newname = map.get(name);
if (newname != null) {
final String[] nameSplit = name.split("\\$");
final String[] newnameSplit = newname.split("\\$");
if (nameSplit.length == newnameSplit.length) {
final String[] newnames = new String[nameBufs.size()];
for (int start = 0, z = 0; z < nameBufs.size(); ++z) {
final int toAggregate = (int) nameBufs.get(z).chars().filter(ch -> ch == '$').count() + 1;
String s = String.join("$", Arrays.copyOfRange(newnameSplit, start, start + toAggregate));
start += toAggregate;
newnames[z] = s;
}


newdesc.append(desc.substring(head, j));
newdesc.append('L');
for (int z = 0; z < newnames.length; ++z) {
if (z > 0) {
newdesc.append('.');
}
newdesc.append(newnames[z]);
final String newgenericParam;
final StringBuilder genericParamBufCurrent = genericParamBufs.get(z);
if (genericParamBufCurrent.length() > 0) {
newgenericParam = "<" + renameClass(genericParamBufCurrent.substring(1, genericParamBufCurrent.length() - 1), map) + ">";
} else {
newgenericParam = genericParamBufCurrent.toString(); //empty string
}
newdesc.append(newgenericParam);
}
newdesc.append(c); //the final semicolon
head = i;
}
}
}

Expand Down
84 changes: 84 additions & 0 deletions src/test/javassist/bytecode/SignatureAttributeTest.java
@@ -0,0 +1,84 @@
package javassist.bytecode;

import static org.junit.Assert.assertEquals;

import java.util.HashMap;

public class SignatureAttributeTest {
void test1() {
final String signature = "TX;TY;La/b/C$D$E$J$K;"; //a sequence of three ReferenceTypeSignature
final HashMap<String, String> map = new HashMap<>();
map.put("a/b/C$D$E$J$K", "o/p/Q$R$S$T$U");
map.put("e/F$G$H$I", "v/W$X$Y$Z");
final String signatureRenamed = SignatureAttribute.renameClass(signature, map);
assertEquals("TX;TY;Lo/p/Q$R$S$T$U;", signatureRenamed);
}

void test2() {
final String signature = "La/b/C<TA;TB;>.D<Ljava/lang/Integer;>;"; //a ClassTypeSignature
final HashMap<String, String> map = new HashMap<>();
map.put("a/b/C$D", "o/p/Q$R");
map.put("java/lang/Integer", "java/lang/Long");
final String signatureRenamed = SignatureAttribute.renameClass(signature, map);
assertEquals("Lo/p/Q<TA;TB;>.R<Ljava/lang/Long;>;", signatureRenamed);
}

void test3() {
final String signature = "BJLB<TX;Lc/D$E;>.F<TY;>;TZ;"; //a sequence of four JavaTypeSignature
final HashMap<String, String> map = new HashMap<>();
map.put("B$F", "P$T");
map.put("c/D$E", "q/R$S");
final String signatureRenamed = SignatureAttribute.renameClass(signature, map);
assertEquals("BJLP<TX;Lq/R$S;>.T<TY;>;TZ;", signatureRenamed);
}

void test4() {
final String signature = "La/b/C<TX;>;[[Ld/E<+TY;-Ljava/lang/Object;*>;Z"; //a sequence of three JavaTypeSignature
final HashMap<String, String> map = new HashMap<>();
map.put("java/lang/Object", "java/util/Map");
map.put("d/E", "F");
final String signatureRenamed = SignatureAttribute.renameClass(signature, map);
assertEquals("La/b/C<TX;>;[[LF<+TY;-Ljava/util/Map;*>;Z", signatureRenamed);
}

void test5() {
final String signature = "La/b/C$D$E<TX;Le/F$G<TY;TZ;>.H$I<TU;TV;>;>.J$K;"; //a ClassTypeSignature
final HashMap<String, String> map = new HashMap<>();
map.put("a/b/C$D$E$J$K", "o/p/Q$R$S$T$U");
map.put("e/F$G$H$I", "v/W$X$Y$Z");
final String signatureRenamed = SignatureAttribute.renameClass(signature, map);
assertEquals("Lo/p/Q$R$S<TX;Lv/W$X<TY;TZ;>.Y$Z<TU;TV;>;>.T$U;", signatureRenamed);
}

void test6() {
final String signature = "<X:La/B$C<TY;>.D<TZ;>;:Le/F$G;>Lh/I$J;"; //a ClassSignature
final HashMap<String, String> map = new HashMap<>();
map.put("a/B$C$D", "o/P$Q$R");
map.put("e/F$G", "s/T$U");
map.put("h/I$J", "v/W$X");
final String signatureRenamed = SignatureAttribute.renameClass(signature, map);
assertEquals("<X:Lo/P$Q<TY;>.R<TZ;>;:Ls/T$U;>Lv/W$X;", signatureRenamed);
}

void test7() {
final String signature = "<A:La/B$C;:Ld/E<TX;>.F<TY;>;:TZ;B:Ljava/lang/Thread;>(LX;TA;LA;)V^Ljava/lang/Exception;"; //a MethodSignature
final HashMap<String, String> map = new HashMap<>();
map.put("A", "P");
map.put("a/B$C", "s/T$U");
map.put("d/E$F", "v/W$X");
map.put("X", "V");
map.put("java/lang/Exception", "java/lang/RuntimeException");
final String signatureRenamed = SignatureAttribute.renameClass(signature, map);
assertEquals("<A:Ls/T$U;:Lv/W<TX;>.X<TY;>;:TZ;B:Ljava/lang/Thread;>(LV;TA;LP;)V^Ljava/lang/RuntimeException;", signatureRenamed);
}

public static void main(String[] s) {
new SignatureAttributeTest().test1();
new SignatureAttributeTest().test2();
new SignatureAttributeTest().test3();
new SignatureAttributeTest().test4();
new SignatureAttributeTest().test5();
new SignatureAttributeTest().test6();
new SignatureAttributeTest().test7();
}
}