Skip to content

Commit f649bc6

Browse files
authored
evaluation handler and stackframe proxy for handling InvalidStackFrameException (#131)
* 1. replace JdiObjectProxy with StackFrameProxy, because only stackframe uses it . * 1. add a missing import. * rename a variable * fix compilation error after merge * 1. add lock to prevent concurrent stackframe access 2. redefine the StackFrameProxy to only have thread and depth since the stackframe instance may be out of date 3. add stackframe provider and add logic to update stackframe during evaluation. * add stackframe provider and add logic to update stackframe during evaluation. * fix for a better flow * fix for a better flow * revert minor changes which is not needed. * remove useless lines. * clean evaluation inner states when the thread is to be continued. * clean evaluation inner states when the thread is to be continued. * lock on acquireEvaluationLock * remove the duplicate expr check * revert unneeded change * refine if-else flow. * redefine the lock * change another method to override indicating sf is updated. * rename some classes suggested by reviewer * 1. refact variableProxy to contain thread reference, 2. stackframes/varaible/setVariable/evaluate will first gain the thread lock on stack frame manager. * some minor changes of spaces and javadoc. * convert to supplyAsync with try logic to handle lock * Andy eval20 (#138) * Redefine HCR event. (#133) * Sequentially process debug requests (#135) * bump version to 0.5.0 (#136) * Redefine HCR event. * bump version to 0.5.0 Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * don't use lock * don't use lock * don't use lock * Minor changes: indentation, rename code -> expression
1 parent 862b445 commit f649bc6

File tree

18 files changed

+376
-320
lines changed

18 files changed

+376
-320
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class DebugAdapterContext implements IDebugAdapterContext {
4545
private RecyclableObjectPool<Long, Object> recyclableIdPool = new RecyclableObjectPool<>();
4646
private IVariableFormatter variableFormatter = VariableFormatterFactory.createVariableFormatter();
4747

48+
private IStackFrameManager stackFrameManager = new StackFrameManager();
49+
4850
public DebugAdapterContext(IProtocolServer server, IProviderContext providerContext) {
4951
this.providerContext = providerContext;
5052
this.server = server;
@@ -224,4 +226,9 @@ public void setStepFilters(StepFilters stepFilters) {
224226
public StepFilters getStepFilters() {
225227
return stepFilters;
226228
}
229+
230+
@Override
231+
public IStackFrameManager getStackFrameManager() {
232+
return stackFrameManager;
233+
}
227234
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,6 @@ public interface IDebugAdapterContext {
9999
void setStepFilters(StepFilters stepFilters);
100100

101101
StepFilters getStepFilters();
102+
103+
IStackFrameManager getStackFrameManager();
102104
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IEvaluationProvider.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
import java.util.concurrent.CompletableFuture;
1515

16-
import com.sun.jdi.StackFrame;
1716
import com.sun.jdi.ThreadReference;
1817
import com.sun.jdi.Value;
1918

@@ -34,18 +33,20 @@ public interface IEvaluationProvider extends IProvider {
3433
* Evaluate the expression at the given project and thread and stack frame depth, the promise is to be resolved/rejected when
3534
* the evaluation finishes.
3635
*
37-
* @param projectName The java project which provides resolve class used in the expression
3836
* @param expression The expression to be evaluated
39-
* @param sf The stack frame of the evaluation task
40-
* @return the evaluation result
37+
* @param thread The jdi thread to the expression will be executed at
38+
* @param depth The depth of stackframe of the stopped thread
39+
* @return the evaluation result future
4140
*/
42-
CompletableFuture<Value> evaluate(String projectName, String expression, StackFrame sf);
41+
CompletableFuture<Value> evaluate(String expression, ThreadReference thread, int depth);
4342

4443

4544
/**
46-
* Cancel ongoing evaluation tasks on specified thread.
45+
* Call this method when the thread is to be resumed by user, it will first cancel ongoing evaluation tasks on specified thread and
46+
* ensure the inner states is cleaned.
47+
*
4748
* @param thread the JDI thread reference where the evaluation task is executing at
4849
*/
49-
void cancelEvaluation(ThreadReference thread);
50+
void clearState(ThreadReference thread);
5051

5152
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.core.adapter;
13+
14+
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
15+
import com.sun.jdi.StackFrame;
16+
import com.sun.jdi.ThreadReference;
17+
18+
public interface IStackFrameManager {
19+
/**
20+
* Get a jdi stack frame from stack frame reference.
21+
*
22+
* @param ref the stackframe reference
23+
* @return the jdi stackframe
24+
*/
25+
StackFrame getStackFrame(StackFrameReference ref);
26+
27+
/**
28+
* Refresh all stackframes from jdi thread.
29+
*
30+
* @param thread the jdi thread
31+
* @return all the stackframes in the specified thread
32+
*/
33+
StackFrame[] reloadStackFrames(ThreadReference thread);
34+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.core.adapter;
13+
14+
import java.util.Collections;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
18+
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
19+
import com.sun.jdi.IncompatibleThreadStateException;
20+
import com.sun.jdi.StackFrame;
21+
import com.sun.jdi.ThreadReference;
22+
23+
public class StackFrameManager implements IStackFrameManager {
24+
private Map<Long, StackFrame[]> threadStackFrameMap = Collections.synchronizedMap(new HashMap<>());
25+
26+
@Override
27+
public StackFrame getStackFrame(StackFrameReference ref) {
28+
ThreadReference thread = ref.getThread();
29+
int depth = ref.getDepth();
30+
StackFrame[] frames = threadStackFrameMap.get(thread.uniqueID());
31+
return frames == null || frames.length < depth ? null : frames[depth];
32+
}
33+
34+
@Override
35+
public StackFrame[] reloadStackFrames(ThreadReference thread) {
36+
return threadStackFrameMap.compute(thread.uniqueID(), (key, old) -> {
37+
try {
38+
return thread.frames().toArray(new StackFrame[0]);
39+
} catch (IncompatibleThreadStateException e) {
40+
return new StackFrame[0];
41+
}
42+
});
43+
}
44+
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.microsoft.java.debug.core.adapter.ErrorCode;
2929
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
3030
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
31+
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
3132
import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider;
3233
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
3334
import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider;
@@ -88,6 +89,8 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
8889
}
8990
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
9091
sourceProvider.initialize(context, options);
92+
IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class);
93+
evaluationProvider.initialize(context, options);
9194
IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class);
9295
hcrProvider.initialize(context, options);
9396

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.microsoft.java.debug.core.adapter.ErrorCode;
2424
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
2525
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
26+
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
2627
import com.microsoft.java.debug.core.protocol.Events;
2728
import com.microsoft.java.debug.core.protocol.Messages.Response;
2829
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
@@ -97,6 +98,10 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
9798
// In order to avoid two duplicated StoppedEvents, the debugger will skip the BreakpointEvent.
9899
} else {
99100
ThreadReference bpThread = ((BreakpointEvent) event).thread();
101+
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
102+
if (engine.isInEvaluation(bpThread)) {
103+
return;
104+
}
100105
context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID()));
101106
debugEvent.shouldResume = false;
102107
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java

Lines changed: 46 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,36 @@
1515
import java.util.List;
1616
import java.util.Map;
1717
import java.util.concurrent.CompletableFuture;
18-
import java.util.regex.Pattern;
19-
import java.util.stream.Collectors;
18+
import java.util.concurrent.CompletionException;
19+
import java.util.concurrent.ExecutionException;
20+
import java.util.logging.Level;
21+
import java.util.logging.Logger;
2022

2123
import org.apache.commons.lang3.StringUtils;
2224

25+
import com.microsoft.java.debug.core.Configuration;
2326
import com.microsoft.java.debug.core.DebugSettings;
2427
import com.microsoft.java.debug.core.adapter.AdapterUtils;
2528
import com.microsoft.java.debug.core.adapter.ErrorCode;
2629
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
2730
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
28-
import com.microsoft.java.debug.core.adapter.variables.JdiObjectProxy;
29-
import com.microsoft.java.debug.core.adapter.variables.Variable;
31+
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
32+
import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter;
33+
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
3034
import com.microsoft.java.debug.core.adapter.variables.VariableProxy;
3135
import com.microsoft.java.debug.core.adapter.variables.VariableUtils;
3236
import com.microsoft.java.debug.core.protocol.Messages.Response;
3337
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
3438
import com.microsoft.java.debug.core.protocol.Requests.Command;
3539
import com.microsoft.java.debug.core.protocol.Requests.EvaluateArguments;
3640
import com.microsoft.java.debug.core.protocol.Responses;
37-
import com.sun.jdi.AbsentInformationException;
3841
import com.sun.jdi.ArrayReference;
39-
import com.sun.jdi.Field;
4042
import com.sun.jdi.ObjectReference;
41-
import com.sun.jdi.PrimitiveValue;
42-
import com.sun.jdi.StackFrame;
43-
import com.sun.jdi.ThreadReference;
4443
import com.sun.jdi.Value;
44+
import com.sun.jdi.VoidValue;
4545

4646
public class EvaluateRequestHandler implements IDebugRequestHandler {
47-
private final Pattern simpleExprPattern = Pattern.compile("[A-Za-z0-9_.\\s]+");
47+
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
4848

4949
@Override
5050
public List<Command> getTargetCommands() {
@@ -54,130 +54,55 @@ public List<Command> getTargetCommands() {
5454
@Override
5555
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
5656
EvaluateArguments evalArguments = (EvaluateArguments) arguments;
57-
if (StringUtils.isBlank(evalArguments.expression)) {
58-
return AdapterUtils.createAsyncErrorResponse(
59-
response,
60-
ErrorCode.ARGUMENT_MISSING,
61-
"EvaluateRequest: property 'expression' is missing, null, or empty");
62-
}
63-
6457
final boolean showStaticVariables = DebugSettings.getCurrent().showStaticVariables;
65-
6658
Map<String, Object> options = context.getVariableFormatter().getDefaultOptions();
6759
VariableUtils.applyFormatterOptions(options, evalArguments.format != null && evalArguments.format.hex);
6860
String expression = evalArguments.expression;
6961

7062
if (StringUtils.isBlank(expression)) {
71-
return AdapterUtils.createAsyncErrorResponse(response,
72-
ErrorCode.EVALUATE_FAILURE,
63+
return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EVALUATE_FAILURE,
7364
"Failed to evaluate. Reason: Empty expression cannot be evaluated.");
7465
}
75-
76-
if (!simpleExprPattern.matcher(expression).matches()) {
77-
return AdapterUtils.createAsyncErrorResponse(response,
78-
ErrorCode.EVALUATE_FAILURE,
79-
"Failed to evaluate. Reason: Complex expression is not supported currently.");
80-
}
81-
82-
JdiObjectProxy<StackFrame> stackFrameProxy = (JdiObjectProxy<StackFrame>) context.getRecyclableIdPool().getObjectById(evalArguments.frameId);
83-
if (stackFrameProxy == null) {
84-
// stackFrameProxy is null means the stackframe is continued by user manually,
85-
return AdapterUtils.createAsyncErrorResponse(response,
86-
ErrorCode.EVALUATE_FAILURE,
66+
StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(evalArguments.frameId);
67+
if (stackFrameReference == null) {
68+
// stackFrameReference is null means the stackframe is continued by user manually,
69+
return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EVALUATE_FAILURE,
8770
"Failed to evaluate. Reason: Cannot evaluate because the thread is resumed.");
8871
}
8972

90-
// split a.b.c => ["a", "b", "c"]
91-
List<String> referenceExpressions = Arrays.stream(StringUtils.split(expression, '.'))
92-
.filter(StringUtils::isNotBlank).map(StringUtils::trim).collect(Collectors.toList());
93-
94-
// get first level of value from stack frame
95-
Variable firstLevelValue = null;
96-
boolean inStaticMethod = stackFrameProxy.getProxiedObject().location().method().isStatic();
97-
String firstExpression = referenceExpressions.get(0);
98-
// handle special case of 'this'
99-
if (firstExpression.equals("this") && !inStaticMethod) {
100-
firstLevelValue = VariableUtils.getThisVariable(stackFrameProxy.getProxiedObject());
101-
}
102-
if (firstLevelValue == null) {
73+
return CompletableFuture.supplyAsync(() -> {
10374
try {
104-
// local variables first, that means
105-
// if both local variable and static variable are found, use local variable
106-
List<Variable> localVariables = VariableUtils.listLocalVariables(stackFrameProxy.getProxiedObject());
107-
List<Variable> matchedLocal = localVariables.stream()
108-
.filter(localVariable -> localVariable.name.equals(firstExpression)).collect(Collectors.toList());
109-
if (!matchedLocal.isEmpty()) {
110-
firstLevelValue = matchedLocal.get(0);
111-
} else {
112-
List<Variable> staticVariables = VariableUtils.listStaticVariables(stackFrameProxy.getProxiedObject());
113-
List<Variable> matchedStatic = staticVariables.stream()
114-
.filter(staticVariable -> staticVariable.name.equals(firstExpression)).collect(Collectors.toList());
115-
if (matchedStatic.isEmpty()) {
116-
return AdapterUtils.createAsyncErrorResponse(response,
117-
ErrorCode.EVALUATE_FAILURE,
118-
String.format("Failed to evaluate. Reason: Cannot find the variable: %s.", referenceExpressions.get(0)));
119-
}
120-
firstLevelValue = matchedStatic.get(0);
75+
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
76+
Value value = engine.evaluate(expression, stackFrameReference.getThread(), stackFrameReference.getDepth()).get();
77+
IVariableFormatter variableFormatter = context.getVariableFormatter();
78+
if (value instanceof VoidValue) {
79+
response.body = new Responses.EvaluateResponseBody(value.toString(), 0, "<void>", 0);
80+
return response;
12181
}
122-
123-
} catch (AbsentInformationException e) {
124-
// ignore
125-
}
126-
}
127-
128-
if (firstLevelValue == null) {
129-
return AdapterUtils.createAsyncErrorResponse(response,
130-
ErrorCode.EVALUATE_FAILURE,
131-
String.format("Failed to evaluate. Reason: Cannot find variable with name '%s'.", referenceExpressions.get(0)));
132-
}
133-
ThreadReference thread = stackFrameProxy.getProxiedObject().thread();
134-
Value currentValue = firstLevelValue.value;
135-
136-
for (int i = 1; i < referenceExpressions.size(); i++) {
137-
String fieldName = referenceExpressions.get(i);
138-
if (currentValue == null) {
139-
return AdapterUtils.createAsyncErrorResponse(response,
140-
ErrorCode.EVALUATE_FAILURE,
141-
"Failed to evaluate. Reason: Evaluation encounters NPE error.");
142-
}
143-
if (currentValue instanceof PrimitiveValue) {
144-
return AdapterUtils.createAsyncErrorResponse(response,
145-
ErrorCode.EVALUATE_FAILURE,
146-
String.format("Failed to evaluate. Reason: Cannot find the field: %s.", fieldName));
147-
}
148-
if (currentValue instanceof ArrayReference) {
149-
return AdapterUtils.createAsyncErrorResponse(response,
150-
ErrorCode.EVALUATE_FAILURE,
151-
String.format("Failed to evaluate. Reason: Evaluating array elements is not supported currently.", fieldName));
152-
}
153-
ObjectReference obj = (ObjectReference) currentValue;
154-
Field field = obj.referenceType().fieldByName(fieldName);
155-
if (field == null) {
156-
return AdapterUtils.createAsyncErrorResponse(response,
157-
ErrorCode.EVALUATE_FAILURE,
158-
String.format("Failed to evaluate. Reason: Cannot find the field: %s.", fieldName));
159-
}
160-
if (field.isStatic()) {
161-
return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EVALUATE_FAILURE,
162-
String.format("Failed to evaluate. Reason: Cannot find the field: %s.", fieldName));
82+
long threadId = stackFrameReference.getThread().uniqueID();
83+
if (value instanceof ObjectReference) {
84+
VariableProxy varProxy = new VariableProxy(stackFrameReference.getThread(), "eval", value);
85+
int referenceId = VariableUtils.hasChildren(value, showStaticVariables)
86+
? context.getRecyclableIdPool().addObject(threadId, varProxy) : 0;
87+
int indexedVariableId = value instanceof ArrayReference ? ((ArrayReference) value).length() : 0;
88+
response.body = new Responses.EvaluateResponseBody(variableFormatter.valueToString(value, options),
89+
referenceId, variableFormatter.typeToString(value == null ? null : value.type(), options),
90+
indexedVariableId);
91+
return response;
92+
}
93+
// for primitive value
94+
response.body = new Responses.EvaluateResponseBody(variableFormatter.valueToString(value, options), 0,
95+
variableFormatter.typeToString(value == null ? null : value.type(), options), 0);
96+
return response;
97+
} catch (InterruptedException | ExecutionException e) {
98+
Throwable cause = e;
99+
if (e instanceof ExecutionException && e.getCause() != null) {
100+
cause = e.getCause();
101+
}
102+
// TODO: distinguish user error of wrong expression(eg: compilation error)
103+
logger.log(Level.WARNING, String.format("Cannot evalution expression because of %s.", cause.toString()), cause);
104+
throw new CompletionException(cause);
163105
}
164-
currentValue = obj.getValue(field);
165-
}
166-
167-
int referenceId = 0;
168-
if (currentValue instanceof ObjectReference && VariableUtils.hasChildren(currentValue, showStaticVariables)) {
169-
// save the evaluated value in object pool, because like java.lang.String, the evaluated object will have sub structures
170-
// we need to set up the id map.
171-
VariableProxy varProxy = new VariableProxy(thread.uniqueID(), "Local", currentValue);
172-
referenceId = context.getRecyclableIdPool().addObject(thread.uniqueID(), varProxy);
173-
}
174-
int indexedVariables = 0;
175-
if (currentValue instanceof ArrayReference) {
176-
indexedVariables = ((ArrayReference) currentValue).length();
177-
}
178-
response.body = new Responses.EvaluateResponseBody(context.getVariableFormatter().valueToString(currentValue, options),
179-
referenceId, context.getVariableFormatter().typeToString(currentValue == null ? null : currentValue.type(), options),
180-
indexedVariables);
181-
return CompletableFuture.completedFuture(response);
106+
});
182107
}
183108
}

0 commit comments

Comments
 (0)