blob: f7bba03633aa26f5ea78f8b42b1e5b108fbb8caf [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2017 Eric Lafortune @ GuardSquare
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package proguard.optimize.peephole;
import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.visitor.*;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import java.util.*;
/**
* This ClassVisitor disambiguates line numbers, in the classes that it
* visits. It shifts line numbers that originate from different classes
* (e.g. due to method inlining or class merging) to blocks that don't
* overlap with the main line numbers and with each other. The line numbers
* then uniquely identify the inlined and merged code in the classes.
*
* @author Eric Lafortune
*/
public class LineNumberLinearizer
extends SimplifiedVisitor
implements ClassVisitor,
MemberVisitor,
AttributeVisitor,
LineNumberInfoVisitor
{
private static final boolean DEBUG = false;
public static final int SHIFT_ROUNDING = 1000;
private static final int SHIFT_ROUNDING_LIMIT = 50000;
private Stack enclosingLineNumbers = new Stack();
private LineNumberInfo previousLineNumberInfo;
private int highestUsedLineNumber;
private int currentLineNumberShift;
// Implementations for ClassVisitor.
public void visitProgramClass(ProgramClass programClass)
{
// Find the highest line number in the entire class.
LineNumberRangeFinder lineNumberRangeFinder =
new LineNumberRangeFinder();
programClass.methodsAccept(new AllAttributeVisitor(true,
new AllLineNumberInfoVisitor(
lineNumberRangeFinder)));
// Are there any inlined line numbers?
if (lineNumberRangeFinder.hasSource())
{
// Remember the minimum initial shift.
highestUsedLineNumber = lineNumberRangeFinder.getHighestLineNumber();
// Shift the inlined line numbers.
programClass.methodsAccept(this);
}
}
// Implementations for MemberVisitor.
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
{
programMethod.attributesAccept(programClass, this);
}
// Implementations for AttributeVisitor.
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
codeAttribute.attributesAccept(clazz, method, this);
}
public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
{
if (DEBUG)
{
System.out.println("LineNumberLinearizer ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]:");
}
enclosingLineNumbers.clear();
previousLineNumberInfo = null;
// Process all line numbers.
lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
}
// Implementations for LineNumberInfoVisitor.
public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
{
String source = lineNumberInfo.getSource();
if (DEBUG)
{
System.out.print(" [" + lineNumberInfo.u2startPC + "] line " + lineNumberInfo.u2lineNumber + (source == null ? "" : " [" + source + "]"));
}
// Is it an inlined line number?
if (source != null)
{
ExtendedLineNumberInfo extendedLineNumberInfo =
(ExtendedLineNumberInfo)lineNumberInfo;
int lineNumber = extendedLineNumberInfo.u2lineNumber;
// Are we entering or exiting a new inlined block?
if (previousLineNumberInfo == null ||
previousLineNumberInfo.getSource() != source)
{
// Are we entering a new inlined block?
if (lineNumber != MethodInliner.INLINED_METHOD_END_LINE_NUMBER)
{
// Remember information about the inlined block.
enclosingLineNumbers.push(previousLineNumberInfo != null ?
new MyLineNumberBlock(currentLineNumberShift,
previousLineNumberInfo.u2lineNumber,
previousLineNumberInfo.getSource()) :
new MyLineNumberBlock(0, 0, null));
// 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));
// 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 = endLineNumber + currentLineNumberShift;
if (DEBUG)
{
System.out.print(" (enter with 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())
{
if (DEBUG)
{
System.err.println("Problem linearizing line numbers for optimized code ("+clazz.getName()+"."+method.getName(clazz)+")");
}
}
// Are we exiting an inlined block?
else
{
// Pop information about the enclosing line number.
MyLineNumberBlock lineNumberBlock =
(MyLineNumberBlock)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;
if (DEBUG)
{
System.out.print(" (exit to shift "+currentLineNumberShift+")");
}
}
}
else
{
if (DEBUG)
{
System.out.print(" (apply shift "+currentLineNumberShift+")");
}
// Apply the shift.
lineNumberInfo.u2lineNumber += currentLineNumberShift;
}
}
previousLineNumberInfo = lineNumberInfo;
if (DEBUG)
{
System.out.println(" -> line " + lineNumberInfo.u2lineNumber);
}
}
/**
* This class represents a block of line numbers that originates from the
* same inlined method.
*/
private static class MyLineNumberBlock
{
public final int lineNumberShift;
public final int enclosingLineNumber;
public final String enclosingSource;
public MyLineNumberBlock(int lineNumberShift,
int enclosingLineNumber,
String enclosingSource)
{
this.lineNumberShift = lineNumberShift;
this.enclosingLineNumber = enclosingLineNumber;
this.enclosingSource = enclosingSource;
}
}
}