Skip to content

Improve error messages when exceeding size limits in the classfie format #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion asm/src/main/java/org/objectweb/asm/ByteVector.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ public ByteVector putLong(final long longValue) {
public ByteVector putUTF8(final String stringValue) {
int charLength = stringValue.length();
if (charLength > 65535) {
throw new IllegalArgumentException();
throw new IllegalArgumentException("Maximum size for a UTF-8 string constant exceeded");
}
int currentLength = length;
if (currentLength + 2 + charLength > data.length) {
Expand Down
53 changes: 53 additions & 0 deletions asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. 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.
// 3. Neither the name of the copyright holders nor the names of its
// 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 COPYRIGHT OWNER 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 org.objectweb.asm;

public final class ClassTooLargeException extends IndexOutOfBoundsException {
private final String className;
private final int constantPoolCount;

/**
* @return The name of the class, or <tt>null</tt> if not known
*/
public String getClassName() {
return className;
}

/**
* @return The number of byte codes in the method.
*/
public int getConstantPoolCount() {
return constantPoolCount;
}

public ClassTooLargeException(String className, int constantPoolCount) {
super("Class file too large" + (className == null ? "" : ": " + className));
this.className = className;
this.constantPoolCount = constantPoolCount;
}
}
5 changes: 3 additions & 2 deletions asm/src/main/java/org/objectweb/asm/ClassWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,9 @@ public byte[] toByteArray() {
// IMPORTANT: this must be the last part of the ClassFile size computation, because the previous
// statements can add attribute names to the constant pool, thereby changing its size!
size += symbolTable.getConstantPoolLength();
if (symbolTable.getConstantPoolCount() > 0xFFFF) {
throw new IndexOutOfBoundsException("Class file too large!");
int constantPoolCount = symbolTable.getConstantPoolCount();
if (constantPoolCount > 0xFFFF) {
throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount);
}

// Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in
Expand Down
71 changes: 71 additions & 0 deletions asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. 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.
// 3. Neither the name of the copyright holders nor the names of its
// 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 COPYRIGHT OWNER 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 org.objectweb.asm;

public final class MethodTooLargeException extends IndexOutOfBoundsException {
private final String className;
private final String methodName;
private final String descriptor;
private final int codeSize;

/**
* @return The name of the enclosing class, or <tt>null</tt> if not known
*/
public String getClassName() {
return className;
}

/**
* @return The name of the method
*/
public String getMethodName() {
return methodName;
}

/**
* @return The descriptor of the the method
*/
public String getDescriptor() {
return descriptor;
}

/**
* @return The number of byte codes in the method.
*/
public int getCodeSize() {
return codeSize;
}

public MethodTooLargeException(String className, String methodName, String descriptor, int codeSize) {
super("Method code too large: " + (className == null ? "" : className + ".") + methodName + " " + descriptor);
this.className = className;
this.methodName = methodName;
this.descriptor = descriptor;
this.codeSize = codeSize;
}
}
6 changes: 5 additions & 1 deletion asm/src/main/java/org/objectweb/asm/MethodWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ final class MethodWriter extends MethodVisitor {
/** The descriptor of this method. */
private final String descriptor;

/** The name of this method. */
private final String name;

// Code attribute fields and sub attributes:

/** The max_stack field of the Code attribute. */
Expand Down Expand Up @@ -592,6 +595,7 @@ final class MethodWriter extends MethodVisitor {
this.symbolTable = symbolTable;
this.accessFlags = "<init>".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
this.nameIndex = symbolTable.addConstantUtf8(name);
this.name = name;
this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
this.descriptor = descriptor;
this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature);
Expand Down Expand Up @@ -2058,7 +2062,7 @@ int computeMethodInfoSize() {
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
if (code.length > 0) {
if (code.length > 65535) {
throw new IndexOutOfBoundsException("Method code too large!");
throw new MethodTooLargeException(symbolTable.getClassName(), name, descriptor, code.length);
}
symbolTable.addConstantUtf8(Constants.CODE);
// The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
Expand Down
23 changes: 20 additions & 3 deletions asm/src/test/java/org/objectweb/asm/ByteVectorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.Arrays;

/**
* ByteVector tests.
Expand Down Expand Up @@ -113,12 +117,25 @@ public void testPutUTF8_unicode() {
assertContains(byteVector, 0, 8, 'a', -64, -128, -62, -128, -32, -96, -128);

char[] charBuffer = new char[32768];
for (int i = 0; i < charBuffer.length; ++i) {
charBuffer[i] = '\u07FF';
}
Arrays.fill(charBuffer, '\u07FF');
assertThrows(IllegalArgumentException.class, () -> byteVector.putUTF8(new String(charBuffer)));
}

@ParameterizedTest
@ValueSource(ints = {65535, 65536})
public void testPutUTF8_tooLong(int size) {
ByteVector byteVector = new ByteVector(0);
char[] charBuffer = new char[size];
Arrays.fill(charBuffer, 'A');
String s = new String(charBuffer);
if (size > 65535) {
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> byteVector.putUTF8(s));
assertEquals("Maximum size for a UTF-8 string constant exceeded", thrown.getMessage());
} else {
byteVector.putUTF8(s);
}
}

@Test
public void testPutByteArray() {
ByteVector byteVector = new ByteVector(0);
Expand Down
25 changes: 19 additions & 6 deletions asm/src/test/java/org/objectweb/asm/ClassWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@
package org.objectweb.asm;

import static java.util.stream.Collectors.toSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.*;
import static org.objectweb.asm.test.Assertions.assertThat;

import java.io.FileInputStream;
Expand Down Expand Up @@ -129,11 +128,18 @@ public void testNewConst() {
@ValueSource(ints = {65535, 65536})
public void testConstantPoolSizeTooLarge(final int constantPoolCount) {
ClassWriter classWriter = new ClassWriter(0);
for (int i = 0; i < constantPoolCount - 1; ++i) {
String className = "A";
classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);
int initConstantPoolCount = 5;
// assert(initConstantPoolCount == classWriter.symbolTable.getConstantPoolCount())
for (int i = 0; i < constantPoolCount - initConstantPoolCount; ++i) {
classWriter.newConst(Integer.valueOf(i));
}
if (constantPoolCount > 65535) {
assertThrows(IndexOutOfBoundsException.class, () -> classWriter.toByteArray());
ClassTooLargeException thrown = assertThrows(ClassTooLargeException.class, () -> classWriter.toByteArray());
assertEquals(className, thrown.getClassName());
assertEquals(constantPoolCount, thrown.getConstantPoolCount());
assertEquals("Class file too large: A", thrown.getMessage());
} else {
classWriter.toByteArray();
}
Expand All @@ -143,8 +149,10 @@ public void testConstantPoolSizeTooLarge(final int constantPoolCount) {
@ValueSource(ints = {65535, 65536})
void testMethodCodeSizeTooLarge(final int methodCodeSize) {
ClassWriter classWriter = new ClassWriter(0);
String methodName = "m";
String descriptor = "()V";
MethodVisitor methodVisitor =
classWriter.visitMethod(Opcodes.ACC_STATIC, "m", "()V", null, null);
classWriter.visitMethod(Opcodes.ACC_STATIC, methodName, descriptor, null, null);
methodVisitor.visitCode();
for (int i = 0; i < methodCodeSize - 1; ++i) {
methodVisitor.visitInsn(Opcodes.NOP);
Expand All @@ -153,7 +161,12 @@ void testMethodCodeSizeTooLarge(final int methodCodeSize) {
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitEnd();
if (methodCodeSize > 65535) {
assertThrows(IndexOutOfBoundsException.class, () -> classWriter.toByteArray());
MethodTooLargeException thrown = assertThrows(MethodTooLargeException.class, () -> classWriter.toByteArray());
assertEquals(methodName, thrown.getMethodName());
assertNull(thrown.getClassName());
assertEquals(descriptor, thrown.getDescriptor());
assertEquals(methodCodeSize, thrown.getCodeSize());
assertEquals("Method code too large: m ()V", thrown.getMessage());
} else {
classWriter.toByteArray();
}
Expand Down