Compare commits

...

12 Commits

Author SHA1 Message Date
niccolo.piazzesi
8f67c1977b Fix test task dependency 2026-02-12 09:11:03 +01:00
niccolo.piazzesi
0303116af9 Update and clean gradle configuration 2026-02-12 09:07:16 +01:00
niccolo.piazzesi
8425e86946 Update ProguardCORE, address API changes 2026-02-12 09:03:30 +01:00
niccolo.piazzesi
164e3d8f4d Remove java-test-fixtures plugin
The Java-test-fixtures plugin would create incorrect pom files. Since we  don't actually define test fixtures in Proguard (the published jars are empty, you can look older releases), we can just remove it.
2026-01-21 12:03:07 +01:00
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
31 changed files with 672 additions and 273 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

@@ -5,7 +5,7 @@ plugins {
afterEvaluate {
publishing {
publications.getByName(project.name) {
publications.named(project.name) {
pom {
description = 'Java annotations to configure ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode'
}

View File

@@ -15,7 +15,7 @@ dependencies {
implementation 'org.apache.ant:ant:1.10.15'
}
task fatJar(type: ShadowJar) {
def fatJar = tasks.register("fatJar", ShadowJar) {
destinationDirectory.set(file("$rootDir/lib"))
archiveFileName.set('proguard-ant.jar')
from sourceSets.main.output
@@ -32,7 +32,7 @@ assemble.dependsOn fatJar
afterEvaluate {
publishing {
publications.getByName(project.name) {
publications.named(project.name) {
pom {
description = 'Ant plugin for ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode'
}

View File

@@ -1,6 +1,7 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id 'java-library'
id 'java-test-fixtures'
id 'maven-publish'
id "org.jetbrains.kotlin.jvm"
id 'com.adarshr.test-logger' version '4.0.0'
@@ -12,10 +13,10 @@ repositories {
mavenCentral()
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
kotlinOptions {
jvmTarget = "${target}"
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.fromTarget(project.findProperty("target")))
}
}
dependencies {
@@ -55,24 +56,25 @@ test {
useJUnitPlatform()
}
task testAllJavaVersions() { testAllTask ->
def testAllJavaVersion = tasks.register("testAllJavaVersions"){ testAllTask ->
dependsOn(test) // the usual test runs on Java 8
}
javaVersionsForTest.each {version ->
task("testJava$version", type: Test) {
useJUnitPlatform()
ignoreFailures = true
javaVersionsForTest.each {version ->
def testJavaVersion = tasks.register("testJava$version", Test) {
useJUnitPlatform()
ignoreFailures = true
// The version of bytebuddy used by mockk only supports Java 22 experimentally so far
// if (version >= 22) systemProperty 'net.bytebuddy.experimental', true
// The version of bytebuddy used by mockk only supports Java 22 experimentally so far
// if (version >= 22) systemProperty 'net.bytebuddy.experimental', true
testAllTask.dependsOn(it)
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(version)
}
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(version)
}
}
testAllJavaVersion.configure { it.dependsOn(testJavaVersion) }
}
jacocoTestReport {
@@ -95,7 +97,7 @@ jacocoTestReport {
afterEvaluate {
publishing {
publications.getByName(project.name) {
publications.named(project.name) {
pom {
description = 'ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode'
}

View File

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

View File

@@ -131,16 +131,19 @@ implements KotlinMetadataVisitor,
kotlinPropertyMetadata.flags.hasAnnotations = false;
}
if (kotlinPropertyMetadata.referencedGetterMethod != null)
if (kotlinPropertyMetadata.getterMetadata != null &&
kotlinPropertyMetadata.getterMetadata.referencedMethod != null)
{
kotlinPropertyMetadata.referencedGetterMethod.accept(clazz, annotationCounter.reset());
kotlinPropertyMetadata.getterFlags.hasAnnotations = annotationCounter.getCount() > 0;
kotlinPropertyMetadata.getterMetadata.referencedMethod.accept(clazz, annotationCounter.reset());
kotlinPropertyMetadata.getterMetadata.hasAnnotations = annotationCounter.getCount() > 0;
}
if (kotlinPropertyMetadata.flags.isVar && kotlinPropertyMetadata.referencedSetterMethod != null)
if (kotlinPropertyMetadata.flags.isVar &&
kotlinPropertyMetadata.setterMetadata != null &&
kotlinPropertyMetadata.setterMetadata.referencedMethod != null)
{
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset());
kotlinPropertyMetadata.setterFlags.hasAnnotations = annotationCounter.getCount() > 0;
kotlinPropertyMetadata.setterMetadata.referencedMethod.accept(clazz, annotationCounter.reset());
kotlinPropertyMetadata.setterMetadata.hasAnnotations = annotationCounter.getCount() > 0;
}
}
@@ -278,9 +281,10 @@ implements KotlinMetadataVisitor,
kotlinPropertyMetadata,
this);
if (kotlinValueParameterMetadata.flags.hasAnnotations)
if (kotlinValueParameterMetadata.flags.hasAnnotations &&
kotlinPropertyMetadata.setterMetadata != null)
{
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset());
kotlinPropertyMetadata.setterMetadata.referencedMethod.accept(clazz, annotationCounter.reset());
kotlinValueParameterMetadata.flags.hasAnnotations =
annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0;
}

View File

@@ -146,8 +146,8 @@ public class Marker implements Pass
KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata,
KotlinPropertyMetadata kotlinPropertyMetadata) {
List<Processable> processables = Stream.of(kotlinPropertyMetadata.referencedBackingField,
kotlinPropertyMetadata.referencedGetterMethod,
kotlinPropertyMetadata.referencedSetterMethod)
kotlinPropertyMetadata.getterMetadata.referencedMethod,
kotlinPropertyMetadata.setterMetadata != null ? kotlinPropertyMetadata.setterMetadata.referencedMethod : null)
.filter(Objects::nonNull)
.collect(Collectors.toList());
int flags = 0;

View File

@@ -64,11 +64,13 @@ implements KotlinMetadataVisitor,
if ((kotlinPropertyMetadata.referencedBackingField != null &&
(kotlinPropertyMetadata.referencedBackingField.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) ||
(kotlinPropertyMetadata.referencedGetterMethod != null &&
(kotlinPropertyMetadata.referencedGetterMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) ||
(kotlinPropertyMetadata.getterMetadata != null &&
kotlinPropertyMetadata.getterMetadata.referencedMethod != null &&
(kotlinPropertyMetadata.getterMetadata.referencedMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) ||
(kotlinPropertyMetadata.referencedSetterMethod != null &&
(kotlinPropertyMetadata.referencedSetterMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0))
(kotlinPropertyMetadata.setterMetadata != null &&
kotlinPropertyMetadata.setterMetadata.referencedMethod != null &&
(kotlinPropertyMetadata.setterMetadata.referencedMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0))
{
return;
}

View File

@@ -114,14 +114,14 @@ implements KotlinMetadataVisitor,
KotlinPropertyMetadata kotlinPropertyMetadata)
{
keepParameterInfo = false;
if (kotlinPropertyMetadata.referencedSetterMethod != null)
if (kotlinPropertyMetadata.setterMetadata != null && kotlinPropertyMetadata.setterMetadata.referencedMethod != null)
{
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, this);
kotlinPropertyMetadata.setterMetadata.referencedMethod.accept(clazz, this);
}
if (keepParameterInfo)
{
kotlinPropertyMetadata.setterParametersAccept(clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.setterParameterAccept(clazz, kotlinDeclarationContainerMetadata, this);
}
}

View File

@@ -108,8 +108,8 @@ public class KotlinContextReceiverUsageMarker implements
public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata)
{
markContextReceiverParameters(kotlinPropertyMetadata.contextReceivers,
kotlinPropertyMetadata.referencedGetterMethod,
kotlinPropertyMetadata.referencedSetterMethod);
kotlinPropertyMetadata.getterMetadata.referencedMethod,
kotlinPropertyMetadata.setterMetadata != null ? kotlinPropertyMetadata.setterMetadata.referencedMethod : null);
}
private void markContextReceiverParameters(List<KotlinTypeMetadata> contextReceivers, Method...methods)

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

@@ -1869,11 +1869,13 @@ implements ClassVisitor,
kotlinPropertyMetadata.referencedBackingField != null &&
isUsed(kotlinPropertyMetadata.referencedBackingField);
boolean getterUsed =
kotlinPropertyMetadata.referencedGetterMethod != null &&
isUsed(kotlinPropertyMetadata.referencedGetterMethod);
kotlinPropertyMetadata.getterMetadata != null &&
kotlinPropertyMetadata.getterMetadata.referencedMethod != null &&
isUsed(kotlinPropertyMetadata.getterMetadata.referencedMethod);
boolean setterUsed =
kotlinPropertyMetadata.referencedSetterMethod != null &&
isUsed(kotlinPropertyMetadata.referencedSetterMethod);
kotlinPropertyMetadata.setterMetadata != null &&
kotlinPropertyMetadata.setterMetadata.referencedMethod != null &&
isUsed(kotlinPropertyMetadata.setterMetadata.referencedMethod);
if (backingFieldUsed || getterUsed || setterUsed)
{
@@ -1914,7 +1916,7 @@ implements ClassVisitor,
kotlinPropertyMetadata.receiverTypeAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.contextReceiverTypesAccept(clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.setterParametersAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.setterParameterAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.typeAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.versionRequirementAccept( clazz, kotlinDeclarationContainerMetadata, this);
}

View File

@@ -26,6 +26,7 @@ import proguard.classfile.kotlin.KotlinClassKindMetadata;
import proguard.classfile.kotlin.KotlinConstants;
import proguard.classfile.kotlin.KotlinConstructorMetadata;
import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata;
import proguard.classfile.kotlin.KotlinEnumEntryMetadata;
import proguard.classfile.kotlin.KotlinFileFacadeKindMetadata;
import proguard.classfile.kotlin.KotlinFunctionMetadata;
import proguard.classfile.kotlin.KotlinMetadata;
@@ -38,6 +39,7 @@ import proguard.classfile.kotlin.KotlinTypeMetadata;
import proguard.classfile.kotlin.KotlinTypeParameterMetadata;
import proguard.classfile.kotlin.KotlinValueParameterMetadata;
import proguard.classfile.kotlin.KotlinVersionRequirementMetadata;
import proguard.classfile.kotlin.flags.KotlinPropertyAccessorMetadata;
import proguard.classfile.kotlin.visitor.AllTypeVisitor;
import proguard.classfile.kotlin.visitor.KotlinConstructorVisitor;
import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor;
@@ -110,8 +112,7 @@ implements KotlinMetadataVisitor,
shrinkMetadataArray(kotlinClassKindMetadata.constructors);
shrinkArray(kotlinClassKindMetadata.enumEntryNames,
kotlinClassKindMetadata.referencedEnumEntries);
shrinkEnumEntries(kotlinClassKindMetadata.enumEntries);
shrinkArray(kotlinClassKindMetadata.nestedClassNames,
kotlinClassKindMetadata.referencedNestedClasses);
@@ -167,7 +168,7 @@ implements KotlinMetadataVisitor,
{
kotlinPropertyMetadata.versionRequirementAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.typeAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.setterParametersAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.setterParameterAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.receiverTypeAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.contextReceiverTypesAccept(clazz, kotlinDeclarationContainerMetadata, this);
kotlinPropertyMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this);
@@ -179,21 +180,18 @@ implements KotlinMetadataVisitor,
kotlinPropertyMetadata.referencedBackingField = null;
}
if (shouldShrinkMetadata(kotlinPropertyMetadata.getterSignature,
kotlinPropertyMetadata.referencedGetterMethod))
if (shouldShrinkMetadata(kotlinPropertyMetadata.getterMetadata))
{
kotlinPropertyMetadata.getterSignature = null;
kotlinPropertyMetadata.referencedGetterMethod = null;
kotlinPropertyMetadata.getterMetadata.signature = null;
kotlinPropertyMetadata.getterMetadata.referencedMethod = null;
}
if (shouldShrinkMetadata(kotlinPropertyMetadata.setterSignature,
kotlinPropertyMetadata.referencedSetterMethod))
if (shouldShrinkMetadata(kotlinPropertyMetadata.setterMetadata))
{
kotlinPropertyMetadata.setterSignature = null;
kotlinPropertyMetadata.referencedSetterMethod = null;
kotlinPropertyMetadata.flags.isVar = false;
kotlinPropertyMetadata.setterParameter = null;
kotlinPropertyMetadata.setterParameters.clear();
kotlinPropertyMetadata.setterMetadata.signature = null;
kotlinPropertyMetadata.setterMetadata.referencedMethod = null;
kotlinPropertyMetadata.flags.isVar = false;
kotlinPropertyMetadata.setterParameter = null;
}
kotlinPropertyMetadata.versionRequirementAccept(clazz,
@@ -206,7 +204,7 @@ implements KotlinMetadataVisitor,
kotlinPropertyMetadata.syntheticMethodForAnnotations = null;
kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations = null;
kotlinPropertyMetadata.referencedSyntheticMethodClass = null;
kotlinPropertyMetadata.flags.hasAnnotations = false;
kotlinPropertyMetadata.annotations.clear();
}
if (kotlinPropertyMetadata.syntheticMethodForDelegate != null &&
@@ -220,8 +218,10 @@ implements KotlinMetadataVisitor,
// Fix inconsistencies that were introduced as
// a result of shrinking
if (kotlinPropertyMetadata.referencedBackingField != null &&
kotlinPropertyMetadata.getterSignature == null &&
kotlinPropertyMetadata.setterSignature == null &&
(kotlinPropertyMetadata.setterMetadata == null ||
kotlinPropertyMetadata.setterMetadata.signature == null) &&
(kotlinPropertyMetadata.getterMetadata == null ||
kotlinPropertyMetadata.getterMetadata.signature == null) &&
(kotlinPropertyMetadata.referencedBackingField.getAccessFlags() & AccessConstants.PRIVATE) != 0 &&
!kotlinPropertyMetadata.flags.visibility.isPrivate)
{
@@ -399,6 +399,11 @@ implements KotlinMetadataVisitor,
!usageMarker.isUsed(jvmElement);
}
private boolean shouldShrinkMetadata(KotlinPropertyAccessorMetadata kotlinPropertyAccessorMetadata) {
return kotlinPropertyAccessorMetadata != null && !usageMarker.isUsed(kotlinPropertyAccessorMetadata.referencedMethod);
}
/**
* Shrinks elements and their corresponding referenced element, based on
@@ -412,10 +417,13 @@ implements KotlinMetadataVisitor,
shrinkArray(usageMarker, elements, referencedJavaElements);
}
private void shrinkEnumEntries(List<KotlinEnumEntryMetadata> enumEntries) {
enumEntries.removeIf(usageMarker::isUsed);
}
/**
* Shrinks elements and their corresponding referenced element, based on
* markings on the referenced element.
*
* List is modified - must be a modifiable list!
*/
static void shrinkArray(SimpleUsageMarker usageMarker,

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

@@ -13,7 +13,7 @@ allprojects {
}
}
task buildDocumentation(type: Exec) {
tasks.register("buildDocumentation", Exec) {
inputs.dir 'docs/md'
inputs.file 'docs/mkdocs.yml'
outputs.dir 'docs/html'
@@ -60,7 +60,7 @@ allprojects { Project project ->
configure(project) {
publishing {
publications {
create(project.name, MavenPublication) {
register(project.name, MavenPublication) {
pom {
artifactId = "proguard-$project.name"
name = "$group:$artifactId"
@@ -115,7 +115,7 @@ allprojects { Project project ->
}
publishing {
publications {
getByName(project.name) {
named(project.name) {
from components.java
}
}
@@ -146,31 +146,34 @@ allprojects { Project project ->
}
distributions {
main {
distributionBaseName.set('proguard')
contents {
into('lib') {
from tasks.getByPath(':proguard-app:fatJar').outputs
from tasks.getByPath(':gui:fatJar').outputs
from tasks.getByPath(':retrace:fatJar').outputs
from tasks.getByPath(':ant:fatJar').outputs
}
into('docs') {
from('docs/md') {
includeEmptyDirs = false
include '**/*.md'
main {
distributionBaseName.set('proguard')
contents {
into("lib") {
def distProjects = [
project(":proguard-app"),
project(":gui"),
project(":retrace"),
project(":ant"),
]
from(distProjects.collect { it.tasks.named { it == "fatJar" } })
}
into('docs') {
from('docs/md') {
includeEmptyDirs = false
include '**/*.md'
}
}
from(rootDir) {
include 'bin/'
include 'examples/'
exclude 'examples/*/build'
exclude 'examples/*/.gradle'
include 'LICENSE'
include 'LICENSE_exception.md'
}
}
from(rootDir) {
include 'bin/'
include 'examples/'
exclude 'examples/*/build'
exclude 'examples/*/.gradle'
include 'LICENSE'
include 'LICENSE_exception.md'
}
}
}
}
distTar {

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,5 +1,5 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id 'com.github.johnrengelman.shadow'
id 'java-gradle-plugin'
@@ -24,12 +24,10 @@ gradlePlugin {
}
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.fromTarget(project.findProperty("target")))
}
}
configurations {
@@ -67,7 +65,7 @@ test {
def localRepo = file("$buildDir/local-repo")
task fatJar(type: ShadowJar) {
def fatJar = tasks.register("fatJar", ShadowJar) {
destinationDirectory.set(localRepo)
archiveFileName.set("proguard-gradle-${version}.jar")
from sourceSets.main.output

View File

@@ -1,7 +1,7 @@
proguardVersion = 7.8.0
proguardVersion = 7.9.0
# The version of ProGuardCORE that sub-projects are built with
proguardCoreVersion = 9.2.0
proguardCoreVersion = 9.3.0
gsonVersion = 2.11.0
kotlinVersion = 2.2.0
target = 1.8

View File

@@ -17,7 +17,7 @@ dependencies {
implementation 'org.apache.logging.log4j:log4j-core:2.24.2'
}
task fatJar(type: ShadowJar) {
def fatJar = tasks.register("fatJar", ShadowJar) {
destinationDirectory.set(file("$rootDir/lib"))
archiveFileName.set('proguardgui.jar')
from sourceSets.main.output
@@ -35,7 +35,7 @@ assemble.dependsOn fatJar
afterEvaluate {
publishing {
publications.getByName(project.name) {
publications.named(project.name) {
pom {
description = 'ProGuardGUI is an interface for ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode'
}

View File

@@ -16,7 +16,7 @@ dependencies {
jar.manifest.attributes('Implementation-Version': version)
task fatJar(type: ShadowJar) {
def fatJar = tasks.register("fatJar",ShadowJar) {
mainClassName = 'proguard.ProGuard'
destinationDirectory.set(file("$rootDir/lib"))
archiveFileName.set('proguard.jar')

View File

@@ -14,7 +14,7 @@ dependencies {
implementation project(':base')
}
task fatJar(type: ShadowJar) {
def fatJar = tasks.register("fatJar", ShadowJar) {
destinationDirectory.set(file("$rootDir/lib"))
archiveFileName.set('retrace.jar')
from sourceSets.main.output
@@ -31,7 +31,7 @@ assemble.dependsOn fatJar
afterEvaluate {
publishing {
publications.getByName(project.name) {
publications.named(project.name) {
pom {
description = "ReTrace is a companion tool for ProGuard and DexGuard that 'de-obfuscates' stack traces."
}