PatternDeserializationLimitsTest.java
/*******************************************************************************
* Copyright (c) 2026 Contributors
* 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
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.aspectj.weaver.patterns;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.VersionedDataInputStream;
import junit.framework.TestCase;
/**
* Checks that counts read from serialized pattern data are validated before they are used to size arrays.
*/
public class PatternDeserializationLimitsTest extends TestCase {
private interface Writer {
void write(CompressingDataOutputStream s) throws IOException;
}
private interface Reader {
void read(VersionedDataInputStream s) throws IOException;
}
private final ConstantPoolSimulator cps = new ConstantPoolSimulator();
private byte[] bytes(Writer writer) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
CompressingDataOutputStream s = new CompressingDataOutputStream(bos, cps);
writer.write(s);
s.flush();
return bos.toByteArray();
}
private VersionedDataInputStream stream(byte[] data) {
return new VersionedDataInputStream(new ByteArrayInputStream(data), cps);
}
private void assertRejected(byte[] data, Reader reader) throws IOException {
try {
reader.read(stream(data));
fail("expected the invalid array length to be rejected");
} catch (BCException expected) {
}
}
private byte[] wildTypePattern() throws IOException {
WildTypePattern wtp = new WildTypePattern(Collections.singletonList(new NamePattern("Foo")), false, 0);
return bytes(wtp::write);
}
public void testTypeVariablePatternListWithHugeCountIsRejected() throws IOException {
assertRejected(bytes(s -> s.writeInt(Integer.MAX_VALUE)), s -> TypeVariablePatternList.read(s, null));
}
public void testTypeVariablePatternWithHugeBoundsCountIsRejected() throws IOException {
assertRejected(bytes(s -> {
s.writeUTF("T");
TypePattern.ANY.write(s);
s.writeInt(Integer.MAX_VALUE);
}), s -> TypeVariablePattern.read(s, null));
}
public void testWildTypePatternWithHugeInterfaceBoundsCountIsRejected() throws IOException {
byte[] data = wildTypePattern();
int last = data.length - 4; // the final int written is the (zero) count of additional interface bounds
assertEquals(0, data[last] | data[last + 1] | data[last + 2] | data[last + 3]);
data[last] = 0x7f;
data[last + 1] = data[last + 2] = data[last + 3] = (byte) 0xff;
assertRejected(data, s -> TypePattern.read(s, null));
}
public void testWildTypePatternWithNegativeNameCountIsRejected() throws IOException {
byte[] data = wildTypePattern();
data[2] = data[3] = (byte) 0xff; // layout: key byte, version byte, then the short count of name patterns
assertRejected(data, s -> TypePattern.read(s, null));
}
public void testTypePatternListWithNegativeCountIsRejected() throws IOException {
assertRejected(bytes(s -> s.writeShort(-1)), s -> TypePatternList.read(s, null));
}
public void testAnnotationPatternListWithNegativeCountIsRejected() throws IOException {
assertRejected(bytes(s -> s.writeShort(-1)), s -> AnnotationPatternList.read(s, null));
}
public void testValidPatternsStillRead() throws IOException {
assertTrue(TypePattern.read(stream(wildTypePattern()), null) instanceof WildTypePattern);
TypeVariablePatternList list = new TypeVariablePatternList(
new TypeVariablePattern[] { new TypeVariablePattern("T"), new TypeVariablePattern("U") });
byte[] data = bytes(list::write);
assertEquals(2, TypeVariablePatternList.read(stream(data), null).getTypeVariablePatterns().length);
}
}