/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source 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.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.bytecode;
import com.caucho.util.ByteBuffer;
import com.caucho.util.IntArray;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Visitor for travelling the code.
*/
public class CodeEnhancer extends CodeVisitor {
static private final Logger log = Logger.getLogger(CodeEnhancer.class.getName());
private ByteBuffer _code;
private ArrayList<Jump> _jumps;
private ArrayList<Switch> _switches;
private boolean _changeLength;
// already visited targets
private IntArray _pendingTargets;
private IntArray _completedTargets;
public CodeEnhancer()
{
}
public CodeEnhancer(JavaClass javaClass, CodeAttribute code)
{
init(javaClass, code);
}
public void init(JavaClass javaClass, CodeAttribute codeAttr)
{
super.init(javaClass, codeAttr);
_code = new ByteBuffer();
byte []codeBuffer = codeAttr.getCode();
_code.add(codeBuffer, 0, codeBuffer.length);
_changeLength = false;
}
/**
* Analyzes the code for a method
*/
public void analyze(Analyzer analyzer, boolean allowFlow)
throws Exception
{
_pendingTargets = new IntArray();
_completedTargets = new IntArray();
analyzeImpl(analyzer, allowFlow, _pendingTargets, _completedTargets);
}
/**
* Returns the code buffer.
*/
public byte []getCode()
{
return _code.getBuffer();
}
/**
* Returns the length.
*/
public int getLength()
{
return _code.getLength();
}
/**
* Adds a byte to the code.
*/
public void addByte(int offset, int value)
{
insertCode(offset, 1);
_code.set(offset, value);
}
/**
* Adds a byte to the code.
*/
public void setByte(int offset, int value)
{
_code.set(offset, value);
}
/**
* Adds a short to the code.
*/
public void addShort(int offset, int value)
{
insertCode(offset, 2);
_code.set(offset + 0, value >> 8);
_code.set(offset + 1, value);
}
/**
* Adds a byte to the code.
*/
public void add(int offset, byte []buffer, int bufOffset, int length)
{
insertCode(offset, length);
_code.set(offset, buffer, bufOffset, length);
}
/**
* Removes a range from the code.
*/
public void remove(int offset, int count)
{
removeCode(offset, count);
}
/**
* Adds a byte to the code.
*/
public void addNulls(int offset, int count)
{
insertCode(offset, count);
}
/**
* Updates indices when adding a chunk of code. The operation at
* the given offset moves, e.g. adding 6 bytes to the beginning of
* the program moves the initial byte down by 6 and therefore needs
* to update the links as well.
*
* Therefore, enhancers which expand an opcode from 2 bytes to 3 bytes
* must insert the new bytes after the initial opcode.
*/
protected void insertCode(int offset, int count)
{
if (_jumps == null)
analyzeJumps();
// XXX: revisits the new code
if (offset <= _offset) {
_offset += count;
}
for (int i = 0; i < _jumps.size(); i++) {
Jump jump = _jumps.get(i);
jump.insert(this, offset, count);
}
ArrayList<CodeAttribute.ExceptionItem> exns = getExceptions();
for (int i = 0; i < exns.size(); i++) {
CodeAttribute.ExceptionItem exn = exns.get(i);
if (offset <= exn.getStart())
exn.setStart(exn.getStart() + count);
if (offset <= exn.getEnd())
exn.setEnd(exn.getEnd() + count);
if (offset <= exn.getHandler())
exn.setHandler(exn.getHandler() + count);
}
if (_pendingTargets != null) {
for (int i = _pendingTargets.size() - 1; i >= 0; i--) {
int target = _pendingTargets.get(i);
if (offset <= target)
_pendingTargets.set(i, target + count);
}
for (int i = _completedTargets.size() - 1; i >= 0; i--) {
int target = _completedTargets.get(i);
if (offset <= target)
_completedTargets.set(i, target + count);
}
}
for (int i = 0; i < _switches.size(); i++) {
Branch branch = _switches.get(i);
branch.insert(this, offset, count);
}
for (int i = 0; i < count; i++)
_code.add(offset, 0);
for (int i = 0; i < _switches.size(); i++) {
Switch branch = _switches.get(i);
branch.insertPad(this, offset, count);
}
}
protected void removeCode(int offset, int count)
{
if (_jumps == null)
analyzeJumps();
if (offset + count < _offset)
_offset -= count;
else if (offset <= _offset)
_offset = offset;
for (int i = 0; i < _jumps.size(); i++) {
Branch jump = _jumps.get(i);
jump.remove(this, offset, count);
}
ArrayList<CodeAttribute.ExceptionItem> exns = getExceptions();
for (int i = 0; i < exns.size(); i++) {
CodeAttribute.ExceptionItem exn = exns.get(i);
exn.setStart(remove(exn.getStart(), offset, count));
exn.setEnd(remove(exn.getEnd(), offset, count));
exn.setHandler(remove(exn.getHandler(), offset, count));
}
if (_pendingTargets != null) {
for (int i = _pendingTargets.size() - 1; i >= 0; i--) {
int target = _pendingTargets.get(i);
_pendingTargets.set(i, remove(target, offset, count));
}
for (int i = _completedTargets.size() - 1; i >= 0; i--) {
int target = _completedTargets.get(i);
_completedTargets.set(i, remove(target, offset, count));
}
}
for (int i = 0; i < _switches.size(); i++) {
Branch branch = _switches.get(i);
branch.remove(this, offset, count);
}
_code.remove(offset, count);
for (int i = 0; i < _switches.size(); i++) {
Switch branch = _switches.get(i);
branch.removePad(this, offset, count);
}
}
protected void analyzeJumps()
{
_jumps = new ArrayList<Jump>();
_switches = new ArrayList<Switch>();
_changeLength = true;
JumpAnalyzer analyzer = new JumpAnalyzer();
CodeVisitor visitor = new CodeVisitor(getJavaClass(), getCodeAttribute());
try {
visitor.analyze(analyzer);
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
}
/**
* Updates the code.
*/
public void update()
{
byte []code = new byte[_code.size()];
System.arraycopy(_code.getBuffer(), 0, code, 0, _code.size());
_codeAttr.setCode(code);
if (_changeLength) {
// XXX: really need more sophisticated solution
ArrayList<Attribute> attrList = getCodeAttribute().getAttributes();
for (int i = attrList.size() - 1; i >= 0; i--) {
Attribute attr = attrList.get(i);
if (attr.getName().equals("LineNumberTable"))
attrList.remove(i);
}
}
}
private int remove(int pc, int offset, int count)
{
if (pc < offset)
return pc;
else if (pc < offset + count)
return offset;
else
return pc - count;
}
abstract static class Branch {
abstract void insert(CodeEnhancer enhancer, int offset, int count);
abstract void remove(CodeEnhancer enhancer, int offset, int count);
}
static class Jump extends Branch {
private int _src;
private int _delta;
Jump(int src, int delta)
{
_src = src;
_delta = delta;
}
void insert(CodeEnhancer enhancer, int offset, int count)
{
// offset is before the jump
if (offset <= _src && offset <= _src + _delta) {
_src += count;
}
// offset is inside a forward jump
else if (_src < offset && offset < _src + _delta) {
_delta += count;
enhancer.setShort(_src + 1, _delta);
}
// offset is inside a backward jump
else if (_src + _delta <= offset && offset <= _src) {
_delta -= count;
enhancer.setShort(_src + 1, _delta);
_src += count;
}
}
void remove(CodeEnhancer enhancer, int offset, int count)
{
// offset is before the jump
if (offset <= _src && offset <= _src + _delta) {
_src -= count;
}
// offset is inside a forward jump
else if (_src < offset && offset < _src + _delta) {
_delta -= count;
enhancer.setShort(_src + 1, _delta);
}
// offset is inside a backward jump
else if (_src + _delta <= offset && offset <= _src) {
_delta += count;
enhancer.setShort(_src + 1, _delta);
_src -= count;
}
}
}
static class Switch extends Branch {
private int _oldSrc;
private int _src;
private int []_offsets;
Switch(int src)
{
_src = src;
_oldSrc = src;
}
protected void setOffsets(int []offsets)
{
_offsets = offsets;
}
void insert(CodeEnhancer enhancer, int offset, int count)
{
for (int i = 0; i < _offsets.length; i++) {
int delta = enhancer.getInt(_offsets[i]);
if (offset <= _src && _src + delta <= offset)
enhancer.setInt(_offsets[i], delta - count);
else if (_src < offset && offset < _src + delta)
enhancer.setInt(_offsets[i], delta + count);
if (offset <= _src + 1)
_offsets[i] += count;
}
if (offset < _src)
_src += count;
}
void remove(CodeEnhancer enhancer, int offset, int count)
{
for (int i = 0; i < _offsets.length; i++) {
int delta = enhancer.getInt(_offsets[i]);
if (offset <= _src && _src + delta <= offset)
enhancer.setInt(_offsets[i], delta + count);
else if (_src < offset && offset < _src + delta)
enhancer.setInt(_offsets[i], delta - count);
if (offset <= _src + 1)
_offsets[i] -= count;
}
if (offset < _src)
_src -= count;
}
void insertPad(CodeEnhancer enhancer, int offset, int count)
{
// offset is before the jump
if (_oldSrc != _src) {
int oldPad = (4 - (_oldSrc + 1) % 4) % 4;
int newPad = (4 - (_src + 1) % 4) % 4;
_oldSrc = _src;
if (newPad < oldPad)
enhancer.remove(_src + 1, oldPad - newPad);
else if (oldPad < newPad)
enhancer.addNulls(_src + 1, newPad - oldPad);
}
}
void removePad(CodeEnhancer enhancer, int offset, int count)
{
// offset is before the jump
if (_oldSrc != _src) {
int oldPad = (4 - (_oldSrc + 1) % 4) % 4;
int newPad = (4 - (_src + 1) % 4) % 4;
_oldSrc = _src;
if (newPad < oldPad)
enhancer.remove(_src + 1, oldPad - newPad);
else if (oldPad < newPad)
enhancer.addNulls(_src + 1, newPad - oldPad);
}
}
public boolean equals(Object v)
{
if (! (v instanceof Switch))
return false;
Switch s = (Switch) v;
return _src == s._src;
}
}
static class TableSwitch extends Switch {
TableSwitch(int src, CodeVisitor visitor)
{
super(src);
int arg = src + 1;
arg += (4 - arg % 4) % 4;
int low = visitor.getInt(arg + 4);
int high = visitor.getInt(arg + 8);
int []offsets = new int[high - low + 2];
offsets[0] = arg;
for (int i = 0; i <= high - low; i++) {
offsets[i + 1] = arg + 12 + i * 4;
}
setOffsets(offsets);
}
}
static class LookupSwitch extends Switch {
LookupSwitch(int src, CodeVisitor visitor)
{
super(src);
int arg = src + 1;
arg += (4 - arg % 4) % 4;
int n = visitor.getInt(arg + 4);
int []offsets = new int[n + 1];
offsets[0] = arg;
for (int i = 0; i < n; i++) {
offsets[i + 1] = arg + 8 + i * 8 + 4;
}
setOffsets(offsets);
}
}
class JumpAnalyzer extends Analyzer {
public void analyze(CodeVisitor visitor)
throws Exception
{
if (visitor.isSwitch()) {
int src = visitor.getOffset();
switch (visitor.getOpcode()) {
case TABLESWITCH:
{
TableSwitch branch = new TableSwitch(src, visitor);
if (! _switches.contains(branch))
_switches.add(branch);
break;
}
case LOOKUPSWITCH:
{
LookupSwitch branch = new LookupSwitch(src, visitor);
if (! _switches.contains(branch))
_switches.add(branch);
break;
}
}
}
else if (visitor.isBranch()) {
int src = visitor.getOffset();
int offset = visitor.getShortArg(1);
_jumps.add(new Jump(src, offset));
}
}
}
}