From d9cb35d747e5f38210a3207d6821e333dcf45a8b Mon Sep 17 00:00:00 2001
Date: Fri, 31 May 2024 15:39:38 +0800
Subject: [PATCH] [Backport]8318889: Backport Important Fixed Issues in Later Versions
 2024_5_30
---
 hotspot/src/os/linux/vm/os_perf_linux.cpp     |   2 +-
 hotspot/src/share/vm/opto/loopnode.cpp        |   8 +-
 hotspot/src/share/vm/prims/jni.cpp            |   5 +
 .../abstractMethod/AbstractMethodClass.jasm   |  43 ++++++
 .../abstractMethod/TestJNIAbstractMethod.java |  68 +++++++++
 .../jni/abstractMethod/libJNIAbstractMethod.c |  43 ++++++
 .../media/sound/StandardMidiFileReader.java   |  13 +-
 .../classes/javax/swing/text/html/CSS.java    |   6 +-
 jdk/src/share/native/java/util/zip/zip_util.c |   2 +-
 .../native/sun/awt/image/jpeg/imageioJPEG.c   |   4 +
 .../native/sun/awt/image/jpeg/jpegdecoder.c   |   4 +
 .../File/SMFInterruptedRunningStatus.java     | 143 ++++++++++++++++++
 12 files changed, 331 insertions(+), 10 deletions(-)
 create mode 100644 hotspot/test/runtime/jni/abstractMethod/AbstractMethodClass.jasm
 create mode 100644 hotspot/test/runtime/jni/abstractMethod/TestJNIAbstractMethod.java
 create mode 100644 hotspot/test/runtime/jni/abstractMethod/libJNIAbstractMethod.c
 create mode 100644 jdk/test/javax/sound/midi/File/SMFInterruptedRunningStatus.java

