This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/10.1.x by this push:
new 3e77fbab72 Review "Extract the SMAP from the end of the compiled class
file"
3e77fbab72 is described below
commit 3e77fbab721b8b2db5e57a54f2fd524c870b317f
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Nov 5 20:09:22 2026 +0000
Review "Extract the SMAP from the end of the compiled class file"
Completely rewrite the SMAP extract.
Rather than replacing one hack with a (maybe) better hack, parse the
class file as per the spec and extract the SMAP.
Co-authored by: Claude / Sonnet 5.5
---
java/org/apache/jasper/compiler/SmapUtil.java | 214 +++++++++++-
.../jasper/resources/LocalStrings.properties | 1 +
test/org/apache/jasper/compiler/TestSmapUtil.java | 375 ++++++++++++++++++---
3 files changed, 532 insertions(+), 58 deletions(-)
diff --git a/java/org/apache/jasper/compiler/SmapUtil.java
b/java/org/apache/jasper/compiler/SmapUtil.java
index abbf6ea1e6..72f24edaaf 100644
--- a/java/org/apache/jasper/compiler/SmapUtil.java
+++ b/java/org/apache/jasper/compiler/SmapUtil.java
@@ -16,6 +16,9 @@
*/
package org.apache.jasper.compiler;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -27,8 +30,11 @@ import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
@@ -49,8 +55,8 @@ public class SmapUtil {
private static final Charset SMAP_ENCODING = StandardCharsets.UTF_8;
- // Marker that begins the SMAP data in the SourceDebugExtension attribute
- private static final String SMAP_START_MARKER = "SMAP\n";
+ // The SMAP data in the SourceDebugExtension attribute starts with this
line
+ private static final String SMAP_HEADER = "SMAP\n";
/**
@@ -436,6 +442,188 @@ public class SmapUtil {
}
}
+ /**
+ * Reads the SMAP from the SourceDebugExtension attribute of a class file.
This walks the structure of the class
+ * file, as {@link SDEInstaller} does when it installs the attribute, so
that exactly the bytes of the attribute are
+ * returned. Searching the class file for the content of the SMAP is not
reliable because the same bytes may appear
+ * elsewhere in the class file, such as in a string constant, or within
the SMAP itself.
+ * <p>
+ * The constant pool may contain more than one entry with the name of the
attribute and other tools may add
+ * SourceDebugExtension attributes that do not contain a SMAP. Only an
attribute that contains a SMAP, which starts
+ * with {@link SmapUtil#SMAP_HEADER}, is returned. If there is more than
one the last is returned, since that is
+ * where {@link SDEInstaller} adds it.
+ */
+ private static class SDEReader {
+
+ private static final int MAGIC = 0xCAFEBABE;
+
+ private static final byte[] SMAP_HEADER_BYTES =
SMAP_HEADER.getBytes(StandardCharsets.ISO_8859_1);
+
+ private final DataInputStream in;
+
+ private SDEReader(InputStream is) {
+ this.in = new DataInputStream(new BufferedInputStream(is));
+ }
+
+ /**
+ * Read the SMAP from a class file.
+ *
+ * @param is The class file
+ *
+ * @return The content of the SourceDebugExtension attribute that
contains the SMAP, including the first line,
+ * or {@code null} if the class file does not have such an
attribute
+ *
+ * @throws IOException If the data is not a valid class file or
reading it fails
+ */
+ static byte[] read(InputStream is) throws IOException {
+ return new SDEReader(is).readSDE();
+ }
+
+ private byte[] readSDE() throws IOException {
+ if (in.readInt() != MAGIC) {
+ throw new
IOException(Localizer.getMessage("jsp.error.invalidClassFile"));
+ }
+ skip(2 + 2); // minor and major version
+
+ Set<Integer> sdeIndexes = readConstantPool();
+ if (sdeIndexes.isEmpty()) {
+ // Without the name in the constant pool there cannot be a
SourceDebugExtension attribute
+ return null;
+ }
+
+ skip(2 + 2 + 2); // access, this, super
+ int interfaceCount = in.readUnsignedShort();
+ skip(interfaceCount * 2L);
+ skipMembers(); // fields
+ skipMembers(); // methods
+
+ // Class attributes
+ byte[] smap = null;
+ int attrCount = in.readUnsignedShort();
+ for (int i = 0; i < attrCount; i++) {
+ int nameIndex = in.readUnsignedShort();
+ int len = readLength();
+ if (sdeIndexes.contains(Integer.valueOf(nameIndex))) {
+ byte[] sde = in.readNBytes(len);
+ if (sde.length != len) {
+ throw new EOFException();
+ }
+ if (isSmap(sde)) {
+ smap = sde;
+ }
+ } else {
+ skip(len);
+ }
+ }
+ return smap;
+ }
+
+ private static boolean isSmap(byte[] sde) {
+ return sde.length >= SMAP_HEADER_BYTES.length &&
+ Arrays.equals(sde, 0, SMAP_HEADER_BYTES.length,
SMAP_HEADER_BYTES, 0, SMAP_HEADER_BYTES.length);
+ }
+
+ /*
+ * Skips the constant pool and returns the indexes of the Utf8 entries
that contain the name of the
+ * SourceDebugExtension attribute. There is normally one but nothing
prevents there being several.
+ */
+ private Set<Integer> readConstantPool() throws IOException {
+ Set<Integer> sdeIndexes = new HashSet<>();
+ int constantPoolCount = in.readUnsignedShort();
+ // Constant pool index zero is not in the class file
+ for (int i = 1; i < constantPoolCount; i++) {
+ int tag = in.readUnsignedByte();
+ switch (tag) {
+ case 7: // Class
+ case 8: // String
+ case 16: // MethodType
+ case 19: // Module
+ case 20: // Package
+ skip(2);
+ break;
+ case 15: // MethodHandle
+ skip(3);
+ break;
+ case 9: // Field
+ case 10: // Method
+ case 11: // InterfaceMethod
+ case 3: // Integer
+ case 4: // Float
+ case 12: // NameAndType
+ case 17: // Dynamic
+ case 18: // InvokeDynamic
+ skip(4);
+ break;
+ case 5: // Long
+ case 6: // Double
+ skip(8);
+ // Long and double values take two entries in the
constant pool
+ i++;
+ break;
+ case 1: // Utf8
+ int len = in.readUnsignedShort();
+ if (len == SDEInstaller.nameSDE.length()) {
+ byte[] utf8 = in.readNBytes(len);
+ if (utf8.length != len) {
+ throw new EOFException();
+ }
+ if (SDEInstaller.nameSDE.equals(new String(utf8,
StandardCharsets.UTF_8))) {
+ sdeIndexes.add(Integer.valueOf(i));
+ }
+ } else {
+ skip(len);
+ }
+ break;
+ default:
+ throw new
IOException(Localizer.getMessage("jsp.error.unexpectedTag",
Integer.valueOf(tag)));
+ }
+ }
+ return sdeIndexes;
+ }
+
+ /*
+ * Skips exactly the requested number of bytes.
InputStream.skipNBytes() is not used as it is not available in
+ * all supported Java versions and InputStream.skip() may skip fewer
bytes than requested.
+ */
+ private void skip(long count) throws IOException {
+ long remaining = count;
+ while (remaining > 0) {
+ long skipped = in.skip(remaining);
+ if (skipped <= 0) {
+ // skip() is allowed to skip nothing. Read a byte to tell
the end of the stream from a skip that
+ // made no progress.
+ if (in.read() == -1) {
+ throw new EOFException();
+ }
+ skipped = 1;
+ }
+ remaining -= skipped;
+ }
+ }
+
+
+ private void skipMembers() throws IOException {
+ int count = in.readUnsignedShort();
+ for (int i = 0; i < count; i++) {
+ skip(2 + 2 + 2); // access, name, descriptor
+ int attrCount = in.readUnsignedShort();
+ for (int j = 0; j < attrCount; j++) {
+ skip(2); // name
+ skip(readLength());
+ }
+ }
+ }
+
+ private int readLength() throws IOException {
+ int len = in.readInt();
+ if (len < 0) {
+ // The length is a u4 but a valid attribute is never larger
than the maximum size of an array
+ throw new
IOException(Localizer.getMessage("jsp.error.invalidClassFile"));
+ }
+ return len;
+ }
+ }
+
/**
* Traverses the JSP AST nodes and populates the given SmapStratum with
line mapping data.
* @param nodes the root JSP AST nodes
@@ -775,22 +963,12 @@ public class SmapUtil {
is = cl.getResourceAsStream(className.replace(".", "/") +
".class");
if (is != null) {
- // The SourceDebugExtension attribute that contains the SMAP
- // is appended as the last attribute of the class file (see
- // SDEInstaller) so the SMAP to extract is the one that starts
- // at the last occurrence of the marker. The first occurrence
- // cannot be used because the same byte sequence may appear
- // earlier in the class file, for example in a string constant
- // in the constant pool created from the template text of the
- // page. Note that, unlike SmapStratum, the optional JSR-045
- // terminator line "* END SMAP" also contains the marker. The
- // SMAP data is installed as ISO-8859-1 (see installSmap())
- // which preserves every byte of the class file, so the marker
- // cannot be missed by the search below.
- String classData = new String(is.readAllBytes(),
StandardCharsets.ISO_8859_1);
- int index = classData.lastIndexOf(SMAP_START_MARKER);
- if (index >= 0) {
- return classData.substring(index +
SMAP_START_MARKER.length());
+ // The SMAP is the content of the SourceDebugExtension
attribute. It is installed as ISO-8859-1 (see
+ // installSmap()). The first line, "SMAP", is not included in
the result.
+ byte[] sde = SDEReader.read(is);
+ if (sde != null) {
+ return new String(sde, SMAP_HEADER.length(), sde.length -
SMAP_HEADER.length(),
+ StandardCharsets.ISO_8859_1);
}
}
} catch (IOException ioe) {
diff --git a/java/org/apache/jasper/resources/LocalStrings.properties
b/java/org/apache/jasper/resources/LocalStrings.properties
index a7ed73bdc1..62dbb8a05e 100644
--- a/java/org/apache/jasper/resources/LocalStrings.properties
+++ b/java/org/apache/jasper/resources/LocalStrings.properties
@@ -103,6 +103,7 @@ jsp.error.invalid.tagdir=Tag file directory [{0}] does not
start with "/WEB-INF/
jsp.error.invalid.taglib.version=Invalid JSP version [{0}] defined by the tag
library used by tag [{1}]
jsp.error.invalid.varscope=Invalid variable scope [{0}]
jsp.error.invalid.version=Invalid JSP version defined for tag file at [{0}]
+jsp.error.invalidClassFile=The data is not a valid class file
jsp.error.ise_on_clear=Illegal to clear() when buffer size == 0
jsp.error.java.line.number=An error occurred at line: [{0}] in the generated
java file: [{1}]
jsp.error.javac=Javac exception
diff --git a/test/org/apache/jasper/compiler/TestSmapUtil.java
b/test/org/apache/jasper/compiler/TestSmapUtil.java
index 379a5751a9..85d207e679 100644
--- a/test/org/apache/jasper/compiler/TestSmapUtil.java
+++ b/test/org/apache/jasper/compiler/TestSmapUtil.java
@@ -16,78 +16,373 @@
*/
package org.apache.jasper.compiler;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
import java.io.File;
+import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
+import org.apache.catalina.startup.ExpandWar;
import org.apache.catalina.startup.TomcatBaseTest;
public class TestSmapUtil extends TomcatBaseTest {
- // The SMAP of a Jasper generated class. The content is valid for
- // loadSmap() and follows the format generated by SmapStratum.
- private static final String SMAP = "SMAP\n" +
- "test.jsp\n" +
- "JSP\n" +
- "*S JSP\n" +
- "*F\n" +
- "+ 0 test.jsp\n" +
- "test.jsp\n" +
- "*L\n" +
- "1,4:120,0\n" +
- "*E\n";
+ // The SMAP of a Jasper generated class. The content is valid for
loadSmap() and follows the format generated by
+ // SmapStratum.
+ private static final String SMAP = smap("test.jsp");
+
+ private final List<File> dirs = new ArrayList<>();
+ private final List<URLClassLoader> loaders = new ArrayList<>();
+
+
+ @After
+ public void cleanUp() throws IOException {
+ for (URLClassLoader loader : loaders) {
+ loader.close();
+ }
+ loaders.clear();
+ for (File dir : dirs) {
+ ExpandWar.delete(dir);
+ }
+ dirs.clear();
+ }
+
+
+ private static String smap(String fileName) {
+ return "SMAP\n" + "test_jsp.java\n" + "JSP\n" + "*S JSP\n" + "*F\n" +
"+ 0 " + fileName + "\n" + fileName +
+ "\n" + "*L\n" + "1,4:120,0\n" + "*E\n";
+ }
+
@Test
public void testLoadSmapFromDebugExtension() throws Exception {
- // A class file that only contains the SMAP in the (simulated)
- // SourceDebugExtension attribute at the end of the file
- URLClassLoader cl = writeClass("clean", "CAFEBABE
junk".getBytes(StandardCharsets.ISO_8859_1));
+ ClassFile classFile = new ClassFile().sde(SMAP);
- SmapStratum smapStratum = SmapUtil.loadSmap("clean", cl);
+ SmapStratum smapStratum = SmapUtil.loadSmap("Test",
writeClass(classFile));
Assert.assertNotNull(smapStratum);
Assert.assertEquals(SMAP, smapStratum.toString());
}
+
@Test
public void testLoadSmapWithMarkerInConstantPool() throws Exception {
- // Jasper does not read the class file as described by the JLS so a
- // string constant in the constant pool that contains "SMAP\n" (which
- // a page with the word "SMAP" at the end of a line of template text
- // produces) must not be mistaken for the start of the SMAP. The SMAP
- // of the SourceDebugExtension attribute has to be extracted even
- // though an earlier occurrence of the marker is present.
- byte[] junk =
- "CAFEBABE constant pool junk that ends a line of template text
with SMAP\nfake content\n"
- .getBytes(StandardCharsets.ISO_8859_1);
+ // A string constant in the constant pool that contains "SMAP\n"
(which a page with the word "SMAP" at the end
+ // of a line of template text produces) must not be mistaken for the
start of the SMAP, wherever it is in the
+ // class file.
+ ClassFile classFile = new ClassFile().constant("template text that
ends a line with SMAP\nfake content\n")
+ .sde(SMAP).constant("more text ending with SMAP\nafter the
name of the attribute\n");
+
+ SmapStratum smapStratum = SmapUtil.loadSmap("Test",
writeClass(classFile));
+
+ Assert.assertNotNull(smapStratum);
+ Assert.assertEquals(SMAP, smapStratum.toString());
+ }
+
+
+ @Test
+ public void testLoadSmapWithMarkerInSmap() throws Exception {
+ // The marker also appears within the SMAP if the name of a source
file ends with "SMAP"
+ String smapWithMarker = smap("ERRORS.SMAP");
+ ClassFile classFile = new ClassFile().sde(smapWithMarker);
+
+ SmapStratum smapStratum = SmapUtil.loadSmap("Test",
writeClass(classFile));
+
+ Assert.assertNotNull(smapStratum);
+ Assert.assertEquals(smapWithMarker, smapStratum.toString());
+ }
+
+
+ @Test
+ public void testLoadSmapWithOtherAttributes() throws Exception {
+ // Attributes on the class before the SourceDebugExtension attribute,
one of which contains the marker, and
+ // members that have attributes
+ ClassFile classFile = new
ClassFile().wideConstants().member().classAttribute("Other", "SMAP\nnot this\n")
+ .sde(SMAP);
+
+ SmapStratum smapStratum = SmapUtil.loadSmap("Test",
writeClass(classFile));
- URLClassLoader cl = writeClass("junk", junk);
+ Assert.assertNotNull(smapStratum);
+ Assert.assertEquals(SMAP, smapStratum.toString());
+ }
- SmapStratum smapStratum = SmapUtil.loadSmap("junk", cl);
- Assert.assertNotNull("SMAP extraction found the marker in the constant
pool junk", smapStratum);
+ @Test
+ public void testLoadSmapWithMultipleDebugExtensionNames() throws Exception
{
+ // More than one entry in the constant pool with the name of the
attribute, one of which is for an attribute
+ // that does not contain a SMAP and that comes after the SMAP
+ ClassFile classFile = new ClassFile().sdeName().sde(SMAP).sde("not a
SMAP\nSMAP\nfake content\n").sdeName();
+
+ SmapStratum smapStratum = SmapUtil.loadSmap("Test",
writeClass(classFile));
+
+ Assert.assertNotNull(smapStratum);
Assert.assertEquals(SMAP, smapStratum.toString());
}
- /**
- * Writes a file named [name].class to the test temporary directory. The
- * content simulates the end of a compiled JSP class file: the given data
- * followed by the SMAP, since Jasper places the SourceDebugExtension
- * attribute last and loadSmap() does not parse the class file structure.
- *
- * @return a class loader for the directory containing the file
+
+ @Test
+ public void testLoadSmapWithOtherDebugExtensionFirst() throws Exception {
+ // An attribute that does not contain a SMAP, before the one that does
+ ClassFile classFile = new ClassFile().sde("not a SMAP\nSMAP\nfake
content\n").sde(SMAP);
+
+ SmapStratum smapStratum = SmapUtil.loadSmap("Test",
writeClass(classFile));
+
+ Assert.assertNotNull(smapStratum);
+ Assert.assertEquals(SMAP, smapStratum.toString());
+ }
+
+
+ @Test
+ public void testLoadSmapWithOnlyOtherDebugExtension() throws Exception {
+ // The only SourceDebugExtension attribute does not contain a SMAP
+ ClassFile classFile = new ClassFile().sde("not a SMAP\nSMAP\nfake
content\n");
+
+ Assert.assertNull(SmapUtil.loadSmap("Test", writeClass(classFile)));
+ }
+
+
+ @Test
+ public void testLoadSmapWithMultipleSmaps() throws Exception {
+ // The last SMAP is used since that is where the SourceDebugExtension
attribute is added
+ ClassFile classFile = new ClassFile().sde(smap("first.jsp")).sde(SMAP);
+
+ SmapStratum smapStratum = SmapUtil.loadSmap("Test",
writeClass(classFile));
+
+ Assert.assertNotNull(smapStratum);
+ Assert.assertEquals(SMAP, smapStratum.toString());
+ }
+
+
+ @Test
+ public void testLoadSmapWithoutDebugExtension() throws Exception {
+ // The name of the attribute is present but there is no such
attribute. The only occurrence of the marker is in
+ // a constant.
+ ClassFile classFile = new ClassFile().constant("text ending with
SMAP\nfake content\n").sdeName();
+
+ Assert.assertNull(SmapUtil.loadSmap("Test", writeClass(classFile)));
+ }
+
+
+ @Test
+ public void testLoadSmapWithoutDebugExtensionName() throws Exception {
+ ClassFile classFile = new ClassFile().constant("text ending with
SMAP\nfake content\n");
+
+ Assert.assertNull(SmapUtil.loadSmap("Test", writeClass(classFile)));
+ }
+
+
+ @Test
+ public void testLoadSmapNotAClassFile() throws Exception {
+ File dir = createDir();
+ Files.writeString(new File(dir, "Test.class").toPath(), "CAFEBABE
junk\n" + SMAP, StandardCharsets.ISO_8859_1);
+
+ Assert.assertNull(SmapUtil.loadSmap("Test", loader(dir)));
+ }
+
+
+ @Test
+ public void testLoadSmapTruncatedClassFile() throws Exception {
+ byte[] bytes = new ClassFile().sde(SMAP).toBytes();
+ File dir = createDir();
+ // Truncate in the middle of the SourceDebugExtension attribute
+ Files.write(new File(dir, "Test.class").toPath(), Arrays.copyOf(bytes,
bytes.length - 10));
+
+ Assert.assertNull(SmapUtil.loadSmap("Test", loader(dir)));
+ }
+
+
+ @Test
+ public void testInstallAndLoadSmap() throws Exception {
+ // Install the SMAP with the code that Jasper uses and then load it
again
+ ClassFile classFile = new ClassFile().constant("text ending with
SMAP\nfake content\n");
+ File dir = createDir();
+ File file = new File(dir, "Test.class");
+ Files.write(file.toPath(), classFile.toBytes());
+
+ SmapStratum stratum = new SmapStratum();
+ stratum.setOutputFileName("test_jsp.java");
+ stratum.setClassFileName(file.getAbsolutePath());
+ stratum.addFile("ERRORS.SMAP");
+ stratum.addLineData(1, "ERRORS.SMAP", 4, 120, 1);
+ SmapUtil.installSmap(Map.of("Test", stratum));
+
+ SmapStratum loaded = SmapUtil.loadSmap("Test", loader(dir));
+
+ Assert.assertNotNull(loaded);
+ Assert.assertEquals(stratum.getSmapString(), loaded.getSmapString());
+ }
+
+
+ private URLClassLoader writeClass(ClassFile classFile) throws Exception {
+ File dir = createDir();
+ Files.write(new File(dir, "Test.class").toPath(), classFile.toBytes());
+ return loader(dir);
+ }
+
+
+ private File createDir() {
+ File dir = new File(getTemporaryDirectory(), "smap-util-" +
dirs.size() + "-" + System.nanoTime());
+ Assert.assertTrue(dir.mkdirs());
+ dirs.add(dir);
+ return dir;
+ }
+
+
+ private URLClassLoader loader(File dir) throws Exception {
+ URLClassLoader loader = new URLClassLoader(new URL[] {
dir.toURI().toURL() });
+ loaders.add(loader);
+ return loader;
+ }
+
+
+ /*
+ * Builds a minimal but structurally valid class file. The constant pool
contains the entries required by the class
+ * plus any that are added. The SourceDebugExtension attribute, if there
is one, is the last attribute.
*/
- private URLClassLoader writeClass(String name, byte[] data) throws
Exception {
- File file = new File(getTemporaryDirectory(), name + ".class");
+ private static class ClassFile {
+
+ // Constants added before the first attribute or name (constants) and
after it (laterConstants)
+ private final List<String> constants = new ArrayList<>();
+ private final List<String> laterConstants = new ArrayList<>();
+ private final List<String[]> attributes = new ArrayList<>();
+ private boolean sdeName;
+ private boolean wide;
+ private boolean member;
+
+ ClassFile constant(String value) {
+ if (sdeName || !attributes.isEmpty()) {
+ laterConstants.add(value);
+ } else {
+ constants.add(value);
+ }
+ return this;
+ }
+
+ /*
+ * Adds the name of the SourceDebugExtension attribute to the constant
pool without adding the attribute.
+ */
+ ClassFile sdeName() {
+ sdeName = true;
+ return this;
+ }
+
+ /*
+ * Adds a SourceDebugExtension attribute. Each attribute has its own
entry in the constant pool for its name so
+ * adding more than one results in more than one entry with the name
of the attribute.
+ */
+ ClassFile sde(String value) {
+ return classAttribute("SourceDebugExtension", value);
+ }
+
+ ClassFile wideConstants() {
+ wide = true;
+ return this;
+ }
+
+ ClassFile member() {
+ member = true;
+ return this;
+ }
+
+ ClassFile classAttribute(String name, String value) {
+ attributes.add(new String[] { name, value });
+ return this;
+ }
+
+ byte[] toBytes() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(baos);
+
+ // Constant pool: 1 Utf8, 2 Class, 3 Utf8, 4 Class, then the
optional entries
+ ByteArrayOutputStream pool = new ByteArrayOutputStream();
+ DataOutputStream poolOut = new DataOutputStream(pool);
+ poolOut.writeByte(1);
+ poolOut.writeUTF("Test");
+ poolOut.writeByte(7);
+ poolOut.writeShort(1);
+ poolOut.writeByte(1);
+ poolOut.writeUTF("java/lang/Object");
+ poolOut.writeByte(7);
+ poolOut.writeShort(3);
+ int count = 5;
- String classData = new String(data, StandardCharsets.ISO_8859_1) +
SMAP;
- Files.writeString(file.toPath(), classData,
StandardCharsets.ISO_8859_1);
+ for (String constant : constants) {
+ poolOut.writeByte(1);
+ poolOut.writeUTF(constant);
+ count++;
+ }
+ if (sdeName) {
+ poolOut.writeByte(1);
+ poolOut.writeUTF("SourceDebugExtension");
+ count++;
+ }
+ int[] attributeNameIndexes = new int[attributes.size()];
+ for (int i = 0; i < attributes.size(); i++) {
+ poolOut.writeByte(1);
+ poolOut.writeUTF(attributes.get(i)[0]);
+ attributeNameIndexes[i] = count++;
+ }
+ if (wide) {
+ // Long and double take two entries in the constant pool
+ poolOut.writeByte(5);
+ poolOut.writeLong(1L);
+ count += 2;
+ poolOut.writeByte(6);
+ poolOut.writeDouble(1.0d);
+ count += 2;
+ }
+ for (String constant : laterConstants) {
+ poolOut.writeByte(1);
+ poolOut.writeUTF(constant);
+ count++;
+ }
+ // Name and descriptor of the member
+ int memberNameIndex = 1;
- return new URLClassLoader(new URL[] {
file.getParentFile().toURI().toURL() });
+ out.writeInt(0xCAFEBABE);
+ out.writeShort(0); // minor
+ out.writeShort(65); // major
+ out.writeShort(count);
+ out.write(pool.toByteArray());
+ out.writeShort(0x21); // access
+ out.writeShort(2); // this
+ out.writeShort(4); // super
+ out.writeShort(0); // interfaces
+ out.writeShort(0); // fields
+ if (member) {
+ // One method with one attribute that contains the marker
+ out.writeShort(1);
+ out.writeShort(0x1); // access
+ out.writeShort(memberNameIndex);
+ out.writeShort(memberNameIndex);
+ out.writeShort(1);
+ out.writeShort(memberNameIndex);
+ byte[] data = "SMAP\nin a
member\n".getBytes(StandardCharsets.ISO_8859_1);
+ out.writeInt(data.length);
+ out.write(data);
+ } else {
+ out.writeShort(0);
+ }
+ out.writeShort(attributes.size());
+ for (int i = 0; i < attributes.size(); i++) {
+ out.writeShort(attributeNameIndexes[i]);
+ byte[] data =
attributes.get(i)[1].getBytes(StandardCharsets.ISO_8859_1);
+ out.writeInt(data.length);
+ out.write(data);
+ }
+ out.flush();
+ return baos.toByteArray();
+ }
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]