/* * Copyright 2011 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.lang; import java.io.Serializable; import java.util.Objects; /*-[ #import "IOSClass.h" #import "java/lang/ClassNotFoundException.h" #import <execinfo.h> ]-*/ /** * Simple iOS version of java.lang.StackTraceElement. * * @author Pankaj Kakkar */ public class StackTraceElement implements Serializable { private String declaringClass; private String methodName; private String fileName; private final int lineNumber; private transient long address; private transient String hexAddress; private transient String offset; public String getClassName() { initializeFromAddress(); return declaringClass; } public String getMethodName() { initializeFromAddress(); return methodName; } public String getFileName() { return fileName; } public int getLineNumber() { return lineNumber; } public StackTraceElement(String className, String methodName, String fileName, int lineNumber) { this.declaringClass = className; this.methodName = methodName; this.fileName = fileName; this.lineNumber = lineNumber; } StackTraceElement(long address) { this(null, null, null, -1); this.address = address; } public String toString() { initializeFromAddress(); StringBuilder sb = new StringBuilder(); sb.append(hexAddress); sb.append(" "); if (declaringClass != null) { sb.append(declaringClass); sb.append('.'); } if (methodName != null) { sb.append(methodName); } if (fileName != null || lineNumber != -1) { sb.append('('); if (fileName != null) { sb.append(fileName); } if (lineNumber != -1) { sb.append(':'); sb.append(lineNumber); } sb.append(')'); } else if (declaringClass != null) { sb.append("()"); } if (offset != null) { sb.append(" + "); sb.append(offset); } return sb.toString(); } /** * Returns true if the specified object is another * {@code StackTraceElement} instance representing the same execution * point as this instance. Two stack trace elements {@code a} and * {@code b} are equal if and only if: * <pre> * equals(a.getFileName(), b.getFileName()) && * a.getLineNumber() == b.getLineNumber()) && * equals(a.getClassName(), b.getClassName()) && * equals(a.getMethodName(), b.getMethodName()) * </pre> * where {@code equals} has the semantics of {@link * java.util.Objects#equals(Object, Object) Objects.equals}. * * @param obj the object to be compared with this stack trace element. * @return true if the specified object is another * {@code StackTraceElement} instance representing the same * execution point as this instance. */ public boolean equals(Object obj) { if (obj==this) return true; if (!(obj instanceof StackTraceElement)) return false; StackTraceElement e = (StackTraceElement)obj; return e.declaringClass.equals(declaringClass) && e.lineNumber == lineNumber && Objects.equals(methodName, e.methodName) && Objects.equals(fileName, e.fileName); } /** * Returns a hash code value for this stack trace element. */ public int hashCode() { int result = 31*declaringClass.hashCode() + methodName.hashCode(); result = 31*result + Objects.hashCode(fileName); result = 31*result + lineNumber; return result; } private static final long serialVersionUID = 6992337162326171013L; /*-[ static NSString *ExtractMethodName( char *rawName, char paramSeparator, NSStringEncoding encoding) { char *hasParamSeparator = strchr(rawName, paramSeparator); if (hasParamSeparator) { char *paramsStart = strstr(rawName, "With"); if (paramsStart) { *paramsStart = '\0'; } } if (strcmp(rawName, "init") == 0) { return @"<init>"; } if (strcmp(rawName, "initialize") == 0) { return @"<clinit>"; } return [[NSString alloc] initWithCString:rawName encoding:encoding]; } ]-*/ /*-[ // This is based on undocumented details of the Swift compiler as reverse-engineered by several // sources online, and it doesn't attempt to be comprehensive, but tries to demangle the most // common type of object instance methods. static void DemangleSwiftMethod( JavaLangStackTraceElement *self, char *start) { // "_T" is a global Swift marker. "F" means this symbol refers to a function/method. if (0 != bcmp(start, "_TF", 3)) return; // Next comes a series of "C" to represent the declaring type in terms of nested classes. // Other non-decimal characters can appear here, but I'm not sure what they mean so we'll bail. start += 3; while (*start == 'C') { start++; } if (*start < '0' || *start > '9') return; // Next up is a series of length-prefixed names, starting with the module name, followed // by nested class names, and finally ending with the function name. #define MAX_SWIFT_NESTING 8 NSMutableArray *names = [[NSMutableArray alloc] initWithCapacity:MAX_SWIFT_NESTING]; char *lenEnd; BOOL ignoreName = NO; while (*start && [names count] < MAX_SWIFT_NESTING) { if (*start == 'P') { // Apparently private functions have a random(?) hexidecimal component preceding the real // name. It's marked by a 'P' prior to the length of that hexidecimal component. start++; ignoreName = YES; } else ignoreName = NO; long len = strtol(start, &lenEnd, 10); if (start == lenEnd) { break; } if (!ignoreName) { NSString *name = [[NSString alloc] initWithBytes:lenEnd length:len encoding:NSASCIIStringEncoding]; [names addObject:name]; RELEASE_(name); } start = lenEnd + len; } if (start != lenEnd || [names count] < 2) { RELEASE_(names); return; } self->methodName_ = RETAIN_([names lastObject]); [names removeLastObject]; self->declaringClass_ = RETAIN_([names componentsJoinedByString:@"."]); RELEASE_(names); } ]-*/ /** * Implements lazy loading of symbol information from application. */ private native void initializeFromAddress() /*-[ if (self->address_ == 0L || self->methodName_) { 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. char *addressEnd = strstr(start, " "); char *hex = strndup(start, addressEnd - start); self->hexAddress_ = [[NSString alloc] initWithCString:hex encoding:encoding]; free(hex); start = addressEnd + 1; // Extract the offset if symbol looks like "<method> + 123". char *offset = strstr(start, " + "); if (offset) { self->offset_ = [[NSString alloc] initWithCString:offset + 3 encoding:encoding]; *offset = '\0'; } // See if a class and method names can be extracted. char *leftBrace = strchr(start, '['); char *rightBrace = strchr(start, ']'); if (leftBrace && rightBrace && (rightBrace - leftBrace) > 0) { char *signature = leftBrace + 1; char *className = strsep(&signature, "[ ]"); if (className && strlen(className) > 0) { IOSClass *cls = [IOSClass classForIosName: [NSString stringWithCString:className encoding:encoding]]; if (cls) { self->declaringClass_ = RETAIN_([cls getName]); } } char *selector = strsep(&signature, "[ ]"); if (selector) { self->methodName_ = ExtractMethodName(selector, ':', encoding); } } else { // Functionized method. Look for the class name portion. IOSClass *cls = nil; // Search backwards for '_' so that we find inner classes before their // outer class. char *idx = start; while (*idx) { idx++; } while (!cls) { while (--idx > start && *idx != '_'); if (idx == start) { break; } NSString *className = [[[NSString alloc] initWithBytesNoCopy:start length:idx - start encoding:encoding freeWhenDone:false] autorelease]; cls = [IOSClass classForIosName:className]; } if (cls) { self->declaringClass_ = RETAIN_([cls getName]); start = idx + 1; } else { // Try to demangle Swift symbol. If it succeeds, methodName_ and declaringClass_ will be // populated. DemangleSwiftMethod(self, start); } if (!self->methodName_) { self->methodName_ = ExtractMethodName(start, '_', encoding); } } free(stackSymbol); ]-*/; }