How to Parse (File, Package, Function Name) from a Stack Trace in ActionScript (AS3)

So stack traces can look different depending on the flash player in which it is running. For example in the Flash IDE standalone player a constructor will look like this ConstructorFunctionName$iinit() but in the player running in a web page it will just look like ConstructorFunctionName(). It is handy for debugging purposes to extract data like the FLA file name of the SWF, the package where the error has been thrown, the class where the error was thrown and the function where the error was thrown. Keep in mind after Flash 10 the stack trace feature is only usable in the debugger version of the Flash player. In non-debugger versions e.getStackTrace() will crash your program. I guess they removed the feature for security reasons so that an attacker has less tools at his disposal to reverse engineer code.

//first thing that needs to be done is an error needs to be thrown, 
//this will give you a stack trace for the place where the error was thrown.
function test():void {
var parsedData:Object;
var stackTrace:String;
//here is where the error is artificially thrown,
try {
throw new Error("");
} catch (e:Error) {
stackTrace = e.getStackTrace();
}
parsedData = parseDataFromStackTrace(stackTrace);
trace("fileName : "+parsedData.fileName);
trace("packageName : "+parsedData.packageName);
trace("className : "+parsedData.className);
trace("functionName : "+parsedData.functionName);
}

test();



function parseDataFromStackTrace(stackTrace:String):Object {
//extract function name from the stack trace
var parsedDataObj:Object = {fileName:"",packageName:"",className:"",functionName:""};
var nameResults:Array;
//extract the package from the class name
var matchExpression:RegExp;
var isFileNameFound:Boolean;
//if running in debugger you are going to remove that data
var removeDebuggerData:RegExp;
removeDebuggerData = /\[.*?\]/msgi;
stackTrace = stackTrace.replace(removeDebuggerData,"");
//remove the Error message at the top of the stack trace
var removeTop:RegExp;
removeTop = /^Error.*?at\s/msi;
stackTrace = stackTrace.replace(removeTop,"");
stackTrace = "at "+stackTrace;
//get file name
matchExpression = /(at\s)*(.*?)_fla::/i;
nameResults = stackTrace.match(matchExpression);
if (nameResults != null && nameResults.length > 2) {
parsedDataObj.fileName = nameResults[2];
parsedDataObj.fileName = parsedDataObj.fileName.replace(/^\s*at\s/i,"")+".fla";
isFileNameFound = true;
}
//match timeline data
matchExpression = /^at\s(.*?)::(.*?)\/(.*?)::(.*?)\(\)/i;
nameResults = stackTrace.match(matchExpression);
if (nameResults != null && nameResults.length > 4) {
if (!isFileNameFound) {
parsedDataObj.fileName = String(nameResults[1]).replace(/_fla$/i,".fla");
parsedDataObj.fileName = parsedDataObj.fileName.replace(/^at\s/i,"");
}
parsedDataObj.packageName = String(nameResults[1]);
parsedDataObj.className = String(nameResults[2]);
parsedDataObj.functionName = String(nameResults[4]);
} else {
//match function in a class of format com.package::SomeClass/somefunction()
matchExpression = /^at\s(.*?)::(.*?)\/(.*?)\(\)/i;
nameResults = stackTrace.match(matchExpression);
if (nameResults != null && nameResults.length > 3) {
if (!isFileNameFound) {
parsedDataObj.fileName = String(nameResults[2])+".as";
}
parsedDataObj.packageName = nameResults[1];
parsedDataObj.className = nameResults[2];
parsedDataObj.functionName = String(nameResults[3]);
} else {
//match a contructor with $iinit
matchExpression = /^at\s(.*?)::(.*?)\$(.*?)\(\)/i;
nameResults = stackTrace.match(matchExpression);
if (nameResults != null && nameResults.length > 3) {
if (!isFileNameFound) {
parsedDataObj.fileName = String(nameResults[2])+".as";
}
parsedDataObj.packageName = String(nameResults[1]);
parsedDataObj.className = String(nameResults[2]);
parsedDataObj.functionName = String(nameResults[2]);
} else {
//match a constructor that looks like this com.package::SomeClassConstructor()
matchExpression = /^at\s(.*?)::(.*?)\(\)/i;
nameResults = stackTrace.match(matchExpression);
if (nameResults != null && nameResults.length > 2) {
if (!isFileNameFound) {
parsedDataObj.fileName = String(nameResults[2])+".as";
}
parsedDataObj.packageName = String(nameResults[1]);
parsedDataObj.className = String(nameResults[2]);
parsedDataObj.functionName = String(nameResults[2]);
} else {
//can't find a match - this is a catch all, you never know,
//if you find situations where this does not work please ,
//post the solution in the comments.
if (!isFileNameFound) {
parsedDataObj.fileName = "NO_DATA";
}
parsedDataObj.packageName = "NO_DATA";
parsedDataObj.className = "NO_DATA";
parsedDataObj.functionName = "NO_DATA";
}
}
}
}
return parsedDataObj;
}


comments powered by Disqus