diff --git a/cli/ballerina-cli/src/test/resources/test-resources/tool-gayals/.DS_Store b/cli/ballerina-cli/src/test/resources/test-resources/tool-gayals/.DS_Store deleted file mode 100644 index 1ecf824cf4e0..000000000000 Binary files a/cli/ballerina-cli/src/test/resources/test-resources/tool-gayals/.DS_Store and /dev/null differ diff --git a/cli/ballerina-cli/src/test/resources/test-resources/tool-gayals/target-dir/.DS_Store b/cli/ballerina-cli/src/test/resources/test-resources/tool-gayals/target-dir/.DS_Store deleted file mode 100644 index 9dae4ac68983..000000000000 Binary files a/cli/ballerina-cli/src/test/resources/test-resources/tool-gayals/target-dir/.DS_Store and /dev/null differ diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java index 8433049daa2f..8cb6dd0d22cc 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java @@ -234,7 +234,9 @@ void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode, boolea Location currentLocation = validFrames.get(0).getJStackFrame().location(); Optional prevLocation = context.getPrevLocation(); if (!validate || prevLocation.isEmpty() || !isWithinSameSource(currentLocation, prevLocation.get())) { - doActivateDynamicBreakPoints(currentLocation); + context.getEventManager().deleteAllBreakpoints(); + configureBreakpointsForMethod(currentLocation); + context.setPrevLocation(currentLocation); } context.setPrevLocation(currentLocation); } @@ -243,7 +245,11 @@ void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode, boolea // temporary breakpoint on the location of its invocation. This is supposed to handle the situations where // the user wants to step over on an exit point of the current function. if (mode == DynamicBreakpointMode.CALLER && validFrames.size() > 1) { - doActivateDynamicBreakPoints(validFrames.get(1).getJStackFrame().location()); + context.getEventManager().deleteAllBreakpoints(); + for (int frameIndex = 1; frameIndex < validFrames.size(); frameIndex++) { + configureBreakpointsForMethod(validFrames.get(frameIndex).getJStackFrame().location()); + } + context.setPrevLocation(validFrames.get(0).getJStackFrame().location()); } } catch (JdiProxyException e) { LOGGER.error(e.getMessage()); @@ -254,12 +260,6 @@ void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode, boolea } } - private void doActivateDynamicBreakPoints(Location location) { - context.getEventManager().deleteAllBreakpoints(); - configureBreakpointsForMethod(location); - context.setPrevLocation(location); - } - /** * Checks whether the given two locations are within the same source file. * diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 0d84b61b4a4c..552d5d9577c0 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -38,6 +38,7 @@ import org.ballerinalang.debugadapter.config.ClientConfigHolder; import org.ballerinalang.debugadapter.config.ClientConfigurationException; import org.ballerinalang.debugadapter.config.ClientLaunchConfigHolder; +import org.ballerinalang.debugadapter.evaluation.BExpressionValue; import org.ballerinalang.debugadapter.evaluation.DebugExpressionEvaluator; import org.ballerinalang.debugadapter.evaluation.EvaluationException; import org.ballerinalang.debugadapter.evaluation.EvaluationExceptionKind; @@ -119,6 +120,9 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -142,19 +146,20 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private ClientConfigHolder clientConfigHolder; private DebugExecutionManager executionManager; private JDIEventProcessor eventProcessor; - private DebugExpressionEvaluator evaluator; private final ExecutionContext context; private ThreadReferenceProxyImpl activeThread; private SuspendedContext suspendedContext; private DebugOutputLogger outputLogger; + private DebugExpressionEvaluator evaluator; private final AtomicInteger nextVarReference = new AtomicInteger(); - private final Map stackFramesMap = new ConcurrentHashMap<>(); - private final Map loadedThreadFrames = new ConcurrentHashMap<>(); - private final Map variableToStackFrameMap = new ConcurrentHashMap<>(); - private final Map scopeIdToFrameIdMap = new ConcurrentHashMap<>(); - + private final Map stackFrames = new HashMap<>(); + private final Map threadStackTraces = new HashMap<>(); + private final Map scopeIdToFrameIds = new HashMap<>(); + private final Map variableToStackFrames = new ConcurrentHashMap<>(); private final Map loadedCompoundVariables = new ConcurrentHashMap<>(); + // Multi-threading is avoided here due to observed intermittent VM crashes, likely related to JDI limitations. + private final ExecutorService variableExecutor = Executors.newSingleThreadExecutor(); private static final Logger LOGGER = LoggerFactory.getLogger(JBallerinaDebugServer.class); private static final String SCOPE_NAME_LOCAL = "Local"; @@ -204,12 +209,12 @@ public CompletableFuture initialize(InitializeRequestArguments arg capabilities.setSupportsExceptionFilterOptions(false); capabilities.setSupportsExceptionInfoRequest(false); - context.setClient(client); context.setSupportsRunInTerminalRequest(args.getSupportsRunInTerminalRequest() != null && args.getSupportsRunInTerminalRequest()); eventProcessor = new JDIEventProcessor(context); + outputLogger = new DebugOutputLogger(client); + context.setClient(client); client.initialized(); - this.outputLogger = new DebugOutputLogger(client); return CompletableFuture.completedFuture(capabilities); } @@ -222,7 +227,7 @@ public CompletableFuture setBreakpoints(SetBreakpointsAr } BalBreakpoint[] balBreakpoints = Arrays.stream(args.getBreakpoints()) - .map((SourceBreakpoint sourceBreakpoint) -> toBreakpoint(sourceBreakpoint, args.getSource())) + .map((SourceBreakpoint sourceBreakpoint) -> toBalBreakpoint(sourceBreakpoint, args.getSource())) .toArray(BalBreakpoint[]::new); LinkedHashMap breakpointsMap = new LinkedHashMap<>(); @@ -332,7 +337,7 @@ public CompletableFuture threads() { @Override public CompletableFuture pause(PauseArguments args) { VirtualMachineProxyImpl debuggeeVM = context.getDebuggeeVM(); - // Checks if the program VM is a read-only VM. (If a method which would modify the state of the VM is called + // Checks if the program VM is a read-only VM. If a method which modified the state of the VM is called // on a read-only VM, a `VMCannotBeModifiedException` will be thrown. if (!debuggeeVM.canBeModified()) { getOutputLogger().sendConsoleOutput("Failed to suspend the remote VM due to: pause requests are not " + @@ -350,15 +355,15 @@ public CompletableFuture stackTrace(StackTraceArguments args StackTraceResponse stackTraceResponse = new StackTraceResponse(); try { activeThread = getAllThreads().get(args.getThreadId()); - if (loadedThreadFrames.containsKey(activeThread.uniqueID())) { - stackTraceResponse.setStackFrames(loadedThreadFrames.get(activeThread.uniqueID())); + if (threadStackTraces.containsKey(activeThread.uniqueID())) { + stackTraceResponse.setStackFrames(threadStackTraces.get(activeThread.uniqueID())); } else { StackFrame[] validFrames = activeThread.frames().stream() .map(this::toDapStackFrame) .filter(JBallerinaDebugServer::isValidFrame) .toArray(StackFrame[]::new); stackTraceResponse.setStackFrames(validFrames); - loadedThreadFrames.put(activeThread.uniqueID(), validFrames); + threadStackTraces.put(activeThread.uniqueID(), validFrames); } return CompletableFuture.completedFuture(stackTraceResponse); } catch (Exception e) { @@ -373,12 +378,12 @@ public CompletableFuture scopes(ScopesArguments args) { // Creates local variable scope. Scope localScope = new Scope(); localScope.setName(SCOPE_NAME_LOCAL); - scopeIdToFrameIdMap.put(nextVarReference.get(), args.getFrameId()); + scopeIdToFrameIds.put(nextVarReference.get(), args.getFrameId()); localScope.setVariablesReference(nextVarReference.getAndIncrement()); // Creates global variable scope. Scope globalScope = new Scope(); globalScope.setName(SCOPE_NAME_GLOBAL); - scopeIdToFrameIdMap.put(nextVarReference.get(), -args.getFrameId()); + scopeIdToFrameIds.put(nextVarReference.get(), -args.getFrameId()); globalScope.setVariablesReference(nextVarReference.getAndIncrement()); Scope[] scopes = {localScope, globalScope}; @@ -389,31 +394,27 @@ public CompletableFuture scopes(ScopesArguments args) { @Override public CompletableFuture variables(VariablesArguments args) { + // 1. If frameId is NULL, returns child variables. + // 2. If frameId < 0, returns global variables. + // 3. If frameId >= 0, returns local variables. VariablesResponse variablesResponse = new VariablesResponse(); try { - // 1. If frameId < 0, returns global variables. - // 2. If frameId >= 0, returns local variables. - // 3. If frameId is NULL, returns child variables. - Integer frameId = scopeIdToFrameIdMap.get(args.getVariablesReference()); - if (frameId != null && frameId < 0) { - StackFrameProxyImpl stackFrame = stackFramesMap.get(-frameId); - if (stackFrame == null) { - variablesResponse.setVariables(new Variable[0]); - return CompletableFuture.completedFuture(variablesResponse); - } + Integer frameId = scopeIdToFrameIds.get(args.getVariablesReference()); + if (frameId == null) { + variablesResponse.setVariables(computeChildVariables(args)); + return CompletableFuture.completedFuture(variablesResponse); + } - suspendedContext = new SuspendedContext(context, activeThread, stackFrame); - variablesResponse.setVariables(computeGlobalVariables(suspendedContext, args.getVariablesReference())); - } else if (frameId != null) { - StackFrameProxyImpl stackFrame = stackFramesMap.get(frameId); - if (stackFrame == null) { - variablesResponse.setVariables(new Variable[0]); - return CompletableFuture.completedFuture(variablesResponse); - } - suspendedContext = new SuspendedContext(context, activeThread, stackFrame); - variablesResponse.setVariables(computeStackFrameVariables(args)); + StackFrameProxyImpl stackFrame = stackFrames.get(Math.abs(frameId)); + if (stackFrame == null) { + variablesResponse.setVariables(new Variable[0]); + return CompletableFuture.completedFuture(variablesResponse); + } + suspendedContext = new SuspendedContext(context, activeThread, stackFrame); + if (frameId < 0) { + variablesResponse.setVariables(computeGlobalScopeVariables(args)); } else { - variablesResponse.setVariables(computeChildVariables(args)); + variablesResponse.setVariables(computeLocalScopeVariables(args)); } return CompletableFuture.completedFuture(variablesResponse); } catch (Exception e) { @@ -465,20 +466,20 @@ public CompletableFuture setExceptionBreakpoints(SetExceptionBreakpointsAr @Override public CompletableFuture evaluate(EvaluateArguments args) { - EvaluateResponse response = new EvaluateResponse(); // If the execution manager is not active, it implies that the debug server is still not connected to the // remote VM and therefore the request should be rejected immediately. if (executionManager == null || !executionManager.isActive()) { context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "Debug server is not " + "connected to any program VM."); - return CompletableFuture.completedFuture(response); + return CompletableFuture.completedFuture(new EvaluateResponse()); } // If the frame ID is missing in the client args, it implies that remote program is still running and therefore // the request should be rejected immediately. if (args.getFrameId() == null) { context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "Remote VM is not suspended " + "and still in running state."); - return CompletableFuture.completedFuture(response); + + return CompletableFuture.completedFuture(new EvaluateResponse()); } // Evaluate arguments context becomes `variables` when we do a `Copy Value` from VS Code, and @@ -486,40 +487,26 @@ public CompletableFuture evaluate(EvaluateArguments args) { // If evaluate arguments context is equal to `variables`, then respond with expression as it is without // evaluation process. if (args.getContext() != null && args.getContext().equals(EVAL_ARGS_CONTEXT_VARIABLES)) { + EvaluateResponse response = new EvaluateResponse(); response.setResult(args.getExpression()); return CompletableFuture.completedFuture(response); } + try { - StackFrameProxyImpl frame = stackFramesMap.get(args.getFrameId()); + StackFrameProxyImpl frame = stackFrames.get(args.getFrameId()); SuspendedContext suspendedContext = new SuspendedContext(context, activeThread, frame); EvaluationContext evaluationContext = new EvaluationContext(suspendedContext); evaluator = Objects.requireNonNullElse(evaluator, new DebugExpressionEvaluator(evaluationContext)); evaluator.setExpression(args.getExpression()); - BVariable variable = evaluator.evaluate().getBVariable(); - - if (variable == null) { - return CompletableFuture.completedFuture(response); - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable bCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, bCompoundVariable); - updateVariableToStackFrameMap(args.getFrameId(), variableReference); - } - Variable dapVariable = variable.getDapVariable(); - response.setResult(dapVariable.getValue()); - response.setType(dapVariable.getType()); - response.setIndexedVariables(dapVariable.getIndexedVariables()); - response.setNamedVariables(dapVariable.getNamedVariables()); - response.setVariablesReference(dapVariable.getVariablesReference()); - return CompletableFuture.completedFuture(response); + BExpressionValue evaluateResult = evaluator.evaluate(); + EvaluateResponse evaluateResponse = constructEvaluateResponse(args, evaluateResult.getBVariable()); + return CompletableFuture.completedFuture(evaluateResponse); } catch (EvaluationException e) { context.getOutputLogger().sendErrorOutput(e.getMessage()); - return CompletableFuture.completedFuture(response); + return CompletableFuture.completedFuture(new EvaluateResponse()); } catch (Exception e) { context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "internal error"); - return CompletableFuture.completedFuture(response); + return CompletableFuture.completedFuture(new EvaluateResponse()); } } @@ -579,20 +566,6 @@ public CompletableFuture completions(CompletionsArguments a }); } - private BalBreakpoint toBreakpoint(SourceBreakpoint sourceBreakpoint, Source source) { - BalBreakpoint breakpoint = new BalBreakpoint(source, sourceBreakpoint.getLine()); - breakpoint.setCondition(sourceBreakpoint.getCondition()); - breakpoint.setLogMessage(sourceBreakpoint.getLogMessage()); - return breakpoint; - } - - Thread toDapThread(ThreadReferenceProxyImpl threadReference) { - Thread thread = new Thread(); - thread.setId((int) threadReference.uniqueID()); - thread.setName(threadReference.name()); - return thread; - } - @Override public CompletableFuture disconnect(DisconnectArguments args) { context.setTerminateRequestReceived(true); @@ -676,7 +649,7 @@ void terminateDebugServer(boolean terminateDebuggee, boolean logsEnabled) { if (context.getLaunchedProcess().isPresent() && context.getLaunchedProcess().get().isAlive()) { killProcessWithDescendants(context.getLaunchedProcess().get()); } - // Destroys remote VM process, if `terminteDebuggee' flag is set. + // Destroys remote VM process, if `terminateDebuggee' flag is set. if (terminateDebuggee && context.getDebuggeeVM() != null) { int exitCode = 0; if (context.getDebuggeeVM().process() != null) { @@ -743,26 +716,42 @@ public void connect(IDebugProtocolClient client) { } private synchronized void updateVariableToStackFrameMap(int parent, int child) { - if (!variableToStackFrameMap.containsKey(parent)) { - variableToStackFrameMap.put(child, parent); + if (!variableToStackFrames.containsKey(parent)) { + variableToStackFrames.put(child, parent); return; } Integer parentRef; do { - parentRef = variableToStackFrameMap.get(parent); - } while (variableToStackFrameMap.containsKey(parentRef)); - variableToStackFrameMap.put(child, parentRef); + parentRef = variableToStackFrames.get(parent); + } while (variableToStackFrames.containsKey(parentRef)); + variableToStackFrames.put(child, parentRef); + } + + /** + * Converts a JDI thread reference to a DAP thread instance. + * + * @param threadReference JDI thread reference + * @return the corresponding DAP thread instance + */ + Thread toDapThread(ThreadReferenceProxyImpl threadReference) { + Thread thread = new Thread(); + thread.setId((int) threadReference.uniqueID()); + thread.setName(threadReference.name()); + return thread; } - public StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) { + /** + * Coverts a JDI stack frame instance to a DAP stack frame instance. + */ + private StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) { try { if (!isBalStackFrame(stackFrameProxy.getStackFrame())) { return null; } int referenceId = nextVarReference.getAndIncrement(); - stackFramesMap.put(referenceId, stackFrameProxy); + stackFrames.put(referenceId, stackFrameProxy); BallerinaStackFrame balStackFrame = new BallerinaStackFrame(context, referenceId, stackFrameProxy); return balStackFrame.getAsDAPStackFrame().orElse(null); } catch (JdiProxyException e) { @@ -770,175 +759,11 @@ public StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) { } } - private Variable[] computeGlobalVariables(SuspendedContext context, int stackFrameReference) { - String classQName = PackageUtils.getQualifiedClassName(context, INIT_CLASS_NAME); - List cls = context.getAttachedVm().classesByName(classQName); - if (cls.size() != 1) { - return new Variable[0]; - } - ArrayList globalVars = new ArrayList<>(); - ReferenceType initClassReference = cls.get(0); - for (Field field : initClassReference.allFields()) { - String fieldName = Utils.decodeIdentifier(field.name()); - if (!field.isPublic() || !field.isStatic() || fieldName.startsWith(GENERATED_VAR_PREFIX)) { - continue; - } - Value fieldValue = initClassReference.getValue(field); - BVariable variable = VariableFactory.getVariable(context, fieldName, fieldValue); - if (variable == null) { - continue; - } - if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable bCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, bCompoundVariable); - updateVariableToStackFrameMap(stackFrameReference, variableReference); - } - globalVars.add(variable.getDapVariable()); - } - return globalVars.toArray(new Variable[0]); - } - - private Variable[] computeStackFrameVariables(VariablesArguments args) throws Exception { - StackFrameProxyImpl stackFrame = suspendedContext.getFrame(); - List variables = new ArrayList<>(); - List localVariableProxies = stackFrame.visibleVariables(); - for (LocalVariableProxyImpl var : localVariableProxies) { - String name = var.name(); - Value value = stackFrame.getValue(var); - // Since the ballerina variables used inside lambda functions are converted into maps during the - // ballerina runtime code generation, such local variables needs to be extracted in a separate manner. - if (VariableUtils.isLambdaParamMap(var)) { - variables.addAll(fetchLocalVariablesFromMap(args, stackFrame, var)); - } else { - Variable dapVariable = getAsDapVariable(name, value, args.getVariablesReference()); - if (dapVariable != null) { - variables.add(dapVariable); - } - } - } - return variables.toArray(new Variable[0]); - } - - /** - * Returns the list of local variables extracted from the given variable map, which contains local variables used - * within lambda functions. - * - * @param args variable args - * @param stackFrame parent stack frame instance - * @param lambdaParamMapVar map variable instance - * @return list of local variables extracted from the given variable map - */ - private List fetchLocalVariablesFromMap(VariablesArguments args, StackFrameProxyImpl stackFrame, - LocalVariableProxyImpl lambdaParamMapVar) { - try { - Value value = stackFrame.getValue(lambdaParamMapVar); - Variable dapVariable = getAsDapVariable("lambdaArgMap", value, args.getVariablesReference()); - if (dapVariable == null || !dapVariable.getType().equals(BVariableType.MAP.getString())) { - return new ArrayList<>(); - } - VariablesArguments childVarRequestArgs = new VariablesArguments(); - childVarRequestArgs.setVariablesReference(dapVariable.getVariablesReference()); - Variable[] childVariables = computeChildVariables(childVarRequestArgs); - return Arrays.asList(childVariables); - } catch (Exception e) { - return new ArrayList<>(); - } - } - - /** - * Coverts a given ballerina runtime value instance into a debugger adapter protocol supported variable instance. - * - * @param name variable name - * @param value runtime value of the variable - * @param stackFrameRef reference ID of the parent stack frame - */ - private Variable getAsDapVariable(String name, Value value, Integer stackFrameRef) { - BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); - if (variable == null) { - return null; - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable bCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, bCompoundVariable); - updateVariableToStackFrameMap(stackFrameRef, variableReference); - } - return variable.getDapVariable(); - } - - private Variable[] computeChildVariables(VariablesArguments args) { - BCompoundVariable parentVar = loadedCompoundVariables.get(args.getVariablesReference()); - Integer stackFrameId = variableToStackFrameMap.get(args.getVariablesReference()); - if (stackFrameId == null) { - return new Variable[0]; - } - - if (parentVar instanceof IndexedCompoundVariable indexedCompoundVariable) { - // Handles indexed variables. - int startIndex = (args.getStart() != null) ? args.getStart() : 0; - int count = (args.getCount() != null) ? args.getCount() : 0; - - Either, List> childVars = indexedCompoundVariable - .getIndexedChildVariables(startIndex, count); - if (childVars.isLeft()) { - // Handles map-type indexed variables. - return createVariableArrayFrom(args, childVars.getLeft()); - } else if (childVars.isRight()) { - // Handles list-type indexed variables. - return createVariableArrayFrom(args, childVars.getRight()); - } - return new Variable[0]; - } else if (parentVar instanceof NamedCompoundVariable namedCompoundVariable) { - // Handles named variables. - Map childVars = namedCompoundVariable.getNamedChildVariables(); - return createVariableArrayFrom(args, childVars); - } - - return new Variable[0]; - } - - private Variable[] createVariableArrayFrom(VariablesArguments args, Map varMap) { - return varMap.entrySet().stream().map(entry -> { - String name = entry.getKey(); - Value value = entry.getValue(); - BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); - if (variable == null) { - return null; - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable bCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, bCompoundVariable); - updateVariableToStackFrameMap(args.getVariablesReference(), variableReference); - } - return variable.getDapVariable(); - }).filter(Objects::nonNull).toArray(Variable[]::new); - } - - private Variable[] createVariableArrayFrom(VariablesArguments args, List varMap) { - int startIndex = (args.getStart() != null) ? args.getStart() : 0; - AtomicInteger index = new AtomicInteger(startIndex); - - return varMap.stream().map(value -> { - String name = String.format("[%d]", index.getAndIncrement()); - BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); - if (variable == null) { - return null; - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable bCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, bCompoundVariable); - updateVariableToStackFrameMap(args.getVariablesReference(), variableReference); - } - return variable.getDapVariable(); - }).filter(Objects::nonNull).toArray(Variable[]::new); + private BalBreakpoint toBalBreakpoint(SourceBreakpoint sourceBreakpoint, Source source) { + BalBreakpoint breakpoint = new BalBreakpoint(source, sourceBreakpoint.getLine()); + breakpoint.setCondition(sourceBreakpoint.getCondition()); + breakpoint.setLogMessage(sourceBreakpoint.getLogMessage()); + return breakpoint; } /** @@ -1021,25 +846,13 @@ static boolean isBalStackFrame(com.sun.jdi.StackFrame frame) { } /** - * Validates a given ballerina stack frame for for its source information. + * Validates a given ballerina stack frame for its source information. * * @param stackFrame ballerina stack frame * @return true if its a valid ballerina frame */ static boolean isValidFrame(StackFrame stackFrame) { - return stackFrame != null && stackFrame.getSource() != null && stackFrame.getLine() > 0 - && !isCompilerGeneratedFrame(stackFrame); - } - - /** - * Validates whether the provided stack frame is a compiler generated one during the codegen phase. - * - * @param stackFrame stack frame instance - * @return true if the provided stack frame is a compiler generated one during the codegen phase - */ - private static boolean isCompilerGeneratedFrame(StackFrame stackFrame) { - String frameName = stackFrame.getName(); - return frameName != null && frameName.startsWith("$") && frameName.endsWith("$"); + return stackFrame != null && stackFrame.getSource() != null && stackFrame.getLine() > 0; } /** @@ -1095,7 +908,7 @@ private void startListeningToProgramOutput() { LOGGER.error(e.getMessage()); outputLogger.sendDebugServerOutput(String.format("Failed to attach to the target VM, address: '%s:%s'.", host, portName)); - terminateDebugServer(context.getDebuggeeVM() != null, false); + terminateDebugServer(context.getDebuggeeVM() != null, true); } }); } @@ -1114,7 +927,7 @@ private void attachToRemoteVM(String hostName, int portName) throws IOException, EventRequestManager erm = context.getEventManager(); ClassPrepareRequest classPrepareRequest = erm.createClassPrepareRequest(); classPrepareRequest.enable(); - eventProcessor.startListening(); + eventProcessor.listenAsync(); } /** @@ -1139,6 +952,239 @@ private boolean isNoDebugMode() { return confHolder instanceof ClientLaunchConfigHolder launchConfigHolder && launchConfigHolder.isNoDebugMode(); } + private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) { + int stackFrameReference = requestArgs.getVariablesReference(); + String classQName = PackageUtils.getQualifiedClassName(suspendedContext, INIT_CLASS_NAME); + List cls = suspendedContext.getAttachedVm().classesByName(classQName); + if (cls.size() != 1) { + return new Variable[0]; + } + List> scheduledVariables = new ArrayList<>(); + ReferenceType initClassReference = cls.get(0); + for (Field field : initClassReference.allFields()) { + String fieldName = Utils.decodeIdentifier(field.name()); + if (!field.isPublic() || !field.isStatic() || fieldName.startsWith(GENERATED_VAR_PREFIX)) { + continue; + } + Value fieldValue = initClassReference.getValue(field); + scheduledVariables.add(computeVariableAsync(fieldName, fieldValue, stackFrameReference)); + } + + return scheduledVariables.stream() + .map(varFuture -> { + try { + return varFuture.get(); + } catch (Exception ignored) { + // Todo - Refactor after implementing debug/trace logger + LOGGER.error("Failed to load some debug variables due to runtime exceptions."); + return null; + } + }) + .filter(Objects::nonNull) + .toArray(Variable[]::new); + } + + private Variable[] computeLocalScopeVariables(VariablesArguments args) throws Exception { + StackFrameProxyImpl stackFrame = suspendedContext.getFrame(); + List> scheduledVariables = new ArrayList<>(); + List> scheduledLambdaMapVariables = new ArrayList<>(); + List localVariableProxies = stackFrame.visibleVariables(); + for (LocalVariableProxyImpl var : localVariableProxies) { + String name = var.name(); + Value value = stackFrame.getValue(var); + // Since the ballerina variables used inside lambda functions are converted into maps during the + // ballerina runtime code generation, such local variables needs to be extracted in a separate manner. + if (VariableUtils.isLambdaParamMap(var)) { + scheduledLambdaMapVariables.add(fetchLocalVariablesFromMap(args, stackFrame, var)); + } else { + CompletableFuture dapVar = computeVariableAsync(name, value, args.getVariablesReference()); + scheduledVariables.add(dapVar); + } + } + + List resolvedVariables = new ArrayList<>(); + scheduledVariables.forEach(varFuture -> { + try { + Variable variable = varFuture.get(); + if (variable != null) { + resolvedVariables.add(variable); + } + } catch (InterruptedException | ExecutionException e) { + // Todo - Refactor after implementing debug/trace logger + LOGGER.error("Failed to load some debug variables due to runtime exceptions."); + } + }); + + scheduledLambdaMapVariables.forEach(varFuture -> { + try { + Variable[] variables = varFuture.get(); + if (variables != null) { + resolvedVariables.addAll(Arrays.asList(variables)); + } + } catch (InterruptedException | ExecutionException e) { + // Todo - Refactor after implementing debug/trace logger + LOGGER.error("Failed to load some debug variables due to runtime exceptions."); + } + }); + return resolvedVariables.toArray(new Variable[0]); + } + + /** + * Returns the list of local variables extracted from the given variable map, which contains local variables used + * within lambda functions. + * + * @param args variable args + * @param stackFrame parent stack frame instance + * @param lambdaParamMapVar map variable instance + * @return list of local variables extracted from the given variable map + */ + private CompletableFuture fetchLocalVariablesFromMap(VariablesArguments args, StackFrameProxyImpl + stackFrame, LocalVariableProxyImpl lambdaParamMapVar) { + try { + Value value = stackFrame.getValue(lambdaParamMapVar); + CompletableFuture scheduledVariable = computeVariableAsync("lambdaArgMap", value, + args.getVariablesReference()); + Variable dapVariable = scheduledVariable.get(); + if (dapVariable == null || !dapVariable.getType().equals(BVariableType.MAP.getString())) { + return new CompletableFuture<>(); + } + VariablesArguments childVarRequestArgs = new VariablesArguments(); + childVarRequestArgs.setVariablesReference(dapVariable.getVariablesReference()); + return computeChildVariablesAsync(childVarRequestArgs); + } catch (Exception e) { + return new CompletableFuture<>(); + } + } + + /** + * Asynchronously computes a debugger adapter protocol supported variable instance from Coverts a given ballerina + * runtime value instance. + * + * @param name variable name + * @param value runtime value of the variable + * @param stackFrameRef reference ID of the parent stack frame + */ + private CompletableFuture computeVariableAsync(String name, Value value, Integer stackFrameRef) { + return CompletableFuture.supplyAsync(() -> { + BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); + if (variable == null) { + return null; + } + if (variable instanceof BSimpleVariable) { + variable.getDapVariable().setVariablesReference(0); + } else if (variable instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + variable.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); + updateVariableToStackFrameMap(stackFrameRef, variableReference); + } + return variable.getDapVariable(); + }, variableExecutor); + } + + private CompletableFuture computeChildVariablesAsync(VariablesArguments args) { + return CompletableFuture.supplyAsync(() -> computeChildVariables(args), variableExecutor); + } + + private Variable[] computeChildVariables(VariablesArguments args) { + // Todo: Add support for parallel child variable computations. + BCompoundVariable parentVar = loadedCompoundVariables.get(args.getVariablesReference()); + Integer stackFrameId = variableToStackFrames.get(args.getVariablesReference()); + if (stackFrameId == null) { + return new Variable[0]; + } + if (parentVar instanceof IndexedCompoundVariable) { + // Handles indexed variables. + int startIndex = (args.getStart() != null) ? args.getStart() : 0; + int count = (args.getCount() != null) ? args.getCount() : 0; + + Either, List> childVars = ((IndexedCompoundVariable) parentVar) + .getIndexedChildVariables(startIndex, count); + if (childVars.isLeft()) { + // Handles map-type indexed variables. + return createVariableArrayFrom(args, childVars.getLeft()); + } else if (childVars.isRight()) { + // Handles list-type indexed variables. + return createVariableArrayFrom(args, childVars.getRight()); + } + return new Variable[0]; + } else if (parentVar instanceof NamedCompoundVariable) { + // Handles named variables. + Map childVars = ((NamedCompoundVariable) parentVar).getNamedChildVariables(); + return createVariableArrayFrom(args, childVars); + } + + return new Variable[0]; + } + + private Variable[] createVariableArrayFrom(VariablesArguments args, Map varMap) { + return varMap.entrySet().stream().map(entry -> { + String name = entry.getKey(); + Value value = entry.getValue(); + BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); + if (variable == null) { + return null; + } else if (variable instanceof BSimpleVariable) { + variable.getDapVariable().setVariablesReference(0); + } else if (variable instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + variable.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); + updateVariableToStackFrameMap(args.getVariablesReference(), variableReference); + } + return variable.getDapVariable(); + }).filter(Objects::nonNull).toArray(Variable[]::new); + } + + private Variable[] createVariableArrayFrom(VariablesArguments args, List varMap) { + int startIndex = (args.getStart() != null) ? args.getStart() : 0; + AtomicInteger index = new AtomicInteger(startIndex); + + return varMap.stream().map(value -> { + String name = String.format("[%d]", index.getAndIncrement()); + BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); + if (variable == null) { + return null; + } else if (variable instanceof BSimpleVariable) { + variable.getDapVariable().setVariablesReference(0); + } else if (variable instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + variable.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); + updateVariableToStackFrameMap(args.getVariablesReference(), variableReference); + } + return variable.getDapVariable(); + }).filter(Objects::nonNull).toArray(Variable[]::new); + } + + /** + * Creates a {@link EvaluateResponse} from the given evaluation result variable. + * + * @param args evaluation arguments. + * @param evaluationResult evaluation result variable + */ + private EvaluateResponse constructEvaluateResponse(EvaluateArguments args, BVariable evaluationResult) { + EvaluateResponse response = new EvaluateResponse(); + if (evaluationResult == null) { + return response; + } else if (evaluationResult instanceof BSimpleVariable) { + evaluationResult.getDapVariable().setVariablesReference(0); + } else if (evaluationResult instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + evaluationResult.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) evaluationResult); + updateVariableToStackFrameMap(args.getFrameId(), variableReference); + } + + Variable dapVariable = evaluationResult.getDapVariable(); + response.setResult(dapVariable.getValue()); + response.setType(dapVariable.getType()); + response.setIndexedVariables(dapVariable.getIndexedVariables()); + response.setNamedVariables(dapVariable.getNamedVariables()); + response.setVariablesReference(dapVariable.getVariablesReference()); + return response; + } + /** * Clears state information. */ @@ -1146,11 +1192,11 @@ private void clearState() { suspendedContext = null; evaluator = null; activeThread = null; - stackFramesMap.clear(); + stackFrames.clear(); loadedCompoundVariables.clear(); - variableToStackFrameMap.clear(); - scopeIdToFrameIdMap.clear(); - loadedThreadFrames.clear(); + variableToStackFrames.clear(); + scopeIdToFrameIds.clear(); + threadStackTraces.clear(); nextVarReference.set(1); } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java index b538daf2e89b..e1e32101cbc8 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java @@ -74,7 +74,7 @@ List getStepRequests() { /** * Asynchronously listens and processes the incoming JDI events. */ - void startListening() { + void listenAsync() { CompletableFuture.runAsync(() -> { isRemoteVmAttached = true; while (isRemoteVmAttached) { diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeInstanceMethod.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeInstanceMethod.java index c1ee806346ab..2ec53b50a3fa 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeInstanceMethod.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeInstanceMethod.java @@ -44,7 +44,7 @@ public RuntimeInstanceMethod(SuspendedContext context, Value objectRef, Method m } @Override - protected Value invoke() throws EvaluationException { + protected synchronized Value invoke() throws EvaluationException { try { if (!(objectValueRef instanceof ObjectReference objectReference)) { throw createEvaluationException(FUNCTION_EXECUTION_ERROR, methodRef.name()); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeStaticMethod.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeStaticMethod.java index 54fe5f10f6b5..50044513fcef 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeStaticMethod.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeStaticMethod.java @@ -45,7 +45,7 @@ public RuntimeStaticMethod(SuspendedContext context, ReferenceType classRef, Met } @Override - protected Value invoke() throws EvaluationException { + protected synchronized Value invoke() throws EvaluationException { try { if (!(classRef instanceof ClassType classType)) { throw createEvaluationException(FUNCTION_EXECUTION_ERROR, methodRef.name()); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java index ca0a141091d0..79181b0655ce 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java @@ -58,6 +58,7 @@ public class StackFrameProxyImpl extends JdiProxy implements StackFrameProxy { private ClassLoaderReference myClassLoader; private ThreeState myIsObsolete = ThreeState.UNSURE; private Map myAllValues; + private static final int RETRY_COUNT = 20; public StackFrameProxyImpl(ThreadReferenceProxyImpl threadProxy, StackFrame frame, int fromBottomIndex) { super(threadProxy.getVirtualMachine()); @@ -72,7 +73,7 @@ public boolean isObsolete() throws JdiProxyException { return myIsObsolete.toBoolean(); } InvalidStackFrameException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { Method method = getMethod(location()); boolean isObsolete = @@ -128,17 +129,31 @@ protected void clearCaches() { @Override public StackFrame getStackFrame() throws JdiProxyException { checkValid(); - if (myStackFrame == null) { + if (myStackFrame != null) { + return myStackFrame; + } + IncompatibleThreadStateException error = null; + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { final ThreadReference threadRef = myThreadProxy.getThreadReference(); myStackFrame = threadRef.frame(getFrameIndex()); - } catch (IndexOutOfBoundsException | IncompatibleThreadStateException e) { + return myStackFrame; + } catch (IncompatibleThreadStateException e) { + error = e; + clearCaches(); + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + } + } catch (IndexOutOfBoundsException e) { throw new JdiProxyException(e.getMessage(), e); } catch (ObjectCollectedException ignored) { throw new JdiProxyException("Thread has been collected"); + } catch (Exception e) { + throw new JdiProxyException("Unknown error when trying to access thread reference", e); } } - return myStackFrame; + throw new JdiProxyException("Thread has become invalid", error); } @Override @@ -162,7 +177,7 @@ public VirtualMachineProxyImpl getVirtualMachine() { @Override public Location location() throws JdiProxyException { InvalidStackFrameException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { return getStackFrame().location(); } catch (InvalidStackFrameException e) { @@ -190,7 +205,7 @@ public String toString() { public ObjectReference thisObject() throws JdiProxyException { checkValid(); try { - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { if (myThisReference == null) { myThisReference = getStackFrame().thisObject(); @@ -221,7 +236,7 @@ public ObjectReference thisObject() throws JdiProxyException { public List visibleVariables() throws JdiProxyException { RuntimeException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { final List list = getStackFrame().visibleVariables(); final List locals = new ArrayList<>(list.size()); @@ -252,7 +267,7 @@ public Value visibleValueByName(String name) throws JdiProxyException { protected LocalVariable visibleVariableByNameInt(String name) throws JdiProxyException { InvalidStackFrameException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { try { return getStackFrame().visibleVariableByName(name); @@ -269,7 +284,7 @@ protected LocalVariable visibleVariableByNameInt(String name) throws JdiProxyExc public Value getValue(LocalVariableProxyImpl localVariable) throws JdiProxyException { Exception error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { Map values = getAllValues(); LocalVariable variable = localVariable.getVariable(); @@ -333,7 +348,7 @@ private Map getAllValues() throws JdiProxyException { public void setValue(LocalVariableProxyImpl localVariable, Value value) throws JdiProxyException, ClassNotLoadedException, InvalidTypeException { InvalidStackFrameException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { final LocalVariable variable = localVariable.getVariable(); final StackFrame stackFrame = getStackFrame(); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java index 879dd77df7a3..d953c1c5a8fd 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java @@ -173,20 +173,54 @@ public static String getStringFrom(Value stringValue) { * @return result of the method invocation as a string. */ public static String getStringValue(SuspendedContext context, Value jvmObject) { + try { + Value result = invokeRemoteVMMethod(context, jvmObject, METHOD_STR_VALUE, Collections.singletonList(null)); + return getStringFrom(result); + } catch (DebugVariableException e) { + return UNKNOWN_VALUE; + } + } + + public static Value invokeRemoteVMMethod(SuspendedContext context, Value jvmObject, String methodName, + List arguments) throws DebugVariableException { + if (!(jvmObject instanceof ObjectReference)) { + throw new DebugVariableException("Failed to invoke remote VM method."); + } + Optional method = VariableUtils.getMethod(jvmObject, methodName); + if (method.isEmpty()) { + throw new DebugVariableException("Failed to invoke remote VM method."); + } + + return invokeRemoteVMMethod(context, jvmObject, method.get(), arguments); + } + + public static Value invokeRemoteVMMethod(SuspendedContext context, Value jvmObject, Method method, + List arguments) throws DebugVariableException { try { if (!(jvmObject instanceof ObjectReference)) { - return UNKNOWN_VALUE; + throw new DebugVariableException("Failed to invoke remote VM method."); } - Optional method = VariableUtils.getMethod(jvmObject, METHOD_STR_VALUE); - if (method.isPresent()) { - Value stringValue = ((ObjectReference) jvmObject).invokeMethod(context.getOwningThread() - .getThreadReference(), method.get(), Collections.singletonList(null), - ObjectReference.INVOKE_SINGLE_THREADED); - return VariableUtils.getStringFrom(stringValue); + + if (arguments == null) { + arguments = Collections.emptyList(); } - return UNKNOWN_VALUE; - } catch (Exception ignored) { - return UNKNOWN_VALUE; + // Since the remote VM's active thread will be accessed from multiple debugger threads, there's a chance + // for 'IncompatibleThreadStateException's. Therefore debugger might need to retry few times until the + // thread gets released. (current wait time => ~1000ms) + for (int attempt = 0; attempt < 100; attempt++) { + try { + return ((ObjectReference) jvmObject).invokeMethod(context.getOwningThread() + .getThreadReference(), method, arguments, ObjectReference.INVOKE_SINGLE_THREADED); + } catch (Exception e) { + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + } + } + } + throw new DebugVariableException("Failed to invoke remote VM method as the invocation thread is busy"); + } catch (Exception e) { + throw new DebugVariableException("Failed to invoke remote VM method due to an internal error"); } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BFuture.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BFuture.java index 91d1afc7f8ae..727588bd4183 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BFuture.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BFuture.java @@ -17,14 +17,12 @@ package org.ballerinalang.debugadapter.variable.types; import com.sun.jdi.Method; -import com.sun.jdi.ObjectReference; import com.sun.jdi.Value; import org.ballerinalang.debugadapter.SuspendedContext; import org.ballerinalang.debugadapter.variable.BVariableType; import org.ballerinalang.debugadapter.variable.NamedCompoundVariable; import org.ballerinalang.debugadapter.variable.VariableUtils; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; @@ -68,9 +66,8 @@ public Map computeChildVariables() { // Invokes "getLocalizedMessage()" method of the panic object. Optional method = VariableUtils.getMethod(panic.get(), METHOD_LOCALIZEDMESSAGE); if (method.isPresent()) { - Value stringValue = ((ObjectReference) panic.get()).invokeMethod(getContext().getOwningThread() - .getThreadReference(), method.get(), new ArrayList<>(), - ObjectReference.INVOKE_SINGLE_THREADED); + Value stringValue = VariableUtils.invokeRemoteVMMethod(context, panic.get(), + METHOD_LOCALIZEDMESSAGE, null); childVarMap.put(FIELD_PANIC, stringValue); } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BMap.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BMap.java index 9ecc8610fdbb..34653bc3ae5b 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BMap.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BMap.java @@ -19,7 +19,6 @@ import com.sun.jdi.ArrayReference; import com.sun.jdi.IntegerValue; import com.sun.jdi.Method; -import com.sun.jdi.ObjectReference; import com.sun.jdi.Value; import org.ballerinalang.debugadapter.SuspendedContext; import org.ballerinalang.debugadapter.variable.BVariableType; @@ -113,12 +112,12 @@ private Map getEntries(int startIndex, int count) { private Value getValueFor(Value key) { try { - Optional getMethod = VariableUtils.getMethod(jvmValue, METHOD_GET); - if (getMethod.isEmpty()) { + Optional getValueMethod = VariableUtils.getMethod(jvmValue, METHOD_GET); + if (getValueMethod.isEmpty()) { return null; } - return ((ObjectReference) jvmValue).invokeMethod(context.getOwningThread().getThreadReference(), - getMethod.get(), Collections.singletonList(key), ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.invokeRemoteVMMethod(context, jvmValue, getValueMethod.get(), + Collections.singletonList(key)); } catch (Exception ignored) { return null; } @@ -130,9 +129,7 @@ private void loadAllKeys() { if (entrySetMethod.isEmpty()) { return; } - Value keyArray = ((ObjectReference) jvmValue).invokeMethod(context.getOwningThread().getThreadReference(), - entrySetMethod.get(), Collections.emptyList(), ObjectReference.INVOKE_SINGLE_THREADED); - + Value keyArray = VariableUtils.invokeRemoteVMMethod(context, jvmValue, entrySetMethod.get(), null); loadedKeys = (ArrayReference) keyArray; loadedValues = new Value[getChildrenCount()]; } catch (Exception ignored) { diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BTable.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BTable.java index 2e02a49946b8..d9584117c43d 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BTable.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BTable.java @@ -144,8 +144,7 @@ private void populateTableSize() { tableSize = -1; return; } - Value size = ((ObjectReference) jvmValue).invokeMethod(getContext().getOwningThread().getThreadReference(), - method.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + Value size = VariableUtils.invokeRemoteVMMethod(context, jvmValue, method.get(), null); tableSize = ((IntegerValue) size).intValue(); } catch (Exception e) { this.tableSize = -1; @@ -186,8 +185,7 @@ private Value getIterator() throws Exception { if (getIteratorMethod.isEmpty()) { return null; } - return ((ObjectReference) jvmValue).invokeMethod(getContext().getOwningThread().getThreadReference(), - getIteratorMethod.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.invokeRemoteVMMethod(context, jvmValue, getIteratorMethod.get(), null); } private boolean hasNext(Value iterator) throws Exception { @@ -195,8 +193,7 @@ private boolean hasNext(Value iterator) throws Exception { if (hasNextMethod.isEmpty()) { return false; } - Value hasNext = ((ObjectReference) iterator).invokeMethod(getContext().getOwningThread().getThreadReference(), - hasNextMethod.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + Value hasNext = VariableUtils.invokeRemoteVMMethod(context, iterator, hasNextMethod.get(), null); return Boolean.parseBoolean(hasNext.toString()); } @@ -205,8 +202,7 @@ private Value nextElement(Value iterator) throws Exception { if (nextMethod.isEmpty()) { return null; } - return ((ObjectReference) iterator).invokeMethod(getContext().getOwningThread().getThreadReference(), - nextMethod.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.invokeRemoteVMMethod(context, iterator, nextMethod.get(), null); } private Value getValues(Value next) throws Exception { @@ -214,7 +210,6 @@ private Value getValues(Value next) throws Exception { if (getValuesMethod.isEmpty()) { return null; } - return ((ObjectReference) next).invokeMethod(getContext().getOwningThread().getThreadReference(), - getValuesMethod.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.invokeRemoteVMMethod(context, next, getValuesMethod.get(), null); } } diff --git a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java index b3125219b782..1e3e39101a20 100644 --- a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java +++ b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java @@ -520,25 +520,26 @@ public boolean waitForDebugTermination(long timeoutMillis) { */ public Map fetchVariables(StoppedEventArguments args, VariableScope scope) throws BallerinaTestException { - Map variables = new HashMap<>(); if (!hitListener.getConnector().isConnected()) { - return variables; + throw new BallerinaTestException("Debug server is not connected."); } - StackTraceArguments stackTraceArgs = new StackTraceArguments(); + StackTraceArguments traceArgs = new StackTraceArguments(); VariablesArguments variableArgs = new VariablesArguments(); ScopesArguments scopeArgs = new ScopesArguments(); - stackTraceArgs.setThreadId(args.getThreadId()); + traceArgs.setThreadId(args.getThreadId()); try { - StackTraceResponse stackResp = hitListener.getConnector().getRequestManager().stackTrace(stackTraceArgs); + StackTraceResponse stackResp = hitListener.getConnector().getRequestManager().stackTrace(traceArgs); StackFrame[] stackFrames = stackResp.getStackFrames(); if (stackFrames.length == 0) { - return variables; + throw new BallerinaTestException("Stack frame response does not contain any frames"); } scopeArgs.setFrameId(scope == VariableScope.LOCAL ? stackFrames[0].getId() : -stackFrames[0].getId()); ScopesResponse scopesResp = hitListener.getConnector().getRequestManager().scopes(scopeArgs); variableArgs.setVariablesReference(scopesResp.getScopes()[0].getVariablesReference()); VariablesResponse variableResp = hitListener.getConnector().getRequestManager().variables(variableArgs); + + Map variables = new HashMap<>(); Arrays.stream(variableResp.getVariables()).forEach(variable -> variables.put(variable.getName(), variable)); return variables; } catch (Exception e) { diff --git a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/variables/VariableVisibilityTest.java b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/variables/VariableVisibilityTest.java index cf0c359c4ada..93633889c655 100644 --- a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/variables/VariableVisibilityTest.java +++ b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/variables/VariableVisibilityTest.java @@ -345,6 +345,7 @@ public void localVariableChildrenVisibilityTest() throws BallerinaTestException localVariables = debugTestRunner.fetchVariables(debugHitInfo.getRight(), VariableScope.LOCAL); // xml child variable visibility test + Assert.assertNotNull(localVariables.get("xmlVar")); Map xmlChildVariables = debugTestRunner.fetchChildVariables(localVariables.get("xmlVar")); debugTestRunner.assertVariable(xmlChildVariables, "attributes", "XMLAttributeMap (size = 16)", "map"); debugTestRunner.assertVariable(xmlChildVariables, "children", "XMLSequence (size = 2)", "xml");