mirror of
https://github.com/Guardsquare/proguard.git
synced 2026-03-13 09:50:34 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6776d6a3aa | ||
|
|
e1e2689c72 | ||
|
|
14dcc28ba2 | ||
|
|
0c51b82946 | ||
|
|
68dcc9880e | ||
|
|
7a76843f0c | ||
|
|
d2170bad63 | ||
|
|
6dfd878ebb |
@@ -108,7 +108,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.0'
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.2'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package proguard.classfile.attribute;
|
||||
|
||||
public enum ProGuardOrigin implements LineOrigin
|
||||
{
|
||||
INLINED
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
218
base/src/test/kotlin/proguard/shrink/UsageMarkerTest.kt
Normal file
218
base/src/test/kotlin/proguard/shrink/UsageMarkerTest.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.0'
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.2'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.0'
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.2'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.guardsquare:proguard-gradle:7.8.0")
|
||||
classpath("com.guardsquare:proguard-gradle:7.8.2")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.0'
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.2'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user