Skip to content

Commit ddde46f

Browse files
committed
test: fix detection of duplicate annotation
1 parent d1fbb23 commit ddde46f

File tree

2 files changed

+111
-56
lines changed

2 files changed

+111
-56
lines changed

classport-commons/src/main/java/io/github/project/classport/commons/AnnotationConstantPool.java

Lines changed: 111 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ public AnnotationConstantPool(ClassportInfo annotationInfo) {
1616
}
1717

1818
public record ConstantPoolData(int entryCount, byte[] data) {}
19+
20+
private record ClassportInfoIndices(
21+
int classportInfoIndex,
22+
int groupIndex,
23+
int versionIndex,
24+
int idIndex,
25+
int sourceProjectIdIndex,
26+
int childIdsIndex,
27+
int artefactIndex,
28+
int isDirectDependencyIndex
29+
) {}
1930

2031
public ConstantPoolData getNewEntries() {
2132
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -110,6 +121,19 @@ public byte[] injectAnnotation(byte[] originalBytes, ConstantPoolData cpd) {
110121
int rvaUtf8Index = findUtf8InConstantPool(existingCpData, originalConstantPoolCount - 1, "RuntimeVisibleAnnotations");
111122
boolean rvaExists = rvaUtf8Index > 0;
112123

124+
// check if ClassportInfo is already in the constant pool (all in one iteration)
125+
// This is a hack because there can be a case where all these constants are present in the constant pool
126+
// but the ClassportInfo is not present
127+
ClassportInfoIndices existingClassportIndices = findClassportInfoIndices(existingCpData, originalConstantPoolCount - 1);
128+
129+
if (existingClassportIndices.classportInfoIndex > 0 && existingClassportIndices.groupIndex > 0 && existingClassportIndices.versionIndex > 0 &&
130+
existingClassportIndices.idIndex > 0 && existingClassportIndices.sourceProjectIdIndex > 0 && existingClassportIndices.childIdsIndex > 0 &&
131+
existingClassportIndices.artefactIndex > 0 && existingClassportIndices.isDirectDependencyIndex > 0) {
132+
// we don't want duplicate classport info entries
133+
// so we return the original bytes
134+
return originalBytes;
135+
}
136+
113137
int newConstantPoolCount = originalConstantPoolCount + cpd.entryCount();
114138
if (!rvaExists) {
115139
newConstantPoolCount++; // add one for "RuntimeVisibleAnnotations"
@@ -153,7 +177,7 @@ public byte[] injectAnnotation(byte[] originalBytes, ConstantPoolData cpd) {
153177
byte[] attrData = new byte[attrLen];
154178
originalBuffer.get(attrData);
155179

156-
if (i == rvaIndex && !hasClassportAnnotation(originalBytes, attrData)) {
180+
if (i == rvaIndex) {
157181
// Merge annotation
158182
dos.writeShort(nameIndex);
159183
byte[] merged = mergeAnnotation(attrData, indices);
@@ -275,61 +299,6 @@ private void writeStringElementValue(DataOutputStream out, int nameIndex, int va
275299
out.writeShort(valueIndex);
276300
}
277301

278-
private boolean hasClassportAnnotation(byte[] classBytes, byte[] rvaAttrData) {
279-
ByteBuffer buffer = ByteBuffer.wrap(rvaAttrData);
280-
int annotationCount = buffer.getShort() & 0xFFFF;
281-
282-
for (int i = 0; i < annotationCount; i++) {
283-
int typeIndex = buffer.getShort() & 0xFFFF;
284-
String descriptor = getCPString(classBytes, typeIndex);
285-
if (CLASSPORT_DESCRIPTOR.equals(descriptor)) {
286-
return true;
287-
}
288-
skipAnnotation(buffer);
289-
}
290-
return false;
291-
}
292-
293-
private void skipAnnotation(ByteBuffer buffer) {
294-
if (buffer.remaining() < 2) return;
295-
int numPairs = buffer.getShort() & 0xFFFF;
296-
for (int j = 0; j < numPairs; j++) {
297-
if (buffer.remaining() < 2) return;
298-
buffer.getShort(); // skip element_name_index
299-
skipElementValue(buffer); // skip element_value
300-
}
301-
}
302-
303-
private void skipElementValue(ByteBuffer buffer) {
304-
if (buffer.remaining() < 1) return;
305-
byte tag = buffer.get();
306-
switch (tag) {
307-
case 'B': case 'C': case 'D': case 'F': case 'I': case 'J': case 'S': case 'Z': case 's': // primitive or string
308-
if (buffer.remaining() < 2) return;
309-
buffer.getShort(); // const_value_index
310-
break;
311-
case 'e': // enum
312-
if (buffer.remaining() < 4) return;
313-
buffer.getShort(); // type_name_index
314-
buffer.getShort(); // const_name_index
315-
break;
316-
case 'c': // class
317-
if (buffer.remaining() < 2) return;
318-
buffer.getShort(); // class_info_index
319-
break;
320-
case '@': // annotation (recursive)
321-
skipAnnotation(buffer);
322-
break;
323-
case '[': // array
324-
if (buffer.remaining() < 2) return;
325-
int numValues = buffer.getShort() & 0xFFFF;
326-
for (int k = 0; k < numValues; k++) {
327-
skipElementValue(buffer);
328-
}
329-
break;
330-
}
331-
}
332-
333302
private int findRuntimeVisibleAnnotationsAttribute(byte[] classBytes, int attrStartPos, int attrCount) {
334303
ByteBuffer buffer = ByteBuffer.wrap(classBytes);
335304
buffer.position(attrStartPos);
@@ -418,6 +387,92 @@ private void copyMembers(ByteBuffer buffer, DataOutputStream out) throws IOExcep
418387
}
419388
}
420389

390+
private ClassportInfoIndices findClassportInfoIndices(byte[] cpData, int cpCount) {
391+
ByteBuffer buffer = ByteBuffer.wrap(cpData);
392+
int currentIndex = 1; // Constant pool indices start at 1
393+
394+
int classportInfoIndex = -1;
395+
int groupIndex = -1;
396+
int versionIndex = -1;
397+
int idIndex = -1;
398+
int sourceProjectIdIndex = -1;
399+
int childIdsIndex = -1;
400+
int artefactIndex = -1;
401+
int isDirectDependencyIndex = -1;
402+
403+
// Check if we've found all strings to potentially exit early
404+
boolean allFound = false;
405+
406+
for (int i = 0; i < cpCount && !allFound; i++) {
407+
int tag = buffer.get() & 0xFF;
408+
409+
switch (tag) {
410+
case 1: // UTF8
411+
int len = buffer.getShort() & 0xFFFF;
412+
byte[] utf8Bytes = new byte[len];
413+
buffer.get(utf8Bytes);
414+
String utf8String = new String(utf8Bytes, java.nio.charset.StandardCharsets.UTF_8);
415+
416+
// Check against all target strings
417+
if (utf8String.equals(CLASSPORT_DESCRIPTOR)) {
418+
classportInfoIndex = currentIndex;
419+
} else if (utf8String.equals("group")) {
420+
groupIndex = currentIndex;
421+
} else if (utf8String.equals("version")) {
422+
versionIndex = currentIndex;
423+
} else if (utf8String.equals("id")) {
424+
idIndex = currentIndex;
425+
} else if (utf8String.equals("sourceProjectId")) {
426+
sourceProjectIdIndex = currentIndex;
427+
} else if (utf8String.equals("childIds")) {
428+
childIdsIndex = currentIndex;
429+
} else if (utf8String.equals("artefact")) {
430+
artefactIndex = currentIndex;
431+
} else if (utf8String.equals("isDirectDependency")) {
432+
isDirectDependencyIndex = currentIndex;
433+
}
434+
435+
// Early exit if all found
436+
if (classportInfoIndex > 0 && groupIndex > 0 && versionIndex > 0 &&
437+
idIndex > 0 && sourceProjectIdIndex > 0 && childIdsIndex > 0 &&
438+
artefactIndex > 0 && isDirectDependencyIndex > 0) {
439+
allFound = true;
440+
}
441+
break;
442+
case 3: case 4: // Integer, Float
443+
buffer.getInt();
444+
break;
445+
case 5: case 6: // Long, Double
446+
buffer.getLong();
447+
i++; // takes 2 slots
448+
currentIndex++; // Long and Double take 2 constant pool slots
449+
break;
450+
case 7: case 8: case 16: case 19: case 20: // Class, String, etc.
451+
buffer.getShort();
452+
break;
453+
case 9: case 10: case 11: case 12: case 17: case 18: // Fieldref, etc.
454+
buffer.getInt();
455+
break;
456+
case 15: // MethodHandle
457+
buffer.get();
458+
buffer.getShort();
459+
break;
460+
}
461+
currentIndex++;
462+
}
463+
464+
return new ClassportInfoIndices(
465+
classportInfoIndex,
466+
groupIndex,
467+
versionIndex,
468+
idIndex,
469+
sourceProjectIdIndex,
470+
childIdsIndex,
471+
artefactIndex,
472+
isDirectDependencyIndex
473+
);
474+
}
475+
421476
private int findUtf8InConstantPool(byte[] cpData, int cpCount, String target) {
422477
ByteBuffer buffer = ByteBuffer.wrap(cpData);
423478
int currentIndex = 1; // Constant pool indices start at 1
243 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)