diff --git a/hotspot/src/os/linux/vm/os_perf_linux.cpp b/hotspot/src/os/linux/vm/os_perf_linux.cpp
index 0d1f75810..6a92675a6 100644
--- a/hotspot/src/os/linux/vm/os_perf_linux.cpp
+++ b/hotspot/src/os/linux/vm/os_perf_linux.cpp
@@ -941,7 +941,7 @@ SystemProcessInterface::SystemProcesses::ProcessIterator::ProcessIterator() {
 bool SystemProcessInterface::SystemProcesses::ProcessIterator::initialize() {
   _dir = os::opendir("/proc");
   _entry = NULL;
-  _valid = true;
+  _valid = _dir != NULL; // May be null if /proc is not accessible.
   next_process();
 
   return true;
diff --git a/hotspot/src/share/vm/opto/loopnode.cpp b/hotspot/src/share/vm/opto/loopnode.cpp
index 5e6d53a48..351e6888b 100644
--- a/hotspot/src/share/vm/opto/loopnode.cpp
+++ b/hotspot/src/share/vm/opto/loopnode.cpp
@@ -2571,6 +2571,7 @@ void PhaseIdealLoop::build_and_optimize(bool do_split_ifs, bool skip_loop_opts)
   NOT_PRODUCT( C->verify_graph_edges(); )
   worklist.push( C->top() );
   build_loop_late( visited, worklist, nstack );
+  if (C->failing()) { return; }
 
   if (_verify_only) {
     // restore major progress flag
@@ -3781,6 +3782,7 @@ void PhaseIdealLoop::build_loop_late( VectorSet &visited, Node_List &worklist, N
       } else {
         // All of n's children have been processed, complete post-processing.
         build_loop_late_post(n);
+        if (C->failing()) { return; }
         if (nstack.is_empty()) {
           // Finished all nodes on stack.
           // Process next node on the worklist.
@@ -3884,13 +3886,15 @@ void PhaseIdealLoop::build_loop_late_post( Node *n ) {
   Node *legal = LCA;            // Walk 'legal' up the IDOM chain
   Node *least = legal;          // Best legal position so far
   while( early != legal ) {     // While not at earliest legal
-#ifdef ASSERT
     if (legal->is_Start() && !early->is_Root()) {
+#ifdef ASSERT
       // Bad graph. Print idom path and fail.
       dump_bad_graph("Bad graph detected in build_loop_late", n, early, LCA);
       assert(false, "Bad graph detected in build_loop_late");
-    }
 #endif
+    C->record_method_not_compilable("Bad graph detected in build_loop_late");
+      return;
+    }
     // Find least loop nesting depth
     legal = idom(legal);        // Bump up the IDOM tree
     // Check for lower nesting depth
diff --git a/hotspot/src/share/vm/prims/jni.cpp b/hotspot/src/share/vm/prims/jni.cpp
index de0aae9b4..cccb578ea 100644
--- a/hotspot/src/share/vm/prims/jni.cpp
+++ b/hotspot/src/share/vm/prims/jni.cpp
@@ -1380,6 +1380,11 @@ static void jni_invoke_nonstatic(JNIEnv *env, JavaValue* result, jobject receive
     }
   }
 
+  if (selected_method->is_abstract()) {
+    ResourceMark rm(THREAD);
+    THROW_MSG(vmSymbols::java_lang_AbstractMethodError(), selected_method->name()->as_C_string());
+  }
+
   methodHandle method(THREAD, selected_method);
 
   // Create object to hold arguments for the JavaCall, and associate it with
diff --git a/hotspot/test/runtime/jni/abstractMethod/AbstractMethodClass.jasm b/hotspot/test/runtime/jni/abstractMethod/AbstractMethodClass.jasm
new file mode 100644
index 000000000..24c53f203
--- /dev/null
+++ b/hotspot/test/runtime/jni/abstractMethod/AbstractMethodClass.jasm
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+/*
+ *  This is a non-abstract class with an abstract method.
+ *
+ */
+super public class AbstractMethodClass
+      extends java/lang/Object
+              version 51:0  // Java 7 version
+{
+
+    public Method "<init>":"()V"
+       stack 1 locals 1
+    {
+        aload_0;
+        invokespecial Method java/lang/Object."<init>":"()V";
+        return;
+    }
+
+    public abstract Method "abstractM":"()V";
+
+}
diff --git a/hotspot/test/runtime/jni/abstractMethod/TestJNIAbstractMethod.java b/hotspot/test/runtime/jni/abstractMethod/TestJNIAbstractMethod.java
new file mode 100644
index 000000000..2384f6d5a
--- /dev/null
+++ b/hotspot/test/runtime/jni/abstractMethod/TestJNIAbstractMethod.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+/*
+ * @test
+ * @bug 8323243
+ * @summary Test that invocation of an abstract method from JNI works correctly
+ * @compile AbstractMethodClass.jasm
+ * @run main/othervm/native TestJNIAbstractMethod
+ */
+
+/**
+ * We are testing invocation of an abstract method from JNI - which should
+ * simply result in throwning AbstractMethodError. To invoke an abstract method
+ * we must have an instance method (as abstract static methods are illegal),
+ * but instantiating an abstract class is also illegal at the Java language
+ * level, so we have to use a custom jasm class that contains an abstract method
+ * declaration, but which is not itself declared as an abstract class.
+ */
+public class TestJNIAbstractMethod {
+
+    // Invokes an abstract method from JNI and throws AbstractMethodError.
+    private static native void invokeAbstractM(Class<?> AMclass,
+                                               AbstractMethodClass receiver);
+
+    static {
+        System.loadLibrary("JNIAbstractMethod");
+    }
+
+    public static void main(String[] args) {
+        AbstractMethodClass obj = new AbstractMethodClass();
+        try {
+            System.out.println("Attempting direct invocation via Java");
+            obj.abstractM();
+            throw new RuntimeException("Did not get AbstractMethodError from Java!");
+        } catch (AbstractMethodError expected) {
+            System.out.println("ok - got expected exception: " + expected);
+        }
+        try {
+            System.out.println("Attempting direct invocation via JNI");
+            invokeAbstractM(obj.getClass(), obj);
+            throw new RuntimeException("Did not get AbstractMethodError from JNI!");
+        } catch (AbstractMethodError expected) {
+            System.out.println("ok - got expected exception: " + expected);
+        }
+    }
+}
diff --git a/hotspot/test/runtime/jni/abstractMethod/libJNIAbstractMethod.c b/hotspot/test/runtime/jni/abstractMethod/libJNIAbstractMethod.c
new file mode 100644
index 000000000..35a28f702
--- /dev/null
+++ b/hotspot/test/runtime/jni/abstractMethod/libJNIAbstractMethod.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+#include <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+JNIEXPORT void JNICALL Java_TestJNIAbstractMethod_invokeAbstractM(JNIEnv* env,
+                                                                  jclass this_cls,
+                                                                  jclass target_cls,
+                                                                  jobject receiver) {
+
+  jmethodID mid = (*env)->GetMethodID(env, target_cls, "abstractM", "()V");
+  if (mid == NULL) {
+    fprintf(stderr, "Error looking up method abstractM\n");
+    (*env)->ExceptionDescribe(env);
+    exit(1);
+  }
+
+  printf("Invoking abstract method ...\n");
+  (*env)->CallVoidMethod(env, receiver, mid);  // Should raise exception
+
+}
diff --git a/jdk/src/share/classes/com/sun/media/sound/StandardMidiFileReader.java b/jdk/src/share/classes/com/sun/media/sound/StandardMidiFileReader.java
index 20ebe4f06..ba7f344a4 100644
--- a/jdk/src/share/classes/com/sun/media/sound/StandardMidiFileReader.java
+++ b/jdk/src/share/classes/com/sun/media/sound/StandardMidiFileReader.java
@@ -326,10 +326,10 @@ final class SMFParser {
             // reset current tick to 0
             long tick = 0;
 
-            // reset current status byte to 0 (invalid value).
+            // reset current running status byte to 0 (invalid value).
             // this should cause us to throw an InvalidMidiDataException if we don't
             // get a valid status byte from the beginning of the track.
-            int status = 0;
+            int runningStatus = 0;
             boolean endOfTrackFound = false;
 
             while (!trackFinished() && !endOfTrackFound) {
@@ -346,10 +346,17 @@ final class SMFParser {
                 // check for new status
                 int byteValue = readUnsigned();
 
+                int status;
                 if (byteValue >= 0x80) {
                     status = byteValue;
+
+                    // update running status (only for channel messages)
+                    if ((status & 0xF0) != 0xF0) {
+                        runningStatus = status;
+                    }
                 } else {
-                    data1 = byteValue;
+                    status = runningStatus;
+                    data1  = byteValue;
                 }
 
                 switch (status & 0xF0) {
diff --git a/jdk/src/share/classes/javax/swing/text/html/CSS.java b/jdk/src/share/classes/javax/swing/text/html/CSS.java
index 4a944d381..4713bcd60 100644
--- a/jdk/src/share/classes/javax/swing/text/html/CSS.java
+++ b/jdk/src/share/classes/javax/swing/text/html/CSS.java
@@ -2581,8 +2581,8 @@ public class CSS implements Serializable {
      * Used for BackgroundImages.
      */
     static class BackgroundImage extends CssValue {
-        private boolean    loadedImage;
-        private ImageIcon  image;
+        private volatile boolean loadedImage;
+        private ImageIcon image;
 
         Object parseCssValue(String value) {
             BackgroundImage retValue = new BackgroundImage();
@@ -2600,7 +2600,6 @@ public class CSS implements Serializable {
                 synchronized(this) {
                     if (!loadedImage) {
                         URL url = CSS.getURL(base, svalue);
-                        loadedImage = true;
                         if (url != null) {
                             image = new ImageIcon();
                             Image tmpImg = Toolkit.getDefaultToolkit().createImage(url);
@@ -2608,6 +2607,7 @@ public class CSS implements Serializable {
                                 image.setImage(tmpImg);
                             }
                         }
+                        loadedImage = true;
                     }
                 }
             }
diff --git a/jdk/src/share/native/java/util/zip/zip_util.c b/jdk/src/share/native/java/util/zip/zip_util.c
index ff59c5ecc..8b0c08909 100644
--- a/jdk/src/share/native/java/util/zip/zip_util.c
+++ b/jdk/src/share/native/java/util/zip/zip_util.c
@@ -443,7 +443,7 @@ hash(const char *s)
 static unsigned int
 hashN(const char *s, int length)
 {
-    int h = 0;
+    unsigned int h = 0;
     while (length-- > 0)
         h = 31*h + *s++;
     return h;
diff --git a/jdk/src/share/native/sun/awt/image/jpeg/imageioJPEG.c b/jdk/src/share/native/sun/awt/image/jpeg/imageioJPEG.c
index 7e1d8c99d..1cd9e8bdb 100644
--- a/jdk/src/share/native/sun/awt/image/jpeg/imageioJPEG.c
+++ b/jdk/src/share/native/sun/awt/image/jpeg/imageioJPEG.c
@@ -1131,6 +1131,10 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
         return;
     }
     num_bytes += sb->remaining_skip;
+    // Check for overflow if remaining_skip value is too large
+    if (num_bytes < 0) {
+        return;
+    }
     sb->remaining_skip = 0;
 
     /* First the easy case where we are skipping <= the current contents. */
diff --git a/jdk/src/share/native/sun/awt/image/jpeg/jpegdecoder.c b/jdk/src/share/native/sun/awt/image/jpeg/jpegdecoder.c
index cea158e17..2f64d33cc 100644
--- a/jdk/src/share/native/sun/awt/image/jpeg/jpegdecoder.c
+++ b/jdk/src/share/native/sun/awt/image/jpeg/jpegdecoder.c
@@ -406,6 +406,10 @@ sun_jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
         return;
     }
     num_bytes += src->remaining_skip;
+    // Check for overflow if remaining_skip value is too large
+    if (num_bytes < 0) {
+        return;
+    }
     src->remaining_skip = 0;
     ret = (int)src->pub.bytes_in_buffer; /* this conversion is safe, because capacity of the buffer is limited by jnit */
     if (ret >= num_bytes) {
diff --git a/jdk/test/javax/sound/midi/File/SMFInterruptedRunningStatus.java b/jdk/test/javax/sound/midi/File/SMFInterruptedRunningStatus.java
new file mode 100644
index 000000000..1b82e2f73
--- /dev/null
+++ b/jdk/test/javax/sound/midi/File/SMFInterruptedRunningStatus.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.ByteArrayInputStream;
+
+import javax.sound.midi.MidiSystem;
+import javax.sound.midi.Sequence;
+import javax.sound.midi.Track;
+
+/**
+ * @test
+ * @bug 8319598
+ * @summary SMFParser bug with running status, interrupted by Meta or SysEx messages
+ */
+public class SMFInterruptedRunningStatus {
+
+    public static void main(String[] args) throws Exception {
+
+        byte[][] files = new byte[][] {SMF_1, SMF_2, SMF_3};
+        for (int i = 0; i < files.length; i++) {
+            Sequence seq = MidiSystem.getSequence(
+                    new ByteArrayInputStream(files[i]));
+            testSequence(seq, i + 1);
+        }
+
+        // no exception thrown, all files have been parsed correctly
+        System.out.println("Test passed");
+    }
+
+    private static void testSequence(Sequence seq, int fileNumber) {
+
+        // check number of tracks and number of events
+        Track[] tracks = seq.getTracks();
+        if (1 != tracks.length) {
+            throw new RuntimeException("file number "
+                    + fileNumber + " fails (incorrect number of tracks: "
+                    + tracks.length + ")");
+        }
+        Track track = tracks[0];
+        if (7 != track.size()) {
+            throw new RuntimeException("file number " + fileNumber
+                    + " fails (incorrect number of events: "
+                    + track.size() + ")");
+        }
+
+        // check status byte of each message
+        int[] expectedStatusBytes = new int[] {
+                0x90, 0xFF, 0x90, 0x90, 0x90, 0xFF, 0xFF};
+        for (int i = 0; i < expectedStatusBytes.length; i++) {
+            int expected = expectedStatusBytes[i];
+            if (expected != track.get(i).getMessage().getStatus()) {
+                throw new RuntimeException("file number " + fileNumber
+                        + " fails (wrong status byte in event " + i + ")");
+            }
+        }
+    }
+
+    // MIDI file without running status - should work equally before
+    // and after the bugfix
+    private static final byte[] SMF_1 = {
+        0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06,  // file header (start)
+        0x00, 0x01, 0x00, 0x01, 0x00, (byte) 0x80,       // file header (end)
+        0x4D, 0x54, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x24,  // track header
+        0x00,                                            // delta time
+        (byte) 0x90, 0x3C, 0x7F,                         // Note-ON (C)
+        0x40,                                            // delta time
+        (byte) 0xFF, 0x01, 0x04, 0x54, 0x65, 0x73, 0x74, // META (text)
+        0x20,                                            // delta time
+        (byte) 0x90, 0x3C, 0x00,                         // Note-OFF (C)
+        0x20,                                            // delta time
+        (byte) 0x90, 0x3E, 0x7F,                         // Note-ON (D)
+        0x60,                                            // delta time
+        (byte) 0x90, 0x3E, 0x00,                         // Note-OFF (D)
+        0x20,                                            // delta time
+        (byte) 0xFF, 0x01, 0x04, 0x54, 0x65, 0x73, 0x74, // META (text)
+        0x00,                                            // delta time
+        (byte) 0xFF, 0x2F, 0x00                          // META (end of track)
+    };
+
+    // MIDI file with running status, interrupted by a META message
+    // - failed before the bugfix
+    private static final byte[] SMF_2 = {
+        0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06,  // file header (start)
+        0x00, 0x01, 0x00, 0x01, 0x00, (byte) 0x80,       // file header (end)
+        0x4D, 0x54, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x21,  // track header
+        0x00,                                            // delta time
+        (byte) 0x90, 0x3C, 0x7F,                         // Note-ON (C)
+        0x40,                                            // delta time
+        (byte) 0xFF, 0x01, 0x04, 0x54, 0x65, 0x73, 0x74, // META (interruptor)
+        0x20,                                            // delta time
+        0x3C, 0x00,                                      // Note-OFF (C) - running status
+        0x20,                                            // delta time
+        0x3E, 0x7F,                                      // Note-ON (D) - running status
+        0x60,                                            // delta time
+        0x3E, 0x00,                                      // Note-OFF (D) - running status
+        0x20,                                            // delta time
+        (byte) 0xFF, 0x01, 0x04, 0x54, 0x65, 0x73, 0x74, // META (text)
+        0x00,                                            // delta time
+        (byte) 0xFF, 0x2F, 0x00                          // META (end of track)
+    };
+
+    // MIDI file with running status, interrupted by a META message
+    // - succeeded before the bugfix but with wrong interpretation of the data
+    private static final byte[] SMF_3 = {
+        0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06,  // file header (start)
+        0x00, 0x01, 0x00, 0x01, 0x00, (byte) 0x80,       // file header (end)
+        0x4D, 0x54, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x21,  // track header
+        0x00,                                            // delta time
+        (byte) 0x90, 0x3C, 0x7F,                         // Note-ON (C)
+        0x40,                                            // delta time
+        (byte) 0xFF, 0x01, 0x04, 0x54, 0x65, 0x73, 0x74, // META (interruptor)
+        0x20,                                            // delta time
+        0x3C, 0x00,                                      // Note-OFF (C) - running status
+        0x0D,                                            // delta time
+        0x3E, 0x7F,                                      // Note-ON (D) - running status
+        0x60,                                            // delta time
+        0x3E, 0x00,                                      // Note-OFF (D) - running status
+        0x20,                                            // delta time
+        (byte) 0xFF, 0x01, 0x04, 0x54, 0x65, 0x73, 0x74, // META (text)
+        0x00,                                            // delta time
+        (byte) 0xFF, 0x2F, 0x00                          // META (end of track)
+    };
+}
-- 
2.23.0

