org.aspectj.weaver.WeaverStateInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aspectjweaver Show documentation
Show all versions of aspectjweaver Show documentation
The AspectJ weaver introduces advices to java classes
/* *******************************************************************
* Copyright (c) 2002-2019 Palo Alto Research Center, Incorporated (PARC).
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v 2.0
* which accompanies this distribution and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
*
* Contributors:
* PARC initial implementation
* ******************************************************************/
package org.aspectj.weaver;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.aspectj.bridge.IMessage;
import org.aspectj.weaver.AjAttribute.WeaverVersionInfo;
/**
* WeaverStateInfo represents how a type was processed. It is used by the weaver to determine how a type was previously treated and
* whether reweaving is allowed. The format in the data stream is:
*
* Byte: Kind. UNTOUCHED|WOVEN|EXTENDED - If extended it can have two extra bits set 'REWEAVABLE' and 'REWEAVABLE_COMPRESSION_BIT'
* Short: typeMungerCount - how many type mungers have affected this type <UnresolvedType & ResolvedTypeMunger>: The type mungers
* themselves If we are reweavable then we also have: Short: Number of aspects that touched this type in some way when it was
* previously woven <String> The fully qualified name of each type Int: Length of class file data (i.e. the unwovenclassfile)
* Byte[]: The class file data, compressed if REWEAVABLE_COMPRESSION_BIT set.
*
* @author Andy Clement
*/
public class WeaverStateInfo {
private List typeMungers;
private boolean oldStyle;
private boolean reweavable;
private boolean reweavableCompressedMode; // If true, unwovenClassFile is uncompressed on read
private boolean reweavableDiffMode; // if true, unwovenClassFile is written and read as a diff
// These must exist in the world for reweaving to be valid.
// It is a set of signatures 'La/b/c/D;'
private Set aspectsAffectingType;
private byte[] unwovenClassFile; // Original 'untouched' class file
private static boolean reweavableDefault = true; // ajh02: changed from false;
private static boolean reweavableCompressedModeDefault = false;
private static boolean reweavableDiffModeDefault = true;
// when serializing the WeaverStateInfo we come to adding the reweavable data,
// we'd like to add a diff of the unwovenClassFile and the wovenClassFile,
// but we don't have the wovenClassFile yet as we're still in the process of making it.
// so we put this key there instead as a stub.
// Then when the wovenClassFile has been made, replaceKeyWithDiff is called.
private static byte[] key = { -51, 34, 105, 56, -34, 65, 45, 78, -26, 125, 114, 97, 98, 1, -1, -42 };
private boolean unwovenClassFileIsADiff = false;
int compressionEnabled = 0; // 0=dont know, 1=no, 2=yes
private void checkCompressionEnabled() {
if (compressionEnabled == 0) {
// work it out!
compressionEnabled = 1;
try {
String value = System.getProperty("aspectj.compression.weaverstateinfo", "false");
if (value.equalsIgnoreCase("true")) {
System.out.println("ASPECTJ: aspectj.compression.weaverstateinfo=true: compressing weaverstateinfo");
compressionEnabled = 2;
}
} catch (Throwable t) {
// nop
}
}
}
private WeaverStateInfo() {
// this(new ArrayList(), false,reweavableDefault,reweavableCompressedModeDefault,reweavableDiffModeDefault);
}
public WeaverStateInfo(boolean reweavable) {
this(new ArrayList<>(), false, reweavable, reweavableCompressedModeDefault, reweavableDiffModeDefault);
}
private WeaverStateInfo(List typeMungers, boolean oldStyle, boolean reweavableMode, boolean reweavableCompressedMode,
boolean reweavableDiffMode) {
this.typeMungers = typeMungers;
this.oldStyle = oldStyle;
this.reweavable = reweavableMode;
this.reweavableCompressedMode = reweavableCompressedMode;
this.reweavableDiffMode = reweavableMode ? reweavableDiffMode : false;
this.aspectsAffectingType = new HashSet<>();
this.unwovenClassFile = null;
}
public static void setReweavableModeDefaults(boolean mode, boolean compress, boolean diff) {
reweavableDefault = mode;
reweavableCompressedModeDefault = compress;
reweavableDiffModeDefault = diff;
}
private static final int UNTOUCHED = 0, WOVEN = 2, EXTENDED = 3;
// Use 'bits' for these capabilities - only valid in EXTENDED mode
private static final byte REWEAVABLE_BIT = 1 << 4;
private static final byte REWEAVABLE_COMPRESSION_BIT = 1 << 5;
private static final byte REWEAVABLE_DIFF_BIT = 1 << 6;
/** See comments on write() */
public static final WeaverStateInfo read(VersionedDataInputStream s, ISourceContext context) throws IOException {
byte b = s.readByte();
boolean isReweavable = ((b & REWEAVABLE_BIT) != 0);
if (isReweavable) {
b = (byte) (b - REWEAVABLE_BIT);
}
boolean isReweavableCompressed = ((b & REWEAVABLE_COMPRESSION_BIT) != 0);
if (isReweavableCompressed) {
b = (byte) (b - REWEAVABLE_COMPRESSION_BIT);
}
boolean isReweavableDiff = ((b & REWEAVABLE_DIFF_BIT) != 0);
if (isReweavableDiff) {
b = (byte) (b - REWEAVABLE_DIFF_BIT);
}
switch (b) {
case UNTOUCHED:
throw new RuntimeException("unexpected UNWOVEN");
case WOVEN:
return new WeaverStateInfo(Collections.emptyList(), true, isReweavable, isReweavableCompressed, isReweavableDiff);
case EXTENDED:
boolean isCompressed = false;
if (s.isAtLeast169()) {
isCompressed = s.readBoolean();
}
int n = s.readShort();
List l = new ArrayList<>();
for (int i = 0; i < n; i++) {
// conditional on version
UnresolvedType aspectType = null;
if (isCompressed) {
int cpIndex = s.readShort();
String signature = s.readUtf8(cpIndex);
if (signature.charAt(0) == '@') { // '@missing@'
aspectType = ResolvedType.MISSING;
} else {
aspectType = UnresolvedType.forSignature(signature);
}
} else {
aspectType = UnresolvedType.read(s);
}
ResolvedTypeMunger typeMunger = ResolvedTypeMunger.read(s, context);
l.add(new Entry(aspectType, typeMunger));
}
WeaverStateInfo wsi = new WeaverStateInfo(l, false, isReweavable, isReweavableCompressed, isReweavableDiff);
readAnyReweavableData(wsi, s, isCompressed);
return wsi;
}
throw new RuntimeException("bad WeaverState.Kind: " + b + ". File was :"
+ (context == null ? "unknown" : context.makeSourceLocation(0, 0).toString()));
}
private static class Entry {
public UnresolvedType aspectType;
public ResolvedTypeMunger typeMunger;
public Entry(UnresolvedType aspectType, ResolvedTypeMunger typeMunger) {
this.aspectType = aspectType;
this.typeMunger = typeMunger;
}
public String toString() {
return "<" + aspectType + ", " + typeMunger + ">";
}
}
/**
* Serialize the WeaverStateInfo. Various bits are set within the 'kind' flag to indicate the structure of the attribute. In
* reweavable diff mode a 'marker' is inserted at the start of the attribute to indicate where the final calculated diff should
* be inserted. When the key is replaced with the diff, the 'kind' byte moves to the front of the attribute - thats why in the
* read logic you'll see it expecting the kind as the first byte.
*/
public void write(CompressingDataOutputStream s) throws IOException {
checkCompressionEnabled();
if (oldStyle || reweavableCompressedMode) {
throw new RuntimeException("shouldn't be writing this");
}
byte weaverStateInfoKind = EXTENDED;
if (reweavable) {
weaverStateInfoKind |= REWEAVABLE_BIT;
}
if (reweavableDiffMode) {
s.write(key); // put key in so we can replace it with the diff later
weaverStateInfoKind |= REWEAVABLE_DIFF_BIT;
}
s.writeByte(weaverStateInfoKind);
// Tag whether the remainder of the data is subject to cp compression
try {
s.compressionEnabled = compressionEnabled == 2;
s.writeBoolean(s.canCompress());
int n = typeMungers.size();
s.writeShort(n);
for (Entry e : typeMungers) {
if (s.canCompress()) {
s.writeCompressedSignature(e.aspectType.getSignature());
} else {
e.aspectType.write(s);
}
e.typeMunger.write(s);
}
writeAnyReweavableData(this, s, s.canCompress());
} finally {
s.compressionEnabled = true;
}
}
private final static byte[] NO_BYTES = new byte[0];
/**
* If the weaver is ever invoked in over weaving mode, we should
* not include the key when writing out, it won't be replaced later.
* If we turn off the reweaving flag that unfortunately removes
* the 'what aspects have been woven into this type' list which we
* want to keep as it helps overweaving avoid weaving an aspect in
* twice.
*/
public void markOverweavingInUse() {
reweavableDiffMode = false;
unwovenClassFile = NO_BYTES;
}
public void addConcreteMunger(ConcreteTypeMunger munger) {
typeMungers.add(new Entry(munger.getAspectType(), munger.getMunger()));
}
public String toString() {
return "WeaverStateInfo(aspectsAffectingType=" + aspectsAffectingType + "," + typeMungers + ", " + oldStyle + ")";
}
public List getTypeMungers(ResolvedType onType) {
World world = onType.getWorld();
List ret = new ArrayList<>();
for (Entry entry : typeMungers) {
ResolvedType aspectType = world.resolve(entry.aspectType, true);
if (aspectType.isMissing()) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.ASPECT_NEEDED, entry.aspectType, onType),
onType.getSourceLocation(), null);
continue;
}
ret.add(new TemporaryTypeMunger(entry.typeMunger, aspectType));
}
return ret;
}
public boolean isOldStyle() {
return oldStyle;
}
public byte[] getUnwovenClassFileData() {
return unwovenClassFile;
}
public byte[] getUnwovenClassFileData(byte wovenClassFile[]) {
if (unwovenClassFileIsADiff) {
unwovenClassFile = applyDiff(wovenClassFile, unwovenClassFile);
unwovenClassFileIsADiff = false;
}
return unwovenClassFile;
}
public void setUnwovenClassFileData(byte[] data) {
unwovenClassFile = data;
}
public boolean isReweavable() {
return reweavable;
}
public void setReweavable(boolean rw) {
reweavable = rw;
}
public void addAspectsAffectingType(Collection aspects) {
aspectsAffectingType.addAll(aspects);
}
public void addAspectAffectingType(String aspectSignature) {
aspectsAffectingType.add(aspectSignature);
}
public Set getAspectsAffectingType() {
return this.aspectsAffectingType;
}
private static void readAnyReweavableData(WeaverStateInfo wsi, VersionedDataInputStream s, boolean compressed)
throws IOException {
if (wsi.isReweavable()) {
// Load list of aspects that need to exist in the world for reweaving to be 'legal'
int numberAspectsAffectingType = s.readShort();
for (int i = 0; i < numberAspectsAffectingType; i++) {
String str = null;
if (compressed) {
str = s.readSignature();
} else {
str = s.readUTF();
// Prior to 1.6.9 we were writing out names (com.foo.Bar) rather than signatures (Lcom/foo/Bar;)
// From 1.6.9 onwards we write out signatures (pr319431)
if (s.getMajorVersion() < WeaverVersionInfo.WEAVER_VERSION_AJ169) {
// It is a name, make it a signature
StringBuilder sb = new StringBuilder();
sb.append("L").append(str.replace('.', '/')).append(";");
str = sb.toString();
}
}
wsi.addAspectAffectingType(str);
}
int unwovenClassFileSize = s.readInt();
byte[] classData = null;
// the unwovenClassFile may have been compressed:
if (wsi.reweavableCompressedMode) {
classData = new byte[unwovenClassFileSize];
ZipInputStream zis = new ZipInputStream(s);
ZipEntry zen = zis.getNextEntry();
int current = 0;
int bytesToGo = unwovenClassFileSize;
while (bytesToGo > 0) {
int amount = zis.read(classData, current, bytesToGo);
current += amount;
bytesToGo -= amount;
}
zis.closeEntry();
if (bytesToGo != 0) {
throw new IOException("ERROR whilst reading compressed reweavable data, expected " + unwovenClassFileSize
+ " bytes, only found " + current);
}
} else {
classData = new byte[unwovenClassFileSize];
if (unwovenClassFileSize != 0) {
int bytesread = s.read(classData);
if (bytesread != unwovenClassFileSize) {
throw new IOException("ERROR whilst reading reweavable data, expected " + unwovenClassFileSize
+ " bytes, only found " + bytesread);
}
}
}
// if it was diffMode we'll have to remember to apply the diff if someone
// asks for the unwovenClassFile
wsi.unwovenClassFileIsADiff = wsi.reweavableDiffMode;
wsi.setUnwovenClassFileData(classData);
}
}
/**
* Here is the cleverness for reweavable diff mode. The class file on disk contains, inside the weaverstateinfo attribute, a
* diff that can be applied to 'itself' to recover the original class - which can then be rewoven.
*/
public byte[] replaceKeyWithDiff(byte wovenClassFile[]) {
// we couldn't have made the diff earlier
// as we didn't have the wovenClassFile
// so we left a key there as a marker to come back to
if (reweavableDiffMode) {
ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
DataOutputStream s = new DataOutputStream(arrayStream);
int endOfKey = findEndOfKey(wovenClassFile);
int startOfKey = endOfKey - key.length;
// the length of the wsi attribute is written infront of it in the classFile,
// swapping the diff for the key will probably change the length of the wsi,
// so we'll have to fiddle with the four 'int length' bytes
int oldLengthLocation = startOfKey - 4;
int oldLength = readInt(wovenClassFile, oldLengthLocation);
wovenClassFile = deleteInArray(wovenClassFile, startOfKey, endOfKey); // delete the key
byte[] wovenClassFileUpToWSI = new byte[oldLengthLocation];
System.arraycopy(wovenClassFile, 0, wovenClassFileUpToWSI, 0, oldLengthLocation);
byte[] diff = generateDiff(wovenClassFileUpToWSI, unwovenClassFile);
try { // put the length of the diff infront of the diff
s.writeInt(diff.length);
s.write(diff);
} catch (IOException e) {
}
diff = arrayStream.toByteArray();
// we have to swap the oldLength for the new one,
// and add the diff, using the oldLength to work out where it should go :)
int newLength = oldLength - key.length + diff.length;
byte newLengthBytes[] = serializeInt(newLength);
// swap in the serialized newLength for the oldOne:
wovenClassFile[oldLengthLocation] = newLengthBytes[0];
wovenClassFile[oldLengthLocation + 1] = newLengthBytes[1];
wovenClassFile[oldLengthLocation + 2] = newLengthBytes[2];
wovenClassFile[oldLengthLocation + 3] = newLengthBytes[3];
// add the diff
wovenClassFile = insertArray(diff, wovenClassFile, oldLengthLocation + 4 + oldLength - key.length);
}
return wovenClassFile;
}
private static final int findEndOfKey(byte[] wovenClassFile) {
// looks through the classfile backwards (as the attributes are all near the end)
for (int i = wovenClassFile.length - 1; i > 0; i--) {
if (endOfKeyHere(wovenClassFile, i)) {
return i + 1;
}
}
throw new RuntimeException("key not found in wovenClassFile"); // should never happen
}
private static final boolean endOfKeyHere(byte lookIn[], int i) {
for (int j = 0; j < key.length; j++) {
if (key[key.length - 1 - j] != lookIn[i - j]) {
return false;
}
}
return true;
}
private static final byte[] insertArray(byte toInsert[], byte original[], int offset) {
byte result[] = new byte[original.length + toInsert.length];
System.arraycopy(original, 0, result, 0, offset);
System.arraycopy(toInsert, 0, result, offset, toInsert.length);
System.arraycopy(original, offset, result, offset + toInsert.length, original.length - offset);
return result;
}
private static final int readInt(byte[] a, int offset) {
ByteArrayInputStream b = new ByteArrayInputStream(a, offset, 4);
DataInputStream d = new DataInputStream(b);
int length = -1;
try {
length = d.readInt();
} catch (IOException e) {
throw (new RuntimeException("readInt called with a bad array or offset")); // should never happen
}
return length;
}
private static final byte[] deleteInArray(byte a[], int start, int end) {
int lengthToDelete = end - start;
byte result[] = new byte[a.length - lengthToDelete]; // make a new array
System.arraycopy(a, 0, result, 0, start); // copy in the bit before the deleted bit
System.arraycopy(a, end, result, start, a.length - end); // copy in the bit after the deleted bit
return result;
}
// ajh02: a quick note about the diff format...
//
// classfiles consist of:
// 8 bytes: magic number and minor and major versions,
// 2 bytes: its constant pool count
// n bytes: the rest of the class file
//
// weaving a classfile never changes the classfile's first 8 bytes,
// and after the constant pool count there's usually a run of bytes that weaving didn't change
// hereafter referred to as the run
//
// so the diff consists of:
// 2 bytes: its constant pool count
// 4 bytes: length of the run
// n bytes: the rest of the unwovenClassFile
byte[] generateDiff(byte[] wovenClassFile, byte[] unWovenClassFile) {
// find how long the run is
int lookingAt = 10;
int shorterLength = (wovenClassFile.length < unWovenClassFile.length) ? wovenClassFile.length : unWovenClassFile.length;
while (lookingAt < shorterLength && (wovenClassFile[lookingAt] == unWovenClassFile[lookingAt])) {
lookingAt++;
}
int lengthInCommon = lookingAt - 10;
byte[] diff = new byte[unWovenClassFile.length - 4 - lengthInCommon];
// first 2 bytes of the diff are the constant pool count
diff[0] = unWovenClassFile[8];
diff[1] = unWovenClassFile[9];
// then 4 bytes saying how long the run is
byte[] lengthInCommonBytes = serializeInt(lengthInCommon);
diff[2] = lengthInCommonBytes[0];
diff[3] = lengthInCommonBytes[1];
diff[4] = lengthInCommonBytes[2];
diff[5] = lengthInCommonBytes[3];
// then we just dump the rest of the unWovenClassFile verbatim
System.arraycopy(unWovenClassFile, 10 + lengthInCommon, diff, 6, diff.length - 6);
return diff;
}
byte[] applyDiff(byte[] wovenClassFile, byte[] diff) {
int lengthInCommon = readInt(diff, 2);
byte[] unWovenClassFile = new byte[4 + diff.length + lengthInCommon];
// copy the first 8 bytes from the wovenClassFile
System.arraycopy(wovenClassFile, 0, unWovenClassFile, 0, 8);
// copy the constant pool count from the diff
unWovenClassFile[8] = diff[0];
unWovenClassFile[9] = diff[1];
// copy the run from the wovenClassFile
System.arraycopy(wovenClassFile, 10, unWovenClassFile, 10, lengthInCommon);
// copy the stuff after the run from the diff
System.arraycopy(diff, 6, unWovenClassFile, 10 + lengthInCommon, diff.length - 6);
return unWovenClassFile;
}
private byte[] serializeInt(int i) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(4);
DataOutputStream dos = new DataOutputStream(bos);
try {
dos.writeInt(i);
} catch (IOException e) {
}
return bos.toByteArray();
}
private static void writeAnyReweavableData(WeaverStateInfo wsi, CompressingDataOutputStream s, boolean compress)
throws IOException {
if (wsi.isReweavable()) {
// Write out list of aspects that must exist next time we try and weave this class
s.writeShort(wsi.aspectsAffectingType.size());
for (String type : wsi.aspectsAffectingType) {
if (compress) {
s.writeCompressedSignature(type);
} else {
s.writeUTF(type);
}
}
byte[] data = wsi.unwovenClassFile;
// if we're not in diffMode, write the unwovenClassFile now,
// otherwise we'll insert it as a diff later
if (!wsi.reweavableDiffMode) {
s.writeInt(data.length);
s.write(wsi.unwovenClassFile);
}
}
}
/**
* @return true if the supplied aspect is already in the list of those affecting this type
*/
public boolean isAspectAlreadyApplied(ResolvedType someAspect) {
String someAspectSignature = someAspect.getSignature();
for (String aspectSignature : aspectsAffectingType) {
if (aspectSignature.equals(someAspectSignature)) {
return true;
}
}
return false;
}
}