Code_attribute.java
/*
* Copyright (c) 2001-2024, Jean Tessier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Jean Tessier nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jeantessier.classreader.impl;
import java.io.*;
import java.util.*;
import java.util.stream.*;
import org.apache.logging.log4j.*;
import com.jeantessier.classreader.Instruction;
import com.jeantessier.classreader.LocalVariable;
import com.jeantessier.classreader.*;
import com.jeantessier.text.*;
public class Code_attribute extends Attribute_info implements Iterable<Instruction>, com.jeantessier.classreader.Code_attribute {
private final int maxStack;
private final int maxLocals;
private final byte[] code;
private final Collection<ExceptionHandler> exceptionHandlers = new LinkedList<>();
private final Collection<Attribute_info> attributes = new LinkedList<>();
public Code_attribute(ConstantPool constantPool, Visitable owner, DataInput in, AttributeFactory attributeFactory) throws IOException {
super(constantPool, owner);
int byteCount = in.readInt();
LogManager.getLogger(getClass()).debug("Attribute length: {}", byteCount);
maxStack = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("Code max stack: {}", maxStack);
maxLocals = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("Code max locals: {}", maxLocals);
int codeLength = in.readInt();
LogManager.getLogger(getClass()).debug("Code length: {}", codeLength);
code = new byte[codeLength];
in.readFully(code);
LogManager.getLogger(getClass()).debug("Read {} byte(s): {}", () -> codeLength, () -> Hex.toString(code));
int exceptionTableLength = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("Reading {} exception handler(s) ...", exceptionTableLength);
IntStream.range (0, exceptionTableLength).forEach(i -> {
try {
LogManager.getLogger(getClass()).debug("Exception handler {}:", i);
exceptionHandlers.add(new ExceptionHandler(this, in));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
int attributeCount = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("Reading {} code attribute(s)", attributeCount);
IntStream.range(0, attributeCount).forEach(i -> {
try {
LogManager.getLogger(getClass()).debug("code attribute {}:", i);
attributes.add(attributeFactory.create(getConstantPool(), this, in));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
if (LogManager.getLogger(getClass()).isDebugEnabled()) {
LogManager.getLogger(getClass()).debug("Read instructions(s):");
forEach(this::logInstruction);
}
}
public int getMaxStack() {
return maxStack;
}
public int getMaxLocals() {
return maxLocals;
}
public byte[] getCode() {
return code;
}
public Iterator<Instruction> iterator() {
return new CodeIterator(this, code);
}
public Spliterator<Instruction> spliterator() {
return new CodeSpliterator(this, code);
}
public Stream<Instruction> stream() {
return StreamSupport.stream(spliterator(), false);
}
public Collection<ExceptionHandler> getExceptionHandlers() {
return exceptionHandlers;
}
public Collection<Attribute_info> getAttributes() {
return attributes;
}
public String toString() {
return "Code";
}
public String getAttributeName() {
return AttributeType.CODE.getAttributeName();
}
public void accept(Visitor visitor) {
visitor.visitCode_attribute(this);
}
private void logInstruction(Instruction instruction) {
StringBuilder message = new StringBuilder();
message.append(" ").append(instruction.getStart()).append(": ").append(instruction);
appendIndexedConstantPoolEntry(message, instruction);
appendIndexedLocalVariable(message, instruction);
appendOffset(message, instruction);
appendValue(message, instruction);
LogManager.getLogger(getClass()).debug(message);
}
private StringBuilder appendIndexedConstantPoolEntry(StringBuilder message, Instruction instruction) {
return switch (instruction.getOpcode()) {
case 0x12: // ldc
case 0x13: // ldc_w
case 0x14: // ldc2_w
case 0xb2: // getstatic
case 0xb3: // putstatic
case 0xb4: // getfield
case 0xb5: // putfield
case 0xb6: // invokevirtual
case 0xb7: // invokespecial
case 0xb8: // invokestatic
case 0xb9: // invokeinterface
case 0xbb: // new
case 0xbd: // anewarray
case 0xc0: // checkcast
case 0xc1: // instanceof
case 0xc5: // multianewarray
yield message.append(" ").append(instruction.getIndex()).append(" (").append(instruction.getIndexedConstantPoolEntry()).append(")");
default:
yield message;
};
}
private StringBuilder appendIndexedLocalVariable(StringBuilder message, Instruction instruction) {
return switch (instruction.getOpcode()) {
case 0x1a: // iload_0
case 0x1e: // lload_0
case 0x22: // fload_0
case 0x26: // dload_0
case 0x2a: // aload_0
case 0x3b: // istore_0
case 0x3f: // lstore_0
case 0x43: // fstore_0
case 0x47: // dstore_0
case 0x4b: // astore_0
case 0x1b: // iload_1
case 0x1f: // lload_1
case 0x23: // fload_1
case 0x27: // dload_1
case 0x2b: // aload_1
case 0x3c: // istore_1
case 0x40: // lstore_1
case 0x44: // fstore_1
case 0x48: // dstore_1
case 0x4c: // astore_1
case 0x1c: // iload_2
case 0x20: // lload_2
case 0x24: // fload_2
case 0x28: // dload_2
case 0x2c: // aload_2
case 0x3d: // istore_2
case 0x41: // lstore_2
case 0x45: // fstore_2
case 0x49: // dstore_2
case 0x4d: // astore_2
case 0x1d: // iload_3
case 0x21: // lload_3
case 0x25: // fload_3
case 0x29: // dload_3
case 0x2d: // aload_3
case 0x3e: // istore_3
case 0x42: // lstore_3
case 0x46: // fstore_3
case 0x4a: // dstore_3
case 0x4e: // astore_3
yield appendLocalVariable(message, instruction.getIndexedLocalVariable());
case 0x15: // iload
case 0x16: // llload
case 0x17: // fload
case 0x18: // dload
case 0x19: // aload
case 0x36: // istore
case 0x37: // lstore
case 0x38: // fstore
case 0x39: // dstore
case 0x3a: // astore
case 0xa9: // ret
case 0x84: // iinc
case 0xc4: // wide
message.append(" ").append(instruction.getIndex());
yield appendLocalVariable(message, instruction.getIndexedLocalVariable());
default:
yield message;
};
}
private StringBuilder appendLocalVariable(StringBuilder message, LocalVariable localVariable) {
String name = "n/a";
if (localVariable != null) {
name = localVariable.toString();
}
return message.append(" (").append(name).append(")");
}
private StringBuilder appendOffset(StringBuilder message, Instruction instruction) {
return switch (instruction.getOpcode()) {
case 0x99: // ifeq
case 0x9a: // ifne
case 0x9b: // iflt
case 0x9c: // ifge
case 0x9d: // ifgt
case 0x9e: // ifle
case 0x9f: // if_icmpeq
case 0xa0: // if_icmpne
case 0xa1: // if_icmplt
case 0xa2: // if_icmpge
case 0xa3: // if_icmpgt
case 0xa4: // if_icmple
case 0xa5: // if_acmpeq
case 0xa6: // if_acmpne
case 0xa7: // goto
case 0xa8: // jsr
case 0xc6: // ifnull
case 0xc7: // ifnonnull
case 0xc8: // goto_w
case 0xc9: // jsr_w
yield message.append(String.format(" %+d (to %d)", instruction.getOffset(), instruction.getStart() + instruction.getOffset()));
default:
yield message;
};
}
private StringBuilder appendValue(StringBuilder message, Instruction instruction) {
return switch (instruction.getOpcode()) {
case 0x10: // bipush
case 0x11: // sipush
yield message.append(" ").append(instruction.getValue());
case 0x84: // iinc
yield message.append(" by ").append(instruction.getValue());
case 0xc4: // wide
yield instruction.getByte(1) == 0x84 /* iinc */ ? message.append(" by ").append(instruction.getValue()) : message;
default:
yield message;
};
}
}