From baf933ecfce7ab81d6c2b299d73747173c91aa31 Mon Sep 17 00:00:00 2001 From: J2ObjC Team Date: Fri, 24 Apr 2026 13:23:44 -0700 Subject: [PATCH] Refactor nativeGetStackTrace to call backtrace_symbols only once when producing call stacks, rather than for every StackTraceElement. This trades off better performance for stack trace production, at the cost of losing lazy-loading for fetched but unlogged stack traces. PiperOrigin-RevId: 905192033 --- jre_emul/Classes/JavaThrowable.m | 37 ++++++++++------- .../Classes/java/lang/StackTraceElement.java | 41 ++++++++++++------- .../com/google/j2objc/ThrowableTest.java | 24 +++++++++++ 3 files changed, 72 insertions(+), 30 deletions(-) diff --git a/jre_emul/Classes/JavaThrowable.m b/jre_emul/Classes/JavaThrowable.m index b2ea767c72..660f9077c6 100644 --- a/jre_emul/Classes/JavaThrowable.m +++ b/jre_emul/Classes/JavaThrowable.m @@ -84,25 +84,26 @@ static bool ShouldFilterStackElement(JavaLangStackTraceElement *element) { return false; } -static void ProcessRawStack(RawStack *rawStack, NSMutableArray *frames, bool applyFilter) { - for (unsigned i = 0; i < rawStack->count_; i++) { - JavaLangStackTraceElement *element = - [[JavaLangStackTraceElement alloc] initWithLong:(jlong)rawStack->frames_[i]]; - if (!applyFilter || !ShouldFilterStackElement(element)) { - [frames addObject:element]; - } - [element release]; - } -} - jarray Java_java_lang_Throwable_nativeGetStackTrace( JNIEnv *_env_, jclass _cls_, jobject stackState) { RawStack *rawStack = stackState; NSMutableArray *frames = [NSMutableArray array]; - if (rawStack) { - ProcessRawStack(rawStack, frames, true); - JavaLangStackTraceElement *element = [frames lastObject]; + if (rawStack && rawStack->count_ > 0) { + char **stackSymbols = backtrace_symbols(rawStack->frames_, rawStack->count_); + NSStringEncoding encoding = [NSString defaultCStringEncoding]; + + for (unsigned i = 0; i < rawStack->count_; i++) { + NSString *symbolStr = [NSString stringWithCString:stackSymbols[i] encoding:encoding]; + JavaLangStackTraceElement *element = + JavaLangStackTraceElement_createFromSymbolWithLong_withNSString_((jlong)rawStack->frames_[i], symbolStr); + + if (!ShouldFilterStackElement(element)) { + [frames addObject:element]; + } + } + // Remove initial Method.invoke(), so app's main method is last. + JavaLangStackTraceElement *element = [frames lastObject]; if ([[element getClassName] isEqualToString:@"JavaLangReflectMethod"] && [[element getMethodName] isEqualToString:@"invoke"]) { [frames removeLastObject]; @@ -110,8 +111,14 @@ jarray Java_java_lang_Throwable_nativeGetStackTrace( // If symbols were removed, the stack trace will be empty at this point. // In order to help with debugging, return the raw stack trace. if ([frames count] == 0) { - ProcessRawStack(rawStack, frames, false); + for (unsigned i = 0; i < rawStack->count_; i++) { + NSString *symbolStr = [NSString stringWithCString:stackSymbols[i] encoding:encoding]; + JavaLangStackTraceElement *element = + JavaLangStackTraceElement_createFromSymbolWithLong_withNSString_((jlong)rawStack->frames_[i], symbolStr); + [frames addObject:element]; + } } + free(stackSymbols); } return [IOSObjectArray arrayWithNSArray:frames type:JavaLangStackTraceElement_class_()]; } diff --git a/jre_emul/Classes/java/lang/StackTraceElement.java b/jre_emul/Classes/java/lang/StackTraceElement.java index 5a35107897..ce730e2fd2 100644 --- a/jre_emul/Classes/java/lang/StackTraceElement.java +++ b/jre_emul/Classes/java/lang/StackTraceElement.java @@ -78,6 +78,13 @@ public StackTraceElement(String className, String methodName, String fileName, i this.address = address; } + public static native StackTraceElement createFromSymbol(long address, String symbol) /*-[ + JavaLangStackTraceElement *element = [[JavaLangStackTraceElement alloc] initWithLong:address]; + NSStringEncoding encoding = [NSString defaultCStringEncoding]; + PopulateElementFromSymbol(element, [symbol UTF8String], encoding); + return AUTORELEASE(element); + ]-*/; + @Override public String toString() { initializeFromAddress(); @@ -239,21 +246,10 @@ static void DemangleSwiftMethod( } ]-*/ - /** - * Implements lazy loading of symbol information from application. - */ - private native void initializeFromAddress() /*-[ - if (self->address_ == 0L // Is there an address to initialze from? - || self->hexAddress_) { // Already initialized? - return; - } - void *shortStack[1]; - shortStack[0] = (void *)self->address_; - char **stackSymbol = backtrace_symbols(shortStack, 1); - NSStringEncoding encoding = [NSString defaultCStringEncoding]; - - // Extract hexAddress. - char *start = strstr(*stackSymbol, "0x"); // Skip text before address. + /*-[ + static void PopulateElementFromSymbol(JavaLangStackTraceElement *self, const char *stackSymbol, NSStringEncoding encoding) { + char *symbolCopy = strdup(stackSymbol); + char *start = strstr(symbolCopy, "0x"); char *addressEnd = strstr(start, " "); char *hex = strndup(start, addressEnd - start); self->hexAddress_ = [[NSString alloc] initWithCString:hex encoding:encoding]; @@ -325,6 +321,21 @@ private native void initializeFromAddress() /*-[ if ([self->methodName_ isEqual:JavaLangStackTraceElement_UNKNOWN]) { self->methodName_ = JavaLangStackTraceElement_STRIPPED; } + free(symbolCopy); + } + ]-*/ + + /** Implements lazy loading of symbol information from application. */ + private native void initializeFromAddress() /*-[ + if (self->address_ == 0L // Is there an address to initialze from? + || self->hexAddress_) { // Already initialized? + return; + } + void *shortStack[1]; + shortStack[0] = (void *)self->address_; + char **stackSymbol = backtrace_symbols(shortStack, 1); + NSStringEncoding encoding = [NSString defaultCStringEncoding]; + PopulateElementFromSymbol(self, *stackSymbol, encoding); free(stackSymbol); ]-*/; } diff --git a/jre_emul/misc_tests/com/google/j2objc/ThrowableTest.java b/jre_emul/misc_tests/com/google/j2objc/ThrowableTest.java index ea5b67e95c..e242cb2fe6 100644 --- a/jre_emul/misc_tests/com/google/j2objc/ThrowableTest.java +++ b/jre_emul/misc_tests/com/google/j2objc/ThrowableTest.java @@ -18,6 +18,7 @@ import com.google.j2objc.util.ReflectionUtil; import java.io.ByteArrayOutputStream; +import java.lang.reflect.Method; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; @@ -123,6 +124,29 @@ public void testThrowableToStringFormat() { assertTrue(ReflectionUtil.matchClassNamePrefix(new Throwable("oops").toString(), expected)); } + public void testSymbolParsing() { + // A typical Objective-C symbol string generated by J2ObjC + String symbol = + "0 MyApp 0x0000000100000f14 [ComGoogleJ2objcThrowableTest" + + " testSymbolParsing] + 0"; + long address = 0x100000f14L; + + // Create a StackTraceElement from the symbol. + StackTraceElement element = StackTraceElement.createFromSymbol(address, symbol); + assertNotNull(element); + assertEquals("com.google.j2objc.ThrowableTest", element.getClassName()); + assertEquals("testSymbolParsing", element.getMethodName()); + + // Create an element via the public constructor and verify equivalence. + StackTraceElement expectedElement = + new StackTraceElement("com.google.j2objc.ThrowableTest", "testSymbolParsing", null, -1); + + assertEquals(expectedElement, element); + assertEquals(expectedElement.toString(), element.toString()); + assertEquals(expectedElement.getClassName(), element.getClassName()); + assertEquals(expectedElement.getMethodName(), element.getMethodName()); + } + public void testNSExceptionDescriptionUnchanged() { assertEquals(NSEXCEPTION_MESSAGE, getNSExceptionDescription()); }