|
1 | 1 | package com.urielsalis.mccrashlib.parser
|
2 | 2 |
|
3 | 3 | import arrow.core.Either
|
4 |
| -import arrow.core.firstOrNone |
5 | 4 | import com.urielsalis.mccrashlib.Crash
|
6 | 5 | import java.io.File
|
7 | 6 |
|
8 |
| -/* |
9 |
| - All the lines of the crash start with # |
10 |
| - The line we care about is |
11 |
| - # C [xxxxxxxx+0x1c82] |
12 |
| - where we want to extract the xxxxxx |
13 |
| - */ |
| 7 | +// https://github.com/openjdk/jdk/blob/829dea45c9ab90518f03a66aad7e681cd4fda8b3/src/hotspot/share/utilities/vmError.cpp#L537 |
| 8 | +internal const val JVM_CRASH_HEADER = "# A fatal error has been detected by the Java Runtime Environment:" |
| 9 | + |
| 10 | +// https://github.com/openjdk/jdk/blob/829dea45c9ab90518f03a66aad7e681cd4fda8b3/src/hotspot/share/utilities/vmError.cpp#L700 |
| 11 | +private const val PROBLEMATIC_FRAME_MARKER = "# Problematic frame:" |
| 12 | +// https://github.com/openjdk/jdk/blob/829dea45c9ab90518f03a66aad7e681cd4fda8b3/src/hotspot/share/utilities/vmError.cpp#L236 |
| 13 | +private const val JAVA_STACK_TRACE_MARKER = "Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)" |
| 14 | + |
| 15 | +private val NATIVE_STACK_TRACE_MARKERS = listOf( |
| 16 | + // https://github.com/openjdk/jdk/blob/829dea45c9ab90518f03a66aad7e681cd4fda8b3/src/hotspot/share/utilities/vmError.cpp#L350 |
| 17 | + "Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)", |
| 18 | + // Before JDK-8264805 |
| 19 | + // https://github.com/openjdk/jdk/blob/15d4787724ad8723d36e771a9709db51933df2c1/src/hotspot/share/utilities/vmError.cpp#L245 |
| 20 | + "Native frames: (J=compiled Java code, A=aot compiled Java code, j=interpreted, Vv=VM code, C=native code)" |
| 21 | +) |
| 22 | + |
14 | 23 | class JvmCrashParser : CrashParser {
|
15 |
| - object IncompleteJvmCrash : ParserError |
| 24 | + /** If present removes the prefix, otherwise returns `null` */ |
| 25 | + private fun String.removeRequiredPrefix(prefix: String): String? { |
| 26 | + return if (startsWith(prefix)) substring(prefix.length) else null |
| 27 | + } |
| 28 | + |
| 29 | + private fun parseFrame(frameLine: String): Crash.JvmFrame { |
| 30 | + if (frameLine.length >= 2 && frameLine[1] == ' ') { |
| 31 | + val frameTypeChar = frameLine[0] |
| 32 | + // Use `trimStart()` because some frames have two spaces between type char and location |
| 33 | + val frameLocation = frameLine.substring(2).trimStart() |
| 34 | + |
| 35 | + // https://github.com/openjdk/jdk/blob/829dea45c9ab90518f03a66aad7e681cd4fda8b3/src/hotspot/share/runtime/frame.cpp#L569-L574 |
| 36 | + return when (frameTypeChar) { |
| 37 | + // Not parsing most of these because they are not relevant for Arisa |
| 38 | + 'J' -> Crash.JvmFrame.JavaFrameCompiled(frameLocation) |
| 39 | + 'j' -> Crash.JvmFrame.JavaFrameInterpreted(frameLocation) |
| 40 | + 'V' -> Crash.JvmFrame.VmFrame(frameLocation) |
| 41 | + 'v' -> Crash.JvmFrame.VmGeneratedFrame(frameLocation) |
| 42 | + 'C' -> parseCFrame(frameLocation) |
| 43 | + else -> Crash.JvmFrame.OtherFrame(frameLine) |
| 44 | + } |
| 45 | + } |
| 46 | + return Crash.JvmFrame.OtherFrame(frameLine) |
| 47 | + } |
| 48 | + |
| 49 | + // https://github.com/openjdk/jdk/blob/829dea45c9ab90518f03a66aad7e681cd4fda8b3/src/hotspot/share/runtime/frame.cpp#L536 |
| 50 | + /** |
| 51 | + * Groups: |
| 52 | + * 1. library name |
| 53 | + * 2. library offset |
| 54 | + * 3. function name (optional) |
| 55 | + * 4. function offset (optional) |
| 56 | + * |
| 57 | + * Only tries to parse function name and offset when library location was parsed successfully; |
| 58 | + * otherwise it might be ambiguous. |
| 59 | + */ |
| 60 | + private val C_FRAME_REGEX = Regex("""\[(.+)\+(.+)\](?: (.+)\+(.+))?""") |
| 61 | + private fun parseCFrame(frameLocation: String): Crash.JvmFrame.CFrame { |
| 62 | + var libraryName: String? = null |
| 63 | + var libraryOffset: String? = null |
| 64 | + var functionName: String? = null |
| 65 | + var functionOffset: String? = null |
| 66 | + |
| 67 | + C_FRAME_REGEX.matchEntire(frameLocation)?.groups?.apply { |
| 68 | + libraryName = get(1)?.value |
| 69 | + libraryOffset = get(2)?.value |
| 70 | + functionName = get(3)?.value |
| 71 | + functionOffset = get(4)?.value |
| 72 | + } |
| 73 | + |
| 74 | + return Crash.JvmFrame.CFrame(frameLocation, libraryName, libraryOffset, functionName, functionOffset) |
| 75 | + } |
| 76 | + |
| 77 | + private fun parseStackTrace(lines: List<String>, marker: String): List<Crash.JvmFrame>? { |
| 78 | + val markerIndex = lines.indexOf(marker) |
| 79 | + if (markerIndex == -1) { |
| 80 | + return null |
| 81 | + } |
| 82 | + |
| 83 | + val stackTrace = mutableListOf<Crash.JvmFrame>() |
| 84 | + for (i in markerIndex + 1 until lines.size) { |
| 85 | + val line = lines[i] |
| 86 | + if (line.isBlank()) { |
| 87 | + break |
| 88 | + } |
| 89 | + |
| 90 | + stackTrace.add(parseFrame(line)) |
| 91 | + } |
| 92 | + return stackTrace |
| 93 | + } |
| 94 | + |
| 95 | + private fun <E> List<E>.indexOf(e: E, startIndex: Int): Int { |
| 96 | + val i = subList(startIndex, size).indexOf(e) |
| 97 | + return if (i == -1) i else startIndex + i |
| 98 | + } |
16 | 99 |
|
17 | 100 | override fun parse(lines: List<String>, mappingsDirectory: File): Either<ParserError, Crash> {
|
18 |
| - val linesWithMarker = lines.map(String::trim).filter { it.startsWith("#") } |
19 |
| - val importantLine = linesWithMarker.firstOrNone { it.startsWith("# C") && "[" in it && "+" in it } |
20 |
| - return importantLine.fold( |
21 |
| - { Either.left(IncompleteJvmCrash) }, |
22 |
| - { Either.right(Crash.Jvm( |
23 |
| - isModded(lines), |
24 |
| - it.substring(it.indexOf('[') + 1, it.indexOf('+'))) |
25 |
| - ) } |
26 |
| - ) |
| 101 | + val trimmedLines = lines.map(String::trim) |
| 102 | + |
| 103 | + val crashHeaderIndex = trimmedLines.indexOf(JVM_CRASH_HEADER) |
| 104 | + if (crashHeaderIndex == -1) { |
| 105 | + throw IllegalArgumentException("Not a JVM crash") |
| 106 | + } |
| 107 | + |
| 108 | + // Find next line containing only '#' followed by line containing exception |
| 109 | + // https://github.com/openjdk/jdk/blob/829dea45c9ab90518f03a66aad7e681cd4fda8b3/src/hotspot/share/utilities/vmError.cpp#L643-L644 |
| 110 | + var exception: String? = null |
| 111 | + val nextEmptyIndex = lines.indexOf("#", crashHeaderIndex) |
| 112 | + if (nextEmptyIndex != -1) { |
| 113 | + exception = lines.getOrNull(nextEmptyIndex + 1)?.removeRequiredPrefix("# ") |
| 114 | + } |
| 115 | + |
| 116 | + val frameMarkerIndex = trimmedLines.indexOf(PROBLEMATIC_FRAME_MARKER) |
| 117 | + var frame: Crash.JvmFrame? = null |
| 118 | + |
| 119 | + if (frameMarkerIndex != -1) { |
| 120 | + val frameLine = trimmedLines.getOrNull(frameMarkerIndex + 1)?.removeRequiredPrefix("# ") |
| 121 | + frame = frameLine?.let(::parseFrame) |
| 122 | + } |
| 123 | + |
| 124 | + return Either.right(Crash.Jvm( |
| 125 | + exception, |
| 126 | + frame, |
| 127 | + // Find first matching marker |
| 128 | + NATIVE_STACK_TRACE_MARKERS.asSequence() |
| 129 | + .mapNotNull { marker -> parseStackTrace(trimmedLines, marker) } |
| 130 | + .firstOrNull(), |
| 131 | + parseStackTrace(trimmedLines, JAVA_STACK_TRACE_MARKER), |
| 132 | + isModded(lines) |
| 133 | + )) |
27 | 134 | }
|
28 | 135 |
|
29 | 136 | private fun isModded(lines: List<String>): Boolean {
|
|
0 commit comments