From c5cb70f2f1ede0e0d62e2fde541b91c5d2d4d409 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 11 Jun 2018 12:15:16 +1000 Subject: [PATCH 1/4] Improve the error when a method is too large --- .../asm/MethodTooLargeException.java | 44 +++++++++++++++++++ .../java/org/objectweb/asm/MethodWriter.java | 6 ++- .../org/objectweb/asm/ClassWriterTest.java | 11 ++++- 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java diff --git a/asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java b/asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java new file mode 100644 index 00000000..96ddbc84 --- /dev/null +++ b/asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java @@ -0,0 +1,44 @@ +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 null 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; + } +} diff --git a/asm/src/main/java/org/objectweb/asm/MethodWriter.java b/asm/src/main/java/org/objectweb/asm/MethodWriter.java index b49f986a..8b35e9bd 100644 --- a/asm/src/main/java/org/objectweb/asm/MethodWriter.java +++ b/asm/src/main/java/org/objectweb/asm/MethodWriter.java @@ -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. */ @@ -592,6 +595,7 @@ final class MethodWriter extends MethodVisitor { this.symbolTable = symbolTable; this.accessFlags = "".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); @@ -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, diff --git a/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java b/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java index 67595b62..40436649 100644 --- a/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java +++ b/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java @@ -143,8 +143,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); @@ -153,7 +155,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()); + assertEquals(null, thrown.getClassName()); + assertEquals(descriptor, thrown.getDescriptor()); + assertEquals(methodCodeSize, thrown.getCodeSize()); + assertEquals("Method code too large: m ()V", thrown.getMessage()); } else { classWriter.toByteArray(); } From 595da48ac5148e71bfd0e8f9d79cb3ccb322c2a0 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 11 Jun 2018 12:26:14 +1000 Subject: [PATCH 2/4] Improve error message when writing a class with an oversized constant pool --- .../objectweb/asm/ClassTooLargeException.java | 26 +++++++++++++++++++ .../java/org/objectweb/asm/ClassWriter.java | 5 ++-- .../org/objectweb/asm/ClassWriterTest.java | 16 ++++++++---- 3 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java diff --git a/asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java b/asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java new file mode 100644 index 00000000..3d0a9e61 --- /dev/null +++ b/asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java @@ -0,0 +1,26 @@ +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 null 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; + } +} diff --git a/asm/src/main/java/org/objectweb/asm/ClassWriter.java b/asm/src/main/java/org/objectweb/asm/ClassWriter.java index 5005c8b0..ac064ce5 100644 --- a/asm/src/main/java/org/objectweb/asm/ClassWriter.java +++ b/asm/src/main/java/org/objectweb/asm/ClassWriter.java @@ -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 diff --git a/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java b/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java index 40436649..15c1a051 100644 --- a/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java +++ b/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java @@ -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; @@ -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(); } @@ -157,7 +163,7 @@ void testMethodCodeSizeTooLarge(final int methodCodeSize) { if (methodCodeSize > 65535) { MethodTooLargeException thrown = assertThrows(MethodTooLargeException.class, () -> classWriter.toByteArray()); assertEquals(methodName, thrown.getMethodName()); - assertEquals(null, thrown.getClassName()); + assertNull(thrown.getClassName()); assertEquals(descriptor, thrown.getDescriptor()); assertEquals(methodCodeSize, thrown.getCodeSize()); assertEquals("Method code too large: m ()V", thrown.getMessage()); From 4a46bef06a936c794ad26470a693b0a270cdd80f Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 11 Jun 2018 12:27:25 +1000 Subject: [PATCH 3/4] Add copyright headers to new files --- .../objectweb/asm/ClassTooLargeException.java | 27 +++++++++++++++++++ .../asm/MethodTooLargeException.java | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java b/asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java index 3d0a9e61..a206e695 100644 --- a/asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java +++ b/asm/src/main/java/org/objectweb/asm/ClassTooLargeException.java @@ -1,3 +1,30 @@ +// 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 { diff --git a/asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java b/asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java index 96ddbc84..9e106e93 100644 --- a/asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java +++ b/asm/src/main/java/org/objectweb/asm/MethodTooLargeException.java @@ -1,3 +1,30 @@ +// 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 { From 9541d0241a80a07cef9f844797395e0431242732 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 11 Jun 2018 12:36:33 +1000 Subject: [PATCH 4/4] Improve the message for a too-long UTF-8 constant --- .../java/org/objectweb/asm/ByteVector.java | 2 +- .../org/objectweb/asm/ByteVectorTest.java | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/asm/src/main/java/org/objectweb/asm/ByteVector.java b/asm/src/main/java/org/objectweb/asm/ByteVector.java index 425324f2..d444fd2a 100644 --- a/asm/src/main/java/org/objectweb/asm/ByteVector.java +++ b/asm/src/main/java/org/objectweb/asm/ByteVector.java @@ -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) { diff --git a/asm/src/test/java/org/objectweb/asm/ByteVectorTest.java b/asm/src/test/java/org/objectweb/asm/ByteVectorTest.java index 75042e77..00db4640 100644 --- a/asm/src/test/java/org/objectweb/asm/ByteVectorTest.java +++ b/asm/src/test/java/org/objectweb/asm/ByteVectorTest.java @@ -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. @@ -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);