package com.intellij.lang.javascript.flex.debug; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.util.Trinity; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtil; import com.intellij.xdebugger.XDebuggerUtil; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.frame.XExecutionStack; import com.intellij.xdebugger.frame.XStackFrame; import com.intellij.xdebugger.frame.XSuspendContext; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class FlexSuspendContext extends XSuspendContext { private final FlexExecutionStack myFlexExecutionStack; private static final Pattern STACK_FRAMES_DELIMITER = Pattern.compile(".(\\r?\\n)#\\d+ "); private static final String AT_MARKER = "at "; public FlexSuspendContext(final FlexStackFrame topFrame) { myFlexExecutionStack = new FlexExecutionStack(topFrame); } public FlexSuspendContext(final FlexDebugProcess flexDebugProcess, final String[] frames) { myFlexExecutionStack = new FlexExecutionStack(createStackFrame(flexDebugProcess, frames[0])); myFlexExecutionStack.myAprioriKnownFrames = myFlexExecutionStack.getFrames(frames); } public XExecutionStack getActiveExecutionStack() { return myFlexExecutionStack; } private static class FlexExecutionStack extends XExecutionStack { private final FlexStackFrame myTopFrame; private @Nullable List<XStackFrame> myAprioriKnownFrames; private FlexExecutionStack(final FlexStackFrame topFrame) { super(""); myTopFrame = topFrame; } public XStackFrame getTopFrame() { return myTopFrame; } public void computeStackFrames(final int frameIndex, final XStackFrameContainer container) { if (myAprioriKnownFrames != null) { container.addStackFrames(myAprioriKnownFrames.subList(frameIndex, myAprioriKnownFrames.size()), true); } else { myTopFrame.getDebugProcess().sendCommand(new DebuggerCommand("bt", CommandOutputProcessingType.SPECIAL_PROCESSING) { @Override CommandOutputProcessingMode onTextAvailable(@NonNls final String s) { if (container.isObsolete()) return CommandOutputProcessingMode.DONE; final List<XStackFrame> frames = getFrames(splitStackFrames(s)); container.addStackFrames(frames.subList(frameIndex, frames.size()), true); return CommandOutputProcessingMode.DONE; } }); } } private List<XStackFrame> getFrames(String[] frames) { final XStackFrame[] allFrames = new XStackFrame[frames.length]; int i = 0; final FlexDebugProcess flexDebugProcess = myTopFrame.getDebugProcess(); if (frames.length == 0) { return Collections.emptyList(); } String frameText = frames[i]; myTopFrame.setScope(extractScope(frameText)); myTopFrame.setFrameIndex(0); allFrames[i++] = myTopFrame; while (i < frames.length) { frameText = frames[i]; final FlexStackFrame flexStackFrame = createStackFrame(flexDebugProcess, frameText); allFrames[i] = flexStackFrame; flexStackFrame.setScope(extractScope(frameText)); flexStackFrame.setFrameIndex(i); i++; } return Arrays.asList(allFrames); } } private static FlexStackFrame createStackFrame(final FlexDebugProcess flexDebugProcess, final String frameText) { // #0 global$init() at Some4Class.as#42:6 // #0 FlexSprite() at FlexSprite.as:59 // #2 Some4Class() at Singleton.as#16:0 // #2 UIComponent() at <null>:0\r\nNo active session // #0 global/publicFun() at publicFun.as#41:5 // #0 HelloFlex4/button1_clickHandler(event=[Object 181591585, class='flash.events::MouseEvent']) at HelloFlex4.mxml#40:11 // #0 NameUtil$/createUniqueName(object=[Object 172624937, class='mx.controls::TextInput']) at NameUtil.as#29:65 // #0 EventDispatcher/dispatchEvent(_arg1=null) at <null>:0\r\nNo active session // #0 FlexEvent(type="valueCommit", bubbles=false, cancelable=false) at FlexEvent.as#18:1178 // #0 HelloFlex4/get abc() at HelloFlex4.mxml#40:18 // #0 HelloFlex4/set abc(value="Asd") at HelloFlex4.mxml#40:22 // #0 this = [Object 94314641, class='BackWorker'].BackWorker/onProgress(event=[Object 246336977, class='flash.events::ProgressEvent']) at BackWorker.as:36 // #1 EventDispatcher/dispatchEventFunction() at <null>:0 // #2 this = [Object 106360769, class='fr.kikko.lab::ShineMP3Encoder'].EventDispatcher/dispatchEvent(_arg1=[Object 246336977, class='flash.events::ProgressEvent']) at <null>:0 // #3 this = [Object 106360769, class='fr.kikko.lab::ShineMP3Encoder'].ShineMP3Encoder/update(event=[Object 246068457, class='flash.events::TimerEvent']) at ShineMP3Encoder.as:63 // #4 Timer/_timerDispatch() at <null>:0 // #5 this = [Object 152354881, class='flash.utils::Timer'].Timer/tick() at <null>:0 VirtualFile file = null; final Trinity<String, String, Integer> fileNameAndIndexAndLine = getFileNameAndIdAndLine(frameText); final String fileName = fileNameAndIndexAndLine.first; final String fileId = fileNameAndIndexAndLine.second; final int line = fileNameAndIndexAndLine.third; if (!StringUtil.isEmpty(fileName)) { file = flexDebugProcess.findFileByNameOrId(fileName, getPackageFromFrameText(frameText), fileId); if (file == null) { // todo find position in decompiled code } } final VirtualFile finalFile = file; final XSourcePosition sourcePosition = file == null ? null : ReadAction.compute( () -> XDebuggerUtil.getInstance().createPosition(finalFile, line > 0 ? line - 1 : line)); return sourcePosition != null ? new FlexStackFrame(flexDebugProcess, sourcePosition) : new FlexStackFrame(flexDebugProcess, fileName, line); } private static String getPackageFromFrameText(final String frameText) { // #2 this = [Object 106360769, class='fr.kikko.lab::ShineMP3Encoder'].EventDispatcher/dispatchEvent(_arg1=[Object 246336977, class='flash.events::ProgressEvent']) at <null>:0 String packageName = null; int startIndex = frameText.indexOf(' '); while (startIndex != -1 && frameText.length() > startIndex && frameText.charAt(startIndex) == ' ') { startIndex++; } if (startIndex > 0 && frameText.substring(startIndex).startsWith("this = [")) { final int classMarkerIndex = frameText.indexOf(FlexStackFrame.CLASS_MARKER, startIndex); final int packageEndIndex = frameText.indexOf("::", classMarkerIndex + FlexStackFrame.CLASS_MARKER.length()); final int classEndIndex = frameText.indexOf("']", classMarkerIndex + FlexStackFrame.CLASS_MARKER.length()); if (classMarkerIndex > 0 && packageEndIndex > classMarkerIndex && packageEndIndex < classEndIndex) { packageName = frameText.substring(classMarkerIndex + FlexStackFrame.CLASS_MARKER.length(), packageEndIndex); } else { packageName = ""; } } return packageName; } static String[] splitStackFrames(String s) { Matcher m = STACK_FRAMES_DELIMITER.matcher(s); ArrayList<String> result = new ArrayList<>(); int prev = 0; while (m.find()) { result.add(s.substring(prev, m.start(1))); prev = m.end(1); } result.add(s.substring(prev)); return ArrayUtil.toStringArray(result); } static String extractScope(final String stackFrame) { // #0 this = [Object 65683745, class='c::Bar$'].Bar$/get text() at Bar.as#23:7 // #0 this = [Object 403095729, class='DisplayShelf'].DisplayShelf() at DisplayShelf.as:156 // #2 apply() at <null>:65535 // #3 EventDispatcher/dispatchEventFunction() at <null>:0 int startIndex = stackFrame.indexOf(']'); if (startIndex == -1) { startIndex = stackFrame.indexOf(' '); while (startIndex != -1 && stackFrame.length() > startIndex + 1 && stackFrame.charAt(startIndex + 1) == ' ') { startIndex++; } } final int dotIndex = Math.max(startIndex, stackFrame.indexOf('.', startIndex + 1)); int lparIndex = stackFrame.indexOf('(', dotIndex); final String clsMethodText = stackFrame.substring(dotIndex + 1, lparIndex != -1 ? lparIndex : stackFrame.length()); int methodStart = clsMethodText.indexOf('/'); return methodStart == -1 ? clsMethodText : clsMethodText.substring(methodStart + 1) + ": " + clsMethodText.substring(0, methodStart); } private static Trinity<String, String, Integer> getFileNameAndIdAndLine(final String text) { final int atPos = text.lastIndexOf(AT_MARKER); if (atPos == -1) return Trinity.create(null, null, 0); final String fileName; String fileId = null; int line = 0; final int eolIndex = Math.min(text.indexOf("\r", atPos), text.indexOf("\n", atPos)); final String fileNameAndFurther = text.substring(atPos + AT_MARKER.length(), eolIndex > atPos ? eolIndex : text.length()); final int hashIndex = fileNameAndFurther.indexOf('#'); final int colonIndex = fileNameAndFurther.indexOf(':', hashIndex); if (hashIndex != -1) { fileName = fileNameAndFurther.substring(0, hashIndex); if (colonIndex > hashIndex) { fileId = fileNameAndFurther.substring(hashIndex + 1, colonIndex); try { line = Integer.parseInt(fileNameAndFurther.substring(colonIndex + 1)); } catch (NumberFormatException e) {/*ignore*/} } } else { if (colonIndex != -1) { fileName = fileNameAndFurther.substring(0, colonIndex); try { line = Integer.parseInt(fileNameAndFurther.substring(colonIndex + 1)); } catch (NumberFormatException e) {/*ignore*/} } else { fileName = fileNameAndFurther; } } return Trinity.create(fileName, fileId, line); } }