Compare commits

...

8 Commits
v7.8 ... v7.8.2

Author SHA1 Message Date
niccolo.piazzesi
6776d6a3aa Prepare for 7.8.2 release
Summary: Update all relevant docs to prepare for release containing D56358

Reviewers: dominik.huber, thomas.vochten

Reviewed By: dominik.huber, thomas.vochten

Differential Revision: https://phabricator.guardsquare.com/D57357
2025-12-03 12:32:49 +01:00
niccolo.piazzesi
e1e2689c72 Document support up until java 25 2025-12-01 09:50:36 +01:00
piazzesiNiccolo-GS
14dcc28ba2 Reapply moved InterfaceUsageMarker to fx interface constant marking (#510) 2025-12-01 09:47:13 +01:00
niccolo.piazzesi
0c51b82946 Use latest version in all documentation 2025-10-27 09:11:20 +01:00
piazzesiNiccolo-GS
68dcc9880e Update proguard version 2025-10-27 09:08:23 +01:00
niccolo.piazzesi
7a76843f0c Add release note 2025-10-27 09:03:19 +01:00
Bengt Verscheure
d2170bad63 Use StructuredLineNumberInfo in LineNumberLinearizer 2025-10-24 11:02:26 +02:00
niccolo.piazzesi
6dfd878ebb Run InterfaceUsageMarker before NestUsageMarker to prevent missing marking of permitted subclasses attribute
Closes #501

1. [ClassUsageMarker assumes that the NestUsageMarker will mark the permitted subclasses](869ce156b1/base/src/main/java/proguard/shrink/ClassUsageMarker.java (L1144)).

2. [ NestUsageMarker only marks class constants in the permittedSubclasses attribute if the referenced class is used ](869ce156b1/base/src/main/java/proguard/shrink/NestUsageMarker.java (L100)).

3.  [ If the referenced class is another interface, this is marked by the InterfaceUsageMarker ](869ce156b1/base/src/main/java/proguard/shrink/InterfaceUsageMarker.java (L36)).

4. [However, the interfaceUsageMarker only runs after the NestUsageMarker, hence nothing gets marked if a sealed interface permits another sealed interface](869ce156b1/base/src/main/java/proguard/shrink/UsageMarker.java (L119-130))
2025-10-14 13:43:05 +02:00
16 changed files with 552 additions and 172 deletions

View File

@@ -108,7 +108,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.8.0'
classpath 'com.guardsquare:proguard-gradle:7.8.2'
}
}
```

View File

@@ -0,0 +1,6 @@
package proguard.classfile.attribute;
public enum ProGuardOrigin implements LineOrigin
{
INLINED
}

View File

@@ -29,18 +29,19 @@ import proguard.classfile.ProgramClass;
import proguard.classfile.ProgramMethod;
import proguard.classfile.attribute.Attribute;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.attribute.ExtendedLineNumberInfo;
import proguard.classfile.attribute.LineNumberInfo;
import proguard.classfile.attribute.LineNumberInfoBlock;
import proguard.classfile.attribute.LineNumberTableAttribute;
import proguard.classfile.attribute.StructuredLineNumberInfo;
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
import proguard.classfile.attribute.visitor.AllLineNumberInfoVisitor;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.attribute.visitor.LineNumberInfoVisitor;
import proguard.classfile.attribute.visitor.LineNumberRangeFinder;
import proguard.classfile.visitor.ClassVisitor;
import proguard.classfile.visitor.MemberVisitor;
import proguard.pass.Pass;
import java.util.Arrays;
import java.util.Stack;
/**
@@ -56,8 +57,7 @@ public class LineNumberLinearizer
implements Pass,
ClassVisitor,
MemberVisitor,
AttributeVisitor,
LineNumberInfoVisitor
AttributeVisitor
{
private static final Logger logger = LogManager.getLogger(LineNumberLinearizer.class);
@@ -76,16 +76,15 @@ implements Pass,
* optimizations like method inlining and class merging.
*/
@Override
public void execute(AppView appView)
{
public void execute(AppView appView) {
appView.programClassPool.classesAccept(this);
}
// Implementations for ClassVisitor.
@Override
public void visitAnyClass(Clazz clazz) { }
public void visitAnyClass(Clazz clazz) {
}
@Override
public void visitProgramClass(ProgramClass programClass)
@@ -109,26 +108,26 @@ implements Pass,
}
}
// Implementations for MemberVisitor.
@Override
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
{
programMethod.attributesAccept(programClass, this);
}
// Implementations for AttributeVisitor.
@Override
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
@Override
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
codeAttribute.attributesAccept(clazz, method, this);
}
@Override
public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
{
logger.debug("LineNumberLinearizer [{}.{}{}]:",
@@ -138,112 +137,142 @@ implements Pass,
);
enclosingLineNumbers.clear();
previousLineNumberInfo = null;
// Process all line numbers.
lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
}
// Figure out which lines need linearizing. Only freshly inlined blocks need to be linearized.
LineNumberInfo[] infos = lineNumberTableAttribute.lineNumberTable;
int lineNumberTableLength = lineNumberTableAttribute.u2lineNumberTableLength;
boolean[] inlinedBlock = new boolean[lineNumberTableLength];
// Implementations for LineNumberInfoVisitor.
public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
{
String source = lineNumberInfo.getSource();
String debugMessage = String.format(" [%s] line %s%s",
lineNumberInfo.u2startPC,
lineNumberInfo.u2lineNumber,
source == null ? "" : " [" + source + "]"
);
// Is it an inlined line number?
if (source != null)
int currentDepth = 0;
for (int i = 0; i < lineNumberTableLength; i++)
{
ExtendedLineNumberInfo extendedLineNumberInfo =
(ExtendedLineNumberInfo)lineNumberInfo;
int lineNumber = extendedLineNumberInfo.u2lineNumber;
// Are we entering or exiting a new inlined block?
if (previousLineNumberInfo == null ||
!source.equals(previousLineNumberInfo.getSource()))
LineNumberInfo currentInfo = infos[i];
if (currentInfo.u2lineNumber == MethodInliner.INLINED_METHOD_START_LINE_NUMBER)
{
currentDepth++;
}
inlinedBlock[i] = currentDepth > 0;
if (currentInfo.u2lineNumber == MethodInliner.INLINED_METHOD_END_LINE_NUMBER)
{
currentDepth--;
}
}
// Linearize the line numbers.
LineNumberInfo previousLineNumberInfo = null;
for (int i = 0; i < lineNumberTableLength; i++)
{
LineNumberInfo lineNumberInfo = infos[i];
String source = lineNumberInfo.getSource();
logger.debug(" [{}] line {}{}", lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber, source == null ? "" : " [" + source + "]");
// Is it an inlined line number?
if (source != null && inlinedBlock[i])
{
int lineNumber = lineNumberInfo.u2lineNumber;
// Are we entering a new inlined block?
if (lineNumber != MethodInliner.INLINED_METHOD_END_LINE_NUMBER)
if (lineNumber == MethodInliner.INLINED_METHOD_START_LINE_NUMBER)
{
// Remember information about the inlined block.
enclosingLineNumbers.push(previousLineNumberInfo != null ?
new MyLineNumberBlock(currentLineNumberShift,
previousLineNumberInfo.u2lineNumber,
previousLineNumberInfo.getSource()) :
new MyLineNumberBlock(0, 0, null));
enclosingLineNumbers.push(
previousLineNumberInfo != null
? new MyLineNumberBlock(
currentLineNumberShift,
previousLineNumberInfo.u2lineNumber,
previousLineNumberInfo.getSource() != null
? previousLineNumberInfo.getBlock()
: null)
: new MyLineNumberBlock(0, 0, null));
// Parse the end line number from the source string,
// so we know how large a block this will be.
// Parse the end line number from the source string, so we know how large a block this
// will be.
int separatorIndex1 = source.indexOf(':');
int separatorIndex2 = source.indexOf(':', separatorIndex1 + 1);
int startLineNumber = Integer.parseInt(source.substring(separatorIndex1 + 1, separatorIndex2));
int endLineNumber = Integer.parseInt(source.substring(separatorIndex2 + 1));
int startLineNumber =
Integer.parseInt(source.substring(separatorIndex1 + 1, separatorIndex2));
int endLineNumber = Integer.parseInt(source.substring(separatorIndex2 + 1));
// Start shifting, if necessary, so the block ends up beyond
// the highest used line number. We're striving for rounded
// shifts, unless we've reached a given limit, to avoid
// running out of line numbers too quickly.
// TODO: this matches a quirk in the old behavior where the opening line is always :0:0
// this is a bug that probably causes overlapping line numbers but for now we will match
// this behavior so we can directly compare old and new mappings.
startLineNumber = 0;
endLineNumber = 0;
// Start shifting, if necessary, so the block ends up beyond the highest used line number.
// We're striving for rounded shifts, unless we've reached a given limit, to avoid running
// out of line numbers too quickly.
currentLineNumberShift =
highestUsedLineNumber > SHIFT_ROUNDING_LIMIT ?
highestUsedLineNumber - startLineNumber + 1 :
startLineNumber > highestUsedLineNumber ? 0 :
(highestUsedLineNumber - startLineNumber + SHIFT_ROUNDING)
/ SHIFT_ROUNDING * SHIFT_ROUNDING;
highestUsedLineNumber > SHIFT_ROUNDING_LIMIT
? highestUsedLineNumber - startLineNumber + 1
: startLineNumber > highestUsedLineNumber
? 0
: (highestUsedLineNumber - startLineNumber + SHIFT_ROUNDING)
/ SHIFT_ROUNDING
* SHIFT_ROUNDING;
highestUsedLineNumber = endLineNumber + currentLineNumberShift;
debugMessage += String.format(" (enter with shift %s)", currentLineNumberShift);
logger.debug(" (enter with shift {})", currentLineNumberShift);
}
// Are we exiting an inlined block?
else if (lineNumber == MethodInliner.INLINED_METHOD_END_LINE_NUMBER)
{
// TODO: There appear to be cases where the stack is empty at this point, so we've added a
// check.
if (enclosingLineNumbers.isEmpty())
{
logger.debug("Problem linearizing line numbers for optimized code ({}.{})", clazz.getName(), method.getName(clazz));
}
else
{
// Pop information about the enclosing line number.
MyLineNumberBlock lineNumberBlock = enclosingLineNumbers.pop();
// Set this end of the block to the line at which it was inlined.
lineNumberInfo =
lineNumberBlock.enclosingSource != null
? lineNumberBlock.enclosingSource.line(
lineNumberInfo.u2startPC, lineNumberBlock.enclosingLineNumber)
: new LineNumberInfo(
lineNumberInfo.u2startPC, lineNumberBlock.enclosingLineNumber);
infos[i] = lineNumberInfo;
// Reset the shift to the shift of the block.
currentLineNumberShift = lineNumberBlock.lineNumberShift;
logger.debug(" (exit to shift {})", currentLineNumberShift);
}
}
else
{
logger.debug(" (apply shift {})", currentLineNumberShift);
// Apply the shift.
lineNumberInfo.u2lineNumber += currentLineNumberShift;
}
// TODO: There appear to be cases where the stack is empty at this point, so we've added a check.
else if (enclosingLineNumbers.isEmpty())
{
debugMessage += String.format("Problem linearizing line numbers for optimized code %s.%s)", clazz.getName(), method.getName(clazz));
logger.debug(debugMessage);
debugMessage = "";
}
// Are we exiting an inlined block?
else
{
// Pop information about the enclosing line number.
MyLineNumberBlock lineNumberBlock = enclosingLineNumbers.pop();
// Set this end of the block to the line at which it was
// inlined.
extendedLineNumberInfo.u2lineNumber = lineNumberBlock.enclosingLineNumber;
extendedLineNumberInfo.source = lineNumberBlock.enclosingSource;
// Reset the shift to the shift of the block.
currentLineNumberShift = lineNumberBlock.lineNumberShift;
debugMessage += String.format(" (exit to shift %s)", currentLineNumberShift);
}
}
else
{
debugMessage += String.format(" (apply shift %s)", currentLineNumberShift);
// Apply the shift.
lineNumberInfo.u2lineNumber += currentLineNumberShift;
}
previousLineNumberInfo = lineNumberInfo;
logger.debug(" -> line {}", lineNumberInfo.u2lineNumber);
}
previousLineNumberInfo = lineNumberInfo;
debugMessage += String.format(" -> line %s", lineNumberInfo.u2lineNumber);
logger.debug(debugMessage);
lineNumberTableAttribute.lineNumberTable =
Arrays.stream(infos, 0, lineNumberTableLength)
.filter(info -> info.u2lineNumber != MethodInliner.INLINED_METHOD_START_LINE_NUMBER)
.toArray(LineNumberInfo[]::new);
lineNumberTableAttribute.u2lineNumberTableLength =
lineNumberTableAttribute.lineNumberTable.length;
}
@@ -253,14 +282,13 @@ implements Pass,
*/
private static class MyLineNumberBlock
{
public final int lineNumberShift;
public final int enclosingLineNumber;
public final String enclosingSource;
public final int lineNumberShift;
public final int enclosingLineNumber;
public final LineNumberInfoBlock enclosingSource;
public MyLineNumberBlock(int lineNumberShift,
int enclosingLineNumber,
String enclosingSource)
{
public MyLineNumberBlock(int lineNumberShift,
int enclosingLineNumber,
LineNumberInfoBlock enclosingSource) {
this.lineNumberShift = lineNumberShift;
this.enclosingLineNumber = enclosingLineNumber;
this.enclosingSource = enclosingSource;

View File

@@ -40,6 +40,8 @@ import proguard.util.ProcessingFlags;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Stack;
/**
@@ -70,8 +72,9 @@ implements AttributeVisitor,
protected static final int MAXIMUM_RESULTING_CODE_LENGTH_JME = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "2000"));
protected static final int MAXIMUM_RESULTING_CODE_LENGTH_JVM = 65535;
static final int METHOD_DUMMY_START_LINE_NUMBER = 0;
static final int INLINED_METHOD_END_LINE_NUMBER = -1;
static final int METHOD_DUMMY_START_LINE_NUMBER = 0;
public static final int INLINED_METHOD_END_LINE_NUMBER = -1;
public static final int INLINED_METHOD_START_LINE_NUMBER = -2;
private static final Logger logger = LogManager.getLogger(MethodInliner.class);
@@ -94,23 +97,23 @@ implements AttributeVisitor,
new MethodInvocationMarker());
private final StackSizeComputer stackSizeComputer = new StackSizeComputer();
private ProgramClass targetClass;
private ProgramMethod targetMethod;
private ConstantAdder constantAdder;
private ExceptionInfoAdder exceptionInfoAdder;
private int estimatedResultingCodeLength;
private boolean inlining;
private Stack inliningMethods = new Stack();
private boolean emptyInvokingStack;
private boolean coveredByCatchAllHandler;
private int exceptionInfoCount;
private int uninitializedObjectCount;
private int variableOffset;
private boolean inlined;
private boolean inlinedAny;
private boolean copiedLineNumbers;
private String source;
private int minimumLineNumberIndex;
private ProgramClass targetClass;
private ProgramMethod targetMethod;
private ConstantAdder constantAdder;
private ExceptionInfoAdder exceptionInfoAdder;
private int estimatedResultingCodeLength;
private boolean inlining;
private Stack inliningMethods = new Stack();
private boolean emptyInvokingStack;
private boolean coveredByCatchAllHandler;
private int exceptionInfoCount;
private int uninitializedObjectCount;
private int variableOffset;
private boolean inlined;
private boolean inlinedAny;
private boolean copiedLineNumbers;
private final Deque<LineNumberInfoBlock> sourceBlock = new ArrayDeque<>();
private int minimumLineNumberIndex;
/**
@@ -322,15 +325,6 @@ implements AttributeVisitor,
public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
{
// Remember the source if we're inlining a method.
source = inlining ?
clazz.getName() + '.' +
method.getName(clazz) +
method.getDescriptor(clazz) + ':' +
lineNumberTableAttribute.getLowestLineNumber() + ':' +
lineNumberTableAttribute.getHighestLineNumber() :
null;
// Insert all line numbers, possibly partly before previously inserted
// line numbers.
lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
@@ -452,39 +446,61 @@ implements AttributeVisitor,
codeAttribute.attributesAccept(clazz, method, this);
// Add a marker at the start of the method. The LineNumberLinearizer relies on this to detect
// inlined blocks.
if (inlining)
{
LineNumberTableAttribute lineNumberTableAttribute =
(LineNumberTableAttribute) codeAttribute.getAttribute(clazz, Attribute.LINE_NUMBER_TABLE);
int lowest = 0;
int highest = 0;
if (lineNumberTableAttribute != null)
{
lowest = lineNumberTableAttribute.getLowestLineNumber();
highest = lineNumberTableAttribute.getHighestLineNumber();
}
StructuredLineNumberInfo.Block block =
new StructuredLineNumberInfo.Block(
ProGuardOrigin.INLINED,
clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz),
lowest,
highest);
sourceBlock.push(block);
// Insert a start marker.
LineNumberInfo startLineNumberInfo = block.line(0, INLINED_METHOD_START_LINE_NUMBER);
minimumLineNumberIndex =
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, startLineNumberInfo) + 1;
}
codeAttribute.attributesAccept(clazz, method, this);
// Make sure we at least have some entry at the start of the method.
if (!copiedLineNumbers)
{
String source = inlining ?
clazz.getName() + '.' +
method.getName(clazz) +
method.getDescriptor(clazz) +
":0:0" :
null;
LineNumberInfo line =
inlining
? sourceBlock.peekLast().line(0, METHOD_DUMMY_START_LINE_NUMBER)
: new LineNumberInfo(0, METHOD_DUMMY_START_LINE_NUMBER);
minimumLineNumberIndex =
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex,
new ExtendedLineNumberInfo(0,
METHOD_DUMMY_START_LINE_NUMBER,
source)) + 1;
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, line) + 1;
}
// Add a marker at the end of an inlined method.
// The marker will be corrected in LineNumberLinearizer,
// so it points to the line of the enclosing method.
// Add a marker at the end of an inlined method. The marker will be corrected in
// LineNumberLinearizer, so it points to the line of the enclosing method.
if (inlining)
{
String source =
clazz.getName() + '.' +
method.getName(clazz) +
method.getDescriptor(clazz) +
":0:0";
clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz) + ":0:0";
minimumLineNumberIndex =
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex,
new ExtendedLineNumberInfo(codeAttribute.u4codeLength,
INLINED_METHOD_END_LINE_NUMBER,
source)) + 1;
codeAttributeComposer.insertLineNumber(
minimumLineNumberIndex,
sourceBlock
.pop()
.line(codeAttribute.u4codeLength, INLINED_METHOD_END_LINE_NUMBER))
+ 1;
}
codeAttributeComposer.endCodeFragment();
@@ -826,19 +842,30 @@ implements AttributeVisitor,
{
try
{
String newSource = lineNumberInfo.getSource() != null ?
lineNumberInfo.getSource() :
source;
LineNumberInfoBlock block = sourceBlock.peekLast();
boolean newSource = block != null;
boolean preserveSource = lineNumberInfo.getSource() != null;
LineNumberInfo newLineNumberInfo = newSource != null ?
new ExtendedLineNumberInfo(lineNumberInfo.u2startPC,
lineNumberInfo.u2lineNumber,
newSource) :
new LineNumberInfo(lineNumberInfo.u2startPC,
lineNumberInfo.u2lineNumber);
LineNumberInfo newLineNumberInfo;
if (preserveSource)
{
newLineNumberInfo =
((StructuredLineNumberInfo) lineNumberInfo)
.getBlock(ProGuardOrigin.INLINED)
.line(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber);
}
else if (newSource)
{
newLineNumberInfo = block.line(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber);
}
else
{
newLineNumberInfo =
new LineNumberInfo(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber);
}
minimumLineNumberIndex =
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, newLineNumberInfo) + 1;
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, newLineNumberInfo) + 1;
}
catch (IllegalArgumentException e)
{

View File

@@ -106,6 +106,10 @@ public class UsageMarker
classUsageMarker))
));
// Mark interfaces that have to be kept. This must be before the NestUsageMarker call right after,
// see https://github.com/Guardsquare/proguard/issues/501.
programClassPool.classesAccept(new InterfaceUsageMarker(classUsageMarker));
// Mark the elements of Kotlin metadata that need to be kept.
if (configuration.keepKotlinMetadata)
{
@@ -114,7 +118,6 @@ public class UsageMarker
new ReferencedKotlinMetadataVisitor(
classUsageMarker));
}
// Mark the inner class and annotation information that has to be kept.
programClassPool.classesAccept(
new UsedClassFilter(simpleUsageMarker,
@@ -126,7 +129,8 @@ public class UsageMarker
new LocalVariableTypeUsageMarker(classUsageMarker)
))));
// Mark interfaces that have to be kept.
// Second Interface Usage marking, this is necessary for marking interface constants that are not directly referenced
// (e.g. interfaces only referenced through annotations). See https://github.com/Guardsquare/proguard/issues/508.
programClassPool.classesAccept(new InterfaceUsageMarker(classUsageMarker));
if (configuration.keepKotlinMetadata)

View File

@@ -0,0 +1,84 @@
package proguard.optimize.peephole
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
import proguard.classfile.AccessConstants
import proguard.classfile.ClassConstants
import proguard.classfile.ProgramMethod
import proguard.classfile.VersionConstants
import proguard.classfile.attribute.Attribute
import proguard.classfile.attribute.Attribute.LINE_NUMBER_TABLE
import proguard.classfile.attribute.LineNumberInfo
import proguard.classfile.attribute.LineNumberTableAttribute
import proguard.classfile.attribute.ProGuardOrigin
import proguard.classfile.attribute.StructuredLineNumberInfo
import proguard.classfile.editor.AttributesEditor
import proguard.classfile.editor.ClassBuilder
import proguard.testutils.CodeAttributeFinder
class InlinedMethodLineNumberLinearizerTest : BehaviorSpec({
Given("A method with two levels of inlined line numbers") {
val clazzBuilder = ClassBuilder(VersionConstants.CLASS_VERSION_18, AccessConstants.PUBLIC, "A", ClassConstants.NAME_JAVA_LANG_OBJECT)
val clazz = clazzBuilder.programClass
val method = clazzBuilder.addAndReturnMethod(
AccessConstants.PUBLIC or AccessConstants.STATIC,
"a",
"()V",
10,
) {
it.return_()
} as ProgramMethod
val codeAttribute = CodeAttributeFinder.findCodeAttribute(method)!!
val blockB = StructuredLineNumberInfo.Block(ProGuardOrigin.INLINED, "B.b()V", 0, 0)
val blockC = StructuredLineNumberInfo.Block(ProGuardOrigin.INLINED, "C.c()V", 0, 0)
val lineNumbers = arrayOf(
LineNumberInfo(0, 1),
blockB.line(1, MethodInliner.INLINED_METHOD_START_LINE_NUMBER),
blockB.line(2, 11),
blockC.line(3, MethodInliner.INLINED_METHOD_START_LINE_NUMBER),
blockC.line(4, 21),
blockC.line(5, MethodInliner.INLINED_METHOD_END_LINE_NUMBER),
blockB.line(6, 12),
blockB.line(7, MethodInliner.INLINED_METHOD_END_LINE_NUMBER),
LineNumberInfo(8, 2)
)
val lineNumberTableAttribute = LineNumberTableAttribute(clazzBuilder.constantPoolEditor.addUtf8Constant(LINE_NUMBER_TABLE), lineNumbers.size, lineNumbers)
AttributesEditor(clazz, method, codeAttribute, true).addAttribute(lineNumberTableAttribute)
When("Linearizing the line numbers") {
clazz.accept(LineNumberLinearizer())
Then("The line number table should look correct") {
val lineNumberTableAttribute = codeAttribute.getAttribute(clazz, Attribute.LINE_NUMBER_TABLE) as LineNumberTableAttribute
val table = lineNumberTableAttribute.lineNumberTable
table[0].u2lineNumber shouldBe 1
table[0].source.shouldBeNull()
table[1].u2lineNumber.mod(LineNumberLinearizer.SHIFT_ROUNDING) shouldBe 11
table[1].source shouldBe "B.b()V:0:0"
table[2].u2lineNumber.mod(LineNumberLinearizer.SHIFT_ROUNDING) shouldBe 21
table[2].source shouldBe "C.c()V:0:0"
table[3].u2lineNumber.mod(LineNumberLinearizer.SHIFT_ROUNDING) shouldBe 11
table[3].source shouldBe "B.b()V:0:0"
table[4].u2lineNumber.mod(LineNumberLinearizer.SHIFT_ROUNDING) shouldBe 12
table[4].source shouldBe "B.b()V:0:0"
table[5].u2lineNumber shouldBe 1
table[5].source.shouldBeNull()
table[6].u2lineNumber shouldBe 2
table[6].source.shouldBeNull()
}
}
}
})

View File

@@ -0,0 +1,218 @@
package proguard.shrink
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import proguard.Configuration
import proguard.classfile.ClassPool
import proguard.classfile.Clazz
import proguard.classfile.attribute.Attribute
import proguard.classfile.attribute.PermittedSubclassesAttribute
import proguard.classfile.attribute.visitor.AllAttributeVisitor
import proguard.classfile.attribute.visitor.AttributeVisitor
import proguard.classfile.constant.ClassConstant
import proguard.classfile.constant.Constant
import proguard.classfile.constant.visitor.ConstantVisitor
import proguard.classfile.visitor.AllMemberVisitor
import proguard.classfile.visitor.MultiClassVisitor
import proguard.resources.file.ResourceFilePool
import proguard.testutils.ClassPoolBuilder
import proguard.testutils.JavaSource
import proguard.testutils.RequiresJavaVersion
import proguard.util.ProcessingFlagSetter
import proguard.util.ProcessingFlags.DONT_SHRINK
class UsageMarkerTest : BehaviorSpec({
Given("A class pool with interfaces only referenced through annotations") {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("MyInterface.java","""
interface MyInterface {}
""".trimIndent()),
JavaSource("MyAnnotation.java","""
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
Class<?> value();
}
""".trimIndent()),
JavaSource("MyImpl.java","""
class MyImpl implements MyInterface {}
""".trimIndent()),
JavaSource("InterfaceTest.java", """
import java.lang.reflect.Field;
class InterfaceTest {
@MyAnnotation(MyImpl.class)
String s;
public static void main(String... args) throws Exception {
Field f = InterfaceTest.class.getDeclaredField("s");
MyAnnotation annotation = f.getAnnotation(MyAnnotation.class);
Object obj = annotation.value().getDeclaredConstructor().newInstance();
if (obj instanceof MyInterface) {
System.out.println("success");
} else {
throw new Exception(obj.getClass() + " does not implement " + MyInterface.class);
}
}
}
""".trimIndent())
)
val implClass = programClassPool.getClass("MyImpl")
val main = programClassPool.getClass("InterfaceTest")
main.accept(
MultiClassVisitor(ProcessingFlagSetter(DONT_SHRINK), AllMemberVisitor(
ProcessingFlagSetter(DONT_SHRINK)
)))
When("marking") {
val simpleUsageMarker = SimpleUsageMarker()
UsageMarker(Configuration()).mark(programClassPool,ClassPool(), ResourceFilePool(),simpleUsageMarker)
Then("The interface class should be marked as used.") {
val used = object : ConstantVisitor {
var used = false
override fun visitAnyConstant(
clazz: Clazz?,
constant: Constant?
) {
used = used or simpleUsageMarker.isUsed(constant)
}
}
implClass.interfaceConstantsAccept(used)
used.used shouldBe true
}
}
}
})
@RequiresJavaVersion(15)
class Java15UsageMarkerTest : BehaviorSpec({
// Regression test for https://github.com/Guardsquare/proguard/issues/501
Given("A class pool containing a sealed interface extending another sealed interface, and final classes implementing both") {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("sample/Animal.java","""
package sample;
public sealed interface Animal permits Fish, Mammal {
static Animal ofType(String type) {
if (Cat.TYPE.matches(type)) {
return new Cat();
} else if (Dog.TYPE.matches(type)) {
return new Dog();
} else if (Fish.TYPE.matches(type)) {
return new Fish();
}
throw new IllegalArgumentException("Wrong animal type: " + type);
}
}
""".trimIndent()),
JavaSource("sample/AnimalType.java","""
package sample;
public enum AnimalType {
CAT("CAT"),
DOG("DOG"),
FISH("FISH");
private final String typeString;
AnimalType(String typeString){
this.typeString = typeString;
}
public boolean matches(String typeString) {
return this.typeString.equalsIgnoreCase(typeString);
}
}
""".trimIndent()),
JavaSource("sample/Mammal.java", """
package sample;
public sealed interface Mammal extends Animal permits Cat, Dog {
}
""".trimIndent()),
JavaSource("sample/Cat.java","""
package sample;
public final class Cat implements Mammal {
public static final AnimalType TYPE = AnimalType.CAT;
}
""".trimIndent()),
JavaSource("sample/Dog.java","""
package sample;
public final class Dog implements Mammal {
public static final AnimalType TYPE = AnimalType.DOG;
}
""".trimIndent()),
JavaSource("sample/Fish.java","""
package sample;
public final class Fish implements Animal {
public static final AnimalType TYPE = AnimalType.FISH;
}
""".trimIndent()),
JavaSource("sample/Main.java","""
package sample;
public class Main {
public static void main(String[] args) {
System.out.println("Trying to create animal");
Animal animal = Animal.ofType("fish");
System.out.println("Successfully created animal");
}
}
""".trimIndent()
), javacArguments = listOf("--enable-preview", "--release", "15"),
)
val animal = programClassPool.getClass("sample/Animal")
val main = programClassPool.getClass("sample/Main")
main.accept(
MultiClassVisitor(ProcessingFlagSetter(DONT_SHRINK), AllMemberVisitor(
ProcessingFlagSetter(DONT_SHRINK)
)))
When("marking") {
val simpleUsageMarker = SimpleUsageMarker()
UsageMarker(Configuration()).mark(programClassPool,ClassPool(), ResourceFilePool(),simpleUsageMarker)
Then("The Animal class permitted subclasses constants should all be marked as used") {
var visited = false
animal.accept(AllAttributeVisitor(object : AttributeVisitor, ConstantVisitor {
override fun visitAnyAttribute(
clazz: Clazz,
attribute: Attribute
) {
}
override fun visitPermittedSubclassesAttribute(
clazz: Clazz,
permittedSubclassesAttribute: PermittedSubclassesAttribute
) {
permittedSubclassesAttribute.permittedSubclassConstantsAccept(clazz,this)
}
override fun visitAnyConstant(
clazz: Clazz,
constant: Constant
) {
}
override fun visitClassConstant(
clazz: Clazz,
classConstant: ClassConstant
) {
visited = true
simpleUsageMarker.isUsed(classConstant) shouldBe true
}
}))
visited shouldBe true
}
}
}
})

View File

@@ -68,9 +68,9 @@ Yes, you can. **ProGuard** itself is distributed under the GPL, but this
doesn't affect the programs that you process. Your code remains yours, and its
license can remain the same.
## Does ProGuard work with Java 2, 5,..., 19? {: #jdk1.4}
## Does ProGuard work with Java 2, 5,..., 25? {: #jdk1.4}
Yes, **ProGuard** supports all JDKs from 1.1 up to and including 19. Java 2
Yes, **ProGuard** supports all JDKs from 1.1 up to and including 25. Java 2
introduced some small differences in the class file format. Java 5 added
attributes for generics and for annotations. Java 6 introduced optional
preverification attributes. Java 7 made preverification obligatory and

View File

@@ -1,4 +1,4 @@
Welcome to the manual for **ProGuard** version 7.8.0 ([what's new?](releasenotes.md)).
Welcome to the manual for **ProGuard** version 7.8.2 ([what's new?](releasenotes.md)).
ProGuard is an open-sourced Java class file shrinker, optimizer, obfuscator, and
preverifier. As a result, ProGuard processed applications and libraries are smaller and faster.

View File

@@ -3,4 +3,4 @@
!!! Warning
ProGuard no longer supports backporting, and cannot backport class files compiled with Java >11.
Provide supports Java versions up to and including 19.
Provide supports Java versions up to and including 25.

View File

@@ -1,3 +1,16 @@
## Version 7.8.2
### Bugfixes
- Fix regression in marking of interface constants (#508).
## Version 7.8.1
### Bugfixes
- Prevent `java.lang.IncompatibleClassChangeError` when shrinking is enabled and sealed interfaces are used (#501).
- Prevent `java.lang.ClassCastException` when inlining (#505).
## Version 7.8
### Kotlin support

View File

@@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.8.0'
classpath 'com.guardsquare:proguard-gradle:7.8.2'
}
}

View File

@@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.8.0'
classpath 'com.guardsquare:proguard-gradle:7.8.2'
}
}

View File

@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath("com.guardsquare:proguard-gradle:7.8.0")
classpath("com.guardsquare:proguard-gradle:7.8.2")
}
}

View File

@@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.8.0'
classpath 'com.guardsquare:proguard-gradle:7.8.2'
}
}

View File

@@ -1,4 +1,4 @@
proguardVersion = 7.8.0
proguardVersion = 7.8.2
# The version of ProGuardCORE that sub-projects are built with
proguardCoreVersion = 9.2.0