/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* This source file is part of the Cangjie project, licensed under Apache-2.0
* with Runtime Library Exception.
*
* See https://cangjie-lang.cn/pages/LICENSE for license information.
*/
package std.core
@Intrinsic
func fillInStackTrace(pc: RawArray<UInt64>, message: RawArray<UInt8>): RawArray<UInt64>
@Intrinsic
func decodeStackTrace(pc: UInt64, funcStart: UInt64, funcDesc: UInt64): StackTraceData
// This data structure is used to represent the StackTraceElement on the runtime.
// Get necessary datas by the intrinsic from runtime and construct StackTraceElement
// on frontend, i.e. func getStackTraceImpl(RawArray<UInt64>)
struct StackTraceData {
let className: RawArray<UInt8> = EMPTY_UINT8_RAW_ARRAY
let methodName: RawArray<UInt8> = EMPTY_UINT8_RAW_ARRAY
let fileName: RawArray<UInt8> = EMPTY_UINT8_RAW_ARRAY
let lineNumber: Int64 = 0
}
func getStackTraceImpl(pcArray: RawArray<UInt64>): Array<StackTraceElement> {
// The size of pcArray is 3n+1 when some frames are folded, and the last frame is invalid.
// In this case, discard the last frame.
let frameSize = arraySize(pcArray) / 3
let emptyElement = StackTraceElement("", "", "", 0)
let stackTraceArray = Array<StackTraceElement>(frameSize, repeat: emptyElement)
var eleSize: Int64 = 0
for (i in 0..frameSize) {
// PC and function start form one pair in pcArray
let data = decodeStackTrace(arrayGet(pcArray, i * 3), arrayGet(pcArray, i * 3 + 1), arrayGet(pcArray, i * 3 + 2))
// Ignore invalid stack trace data
if (data.lineNumber == 0 && arraySize(data.fileName) == 0) {
continue
}
if (data.lineNumber == -1) {
continue
}
let stkEle = StackTraceElement(
String(data.className),
String(data.methodName),
String(data.fileName),
data.lineNumber
)
stackTraceArray[eleSize] = stkEle
eleSize++
}
return stackTraceArray.clone(0..eleSize)
}
public open class Exception <: ToString {
private var detailMessage: String = String.empty
private var pcArray: RawArray<UInt64> = RawArray<UInt64>(1, repeat: 0)
private var traceElementArray: Array<StackTraceElement> = []
private var innerCause: ?Exception = None
public init() {
pcArray = fillInStackTrace(RawArray<UInt64>(1, repeat: 0), detailMessage.toArray().rawptr)
}
public init(message: String) {
detailMessage = message
pcArray = fillInStackTrace(RawArray<UInt64>(1, repeat: 0), detailMessage.toArray().rawptr)
}
@When[env != "ohos"]
public init(message: String, causedBy: Exception) {
detailMessage = message
innerCause = causedBy
pcArray = fillInStackTrace(RawArray<UInt64>(1, repeat: 0), detailMessage.toArray().rawptr)
}
@When[env != "ohos"]
public init(causedBy: Exception) {
innerCause = causedBy
pcArray = fillInStackTrace(RawArray<UInt64>(1, repeat: 0), detailMessage.toArray().rawptr)
}
protected open func getClassName(): String {
return "Exception"
}
public open prop message: String {
get() {
return detailMessage
}
}
/*
* Cause of this exception, which may be null. The cause is the exception that caused this exception to get thrown.
* It is possible that `printStackTrace` will recurse infinitely and eventually cause a stack overflow. Be careful.
* To fix it, Exception should implement `Hashable & Equatable` and check if a cause has already been printed.
*/
@When[env != "ohos"]
public mut prop causedBy: ?Exception {
get() {
return innerCause
}
set(cause) {
innerCause = cause
}
}
public open func toString(): String {
let className: String = getClassName()
let message: String = this.message
if (message.isEmpty()) {
return className
}
return "${className}: ${message}"
}
public func printStackTrace(): Unit {
printStackTrace("An exception has occurred:\n", Array<StackTraceElement>())
}
/*
* Print the stack trace of this exception with a caption and enclosing trace.
*
* @param caption the caption to print before the stack trace
* @param enclosingTrace the enclosing stack trace if present (that is the stack trace of `causedBy`)
*/
func printStackTrace(caption: String, enclosingTrace: Array<StackTraceElement>): Unit {
eprintln("${caption}${toString()}")
let trace = getStackTrace()
var ourSize = trace.size - 1
var enclosingSize = enclosingTrace.size - 1
while (ourSize >= 0 && enclosingSize >= 0 && trace[ourSize].equals(enclosingTrace[enclosingSize])) {
ourSize--
enclosingSize--
}
let commonSize = trace.size - 1 - ourSize
for (i in 0..=ourSize) {
let element = trace[i]
if (element.methodName.contains("Exception::init")) {
continue
}
if (element.declaringClass.isEmpty() && element.methodName.isEmpty()) { // simple stack trace format
eprintln("\t at ${element.fileName}:${element.lineNumber}")
} else {
let packageDelimiter: String = if (element.declaringClass.isEmpty()) { "" } else { "::" }
eprintln(
"\t at ${element.declaringClass}${packageDelimiter}${element.methodName}(${element.fileName}:${element.lineNumber})")
}
}
if (commonSize > 0) {
eprintln("\t ... ${commonSize} more")
}
if (let Some(cause) <- innerCause) {
cause.printStackTrace("Caused by: ", trace)
}
}
public func getStackTrace(): Array<StackTraceElement> {
getStackTraceImpl(pcArray)
}
}