Compare commits

...

17 Commits
v7.7 ... v7.8.1

Author SHA1 Message Date
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
niccolo.piazzesi
869ce156b1 Update dependency, update samples, update release notes for 7.8 2025-10-07 13:34:28 +02:00
Fergal Whyte
124b33e473 Bump ProGuard to 7.8.0 and ProGuardCORE to 9.2.0 2025-10-07 13:34:16 +02:00
Robin Lefever
b8a62c8ca8 Bump Nexus publish plugin version 2025-08-25 11:53:54 +02:00
niccolo.piazzesi
886477806c Temporarily downgrade version in examples until release is properly done 2025-08-22 10:47:01 +02:00
Robin Lefever
7fc907d1fb Update ProGuardCORE version to support Kotlin 2.2 2025-08-22 06:48:58 +02:00
piazzesiNiccolo-GS
b10346ba32 Upgrade and cleanup ci configuration (#492) 2025-08-21 09:57:43 +02:00
Thomas Vochten
f2ced20be4 Fix ktlint issue in ClassUsageMarkerTest 2025-08-21 08:28:19 +02:00
huqiuser
35ea6d587f fix NullPointerException for Kotlin typealias of typealias of lambda (#490) 2025-08-20 16:25:40 +02:00
Eric Salemi
eea0ccbe8f Migrate to OSSRH staging API service 2025-08-20 08:18:34 +02:00
Joren Hannes
bee74a9963 Run kotlin linter 2025-08-13 14:56:39 +02:00
Oberon Swings
4b4aa93335 Fix retrace regex mentioned in the manual. 2025-08-13 14:56:37 +02:00
Nolij
4781f5898f Exclude annotation members from aggressive overloading (#453)
* Exclude annotation members from aggressive overloading
2025-08-11 09:15:43 +02:00
Jelle De Coninck
1f9a4a1b94 Remove AppSweep page from manual
Summary: Remove AppSweep page from manual and remove link from toolbar.
2025-06-13 15:52:18 +02:00
Blend Hamiti
40f9222bc3 Add mac finder files to gitignore 2025-05-02 16:28:52 +02:00
31 changed files with 602 additions and 206 deletions

View File

@@ -15,19 +15,21 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Checkout
uses: actions/checkout@v4
with:
path: proguard-main
- uses: actions/setup-java@v1
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: 8
- uses: eskatos/gradle-command-action@v1
with:
build-root-directory: proguard-main/
wrapper-directory: proguard-main/
arguments: test :base:testAllJavaVersions :base:jacocoTestReport jar --info
- name: Setup gradle
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # version 4.4.2
- name: Test
run: ./gradlew test :base:testAllJavaVersions :base:jacocoTestReport jar --info
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # version 5.6.2
if: success() || failure() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ local.properties
/lib/
docs/html
.kotlin
.DS_Store

View File

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

View File

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

View File

@@ -78,7 +78,8 @@ implements MemberVisitor
String descriptor = member.getDescriptor(clazz);
// Check whether we're allowed to do aggressive overloading
if (!allowAggressiveOverloading)
// Annotations are always excluded from aggressive overloading due to JVM limitations
if (!allowAggressiveOverloading || clazz.extendsOrImplements(ClassConstants.NAME_JAVA_LANG_ANNOTATION_ANNOTATION))
{
// Trim the return argument from the descriptor if not.
// Works for fields and methods alike.

View File

@@ -109,7 +109,8 @@ public class MemberNameConflictFixer implements MemberVisitor
String descriptor = member.getDescriptor(clazz);
// Check whether we're allowed to overload aggressively.
if (!allowAggressiveOverloading)
// Annotations are always excluded from aggressive overloading due to JVM limitations
if (!allowAggressiveOverloading || clazz.extendsOrImplements(ClassConstants.NAME_JAVA_LANG_ANNOTATION_ANNOTATION))
{
// Trim the return argument from the descriptor if not.
// Works for fields and methods alike.

View File

@@ -81,7 +81,8 @@ implements MemberVisitor
String descriptor = member.getDescriptor(clazz);
// Check whether we're allowed to overload aggressively.
if (!allowAggressiveOverloading)
// Annotations are always excluded from aggressive overloading due to JVM limitations
if (!allowAggressiveOverloading || clazz.extendsOrImplements(ClassConstants.NAME_JAVA_LANG_ANNOTATION_ANNOTATION))
{
// Trim the return argument from the descriptor if not.
// Works for fields and methods alike.

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

@@ -1886,6 +1886,7 @@ implements ClassVisitor,
markAsUsed(kotlinPropertyMetadata.receiverType);
markAsUsed(kotlinPropertyMetadata.typeParameters);
markAsUsed(kotlinPropertyMetadata.setterParameters);
markAsUsed(kotlinPropertyMetadata.setterParameter);
markAsUsed(kotlinPropertyMetadata.type);
if (kotlinPropertyMetadata.flags.hasAnnotations &&
@@ -2068,7 +2069,10 @@ implements ClassVisitor,
else if (kotlinTypeMetadata.aliasName != null && !isUsed(kotlinTypeMetadata.referencedTypeAlias))
{
markAsUsed(kotlinTypeMetadata.referencedTypeAlias);
kotlinTypeMetadata.referencedTypeAlias.accept(null, null, this);
kotlinTypeMetadata.referencedTypeAlias.accept(
kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer.ownerReferencedClass,
kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer,
this);
}
markAsUsed(kotlinTypeMetadata.typeArguments);

View File

@@ -192,6 +192,7 @@ implements KotlinMetadataVisitor,
kotlinPropertyMetadata.setterSignature = null;
kotlinPropertyMetadata.referencedSetterMethod = null;
kotlinPropertyMetadata.flags.isVar = false;
kotlinPropertyMetadata.setterParameter = null;
kotlinPropertyMetadata.setterParameters.clear();
}

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,8 +129,6 @@ public class UsageMarker
new LocalVariableTypeUsageMarker(classUsageMarker)
))));
// Mark interfaces that have to be kept.
programClassPool.classesAccept(new InterfaceUsageMarker(classUsageMarker));
if (configuration.keepKotlinMetadata)
{

View File

@@ -0,0 +1,47 @@
package proguard.obfuscate
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.equals.shouldBeEqual
import proguard.classfile.ProgramClass
import proguard.testutils.ClassPoolBuilder
import proguard.testutils.JavaSource
import java.util.HashMap
class AnnotationTest : FreeSpec({
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public @interface Test {
boolean testBoolean1() default false;
boolean testBoolean2() default true;
String testString1() default "";
}
""".trimIndent(),
),
)
val testClass = programClassPool.getClass("Test") as ProgramClass
"Annotation members should be excluded from aggressive overloading" {
val descriptorMap = HashMap<String, Map<String, String>>()
testClass.methodsAccept(
MemberObfuscator(
true,
SimpleNameFactory(),
descriptorMap,
),
)
descriptorMap shouldBeEqual
mapOf(
"()" to
mapOf(
"a" to "testBoolean1",
"b" to "testBoolean2",
"c" to "testString1",
),
)
}
})

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

@@ -14,6 +14,8 @@ import proguard.classfile.Clazz
import proguard.classfile.Member
import proguard.classfile.attribute.annotation.visitor.AllElementValueVisitor
import proguard.classfile.attribute.visitor.AllAttributeVisitor
import proguard.classfile.kotlin.KotlinTypeAliasMetadata
import proguard.classfile.kotlin.visitor.AllTypeAliasVisitor
import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor
import proguard.classfile.util.EnumFieldReferenceInitializer
import proguard.classfile.visitor.AllMethodVisitor
@@ -266,4 +268,39 @@ class ClassUsageMarkerTest : StringSpec({
val fooInterface = programClassPool.getClass("test/Interface").findMethod("foo", null)
fooInterface should beMarkedWith(usageMarker)
}
"Given Kotlin `typealias` declarations where one aliases another" {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
KotlinSource(
"KotlinTypeAlias.kt",
"""
typealias P = () -> Unit
typealias A = P
""".trimIndent(),
),
kotlincArguments = listOf("-language-version=1.9"),
)
// Obtain the `typealias` declarations A and P
val typeAliasList = mutableListOf<KotlinTypeAliasMetadata>()
programClassPool.classesAccept(
ReferencedKotlinMetadataVisitor(
AllTypeAliasVisitor { _, _, kotlinTypeAliasMetadata ->
typeAliasList.add(kotlinTypeAliasMetadata)
},
),
)
// Ensure only `typealias` A and `typealias` P are present
typeAliasList.map { it.name }.toSet() shouldBe setOf("A", "P")
// Both `typealias` A and `typealias` P should be marked as used by usageMarker.
val usageMarker = SimpleUsageMarker()
val classUsageMarker = ClassUsageMarker(usageMarker)
programClassPool.classesAccept(classUsageMarker)
typeAliasList.forEach {
it should beMarkedWith(usageMarker)
}
}
})

View File

@@ -0,0 +1,150 @@
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
@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

@@ -24,6 +24,7 @@ task buildDocumentation(type: Exec) {
nexusPublishing {
repositories {
sonatype {
nexusUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/")
username = findProperty('PROGUARD_STAGING_USERNAME')
password = findProperty('PROGUARD_STAGING_PASSWORD')
}

View File

@@ -1,4 +1,4 @@
Welcome to the manual for **ProGuard** version 7.7 ([what's new?](releasenotes.md)).
Welcome to the manual for **ProGuard** version 7.8.0 ([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

@@ -1,3 +1,20 @@
## 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
- Add support for Kotlin 2.2.
### Java support
- Add support for Java 25. (#481)
## Version 7.7
### Java support

View File

@@ -1,8 +0,0 @@
# AppSweep
[AppSweep](https://appsweep.guardsquare.com/) is a free online app security testing tool that allows you to find and fix security
issues in your Android app's code and dependencies.
<center>
![](appsweep.png)
</center>

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -51,7 +51,7 @@ The following options are supported:
de-obfuscate more general types of input than just stack traces. A relatively
simple expression like this works for basic stack trace formats:
(?:.*? at %c\.%m\(%s(?::%l)?\))|(?:(?:.*?[:"] +)?%c(?::.*)?)
(?:.*? at %c\.%m\(%s(?::%l)?\))|(?:(?:.*?[:\"] +)?%c(?::.*)?)
It for instance matches the following lines:

View File

@@ -91,7 +91,6 @@ nav:
- Tools:
- ReTrace: manual/tools/retrace.md
- Playground: manual/tools/playground.md
- AppSweep: manual/tools/appsweep.md
- Troubleshooting:
- Overview: manual/troubleshooting/troubleshooting.md
- Limitations: manual/troubleshooting/limitations.md

View File

@@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.7.0'
classpath 'com.guardsquare:proguard-gradle:7.8.0'
}
}
@@ -31,16 +31,12 @@ test {
useJUnitPlatform()
}
compileKotlin {
kotlinOptions.jvmTarget = '1.8'
}
compileTestKotlin {
kotlinOptions.jvmTarget = '1.8'
kotlin {
jvmToolchain(8)
}
application {
mainClass = 'AppKt'
mainClass.set('com.example.AppKt')
}
ext.baseCoordinates = "${project.name}-${project.version}"

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

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

View File

@@ -1,9 +1,9 @@
proguardVersion = 7.7.0
proguardVersion = 7.8.1
# The version of ProGuardCORE that sub-projects are built with
proguardCoreVersion = 9.1.10
proguardCoreVersion = 9.2.0
gsonVersion = 2.11.0
kotlinVersion = 2.1.0
kotlinVersion = 2.2.0
target = 1.8
# Optionally compile the WTK plugin.

View File

@@ -5,7 +5,7 @@ pluginManagement {
useVersion '8.1.1'
}
if (requested.id.id == 'io.github.gradle-nexus.publish-plugin') {
useVersion '1.1.0'
useVersion '2.0.0'
}
}
}