Skip to content

Commit eb1b8dd

Browse files
authored
Merge pull request #50637 from angelozerr/stacktrace_improvement
Refactor RemoteThread to improve section frame handling (#for, #each, #if)
2 parents 86b175f + 93ecd69 commit eb1b8dd

File tree

22 files changed

+437
-34
lines changed

22 files changed

+437
-34
lines changed

independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public class LoopSectionHelper implements SectionHelper {
4545
this.engine = context.getEngine();
4646
}
4747

48+
public String getMetadataPrefix() {
49+
return metadataPrefix;
50+
}
51+
4852
@Override
4953
public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
5054
return context.resolutionContext().evaluate(iterable).thenCompose(it -> {

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/DebuggeeAgent.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.quarkus.qute.debug.agent;
22

3-
import static io.quarkus.qute.debug.agent.RemoteStackFrame.EMPTY_STACK_FRAMES;
3+
import static io.quarkus.qute.debug.agent.frames.RemoteStackFrame.EMPTY_STACK_FRAMES;
44
import static io.quarkus.qute.debug.agent.scopes.RemoteScope.EMPTY_SCOPES;
55

66
import java.net.URI;
@@ -37,6 +37,7 @@
3737
import io.quarkus.qute.debug.agent.breakpoints.RemoteBreakpoint;
3838
import io.quarkus.qute.debug.agent.completions.CompletionSupport;
3939
import io.quarkus.qute.debug.agent.evaluations.EvaluationSupport;
40+
import io.quarkus.qute.debug.agent.frames.RemoteStackFrame;
4041
import io.quarkus.qute.debug.agent.source.SourceReferenceRegistry;
4142
import io.quarkus.qute.debug.agent.source.SourceTemplateRegistry;
4243
import io.quarkus.qute.debug.agent.variables.VariablesRegistry;
@@ -177,7 +178,7 @@ public void onStartTemplate(TemplateEvent event) {
177178
*
178179
* @param event the resolve event representing the current node.
179180
*/
180-
public void onTemplateNode(ResolveEvent event) {
181+
public void onBeforeResolve(ResolveEvent event) {
181182
if (!isEnabled()) {
182183
return;
183184
}
@@ -188,7 +189,15 @@ public void onTemplateNode(ResolveEvent event) {
188189
output(args);
189190

190191
RemoteThread debuggee = getOrCreateDebuggeeThread();
191-
debuggee.onTemplateNode(event);
192+
debuggee.onBeforeResolve(event);
193+
}
194+
195+
public void onAfterResolve(ResolveEvent event) {
196+
if (!isEnabled()) {
197+
return;
198+
}
199+
RemoteThread debuggee = getOrCreateDebuggeeThread();
200+
debuggee.onAfterResolve(event);
192201
}
193202

194203
/**

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/DebuggerEvalContext.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.quarkus.qute.EvalContext;
88
import io.quarkus.qute.Expression;
99
import io.quarkus.qute.ResolutionContext;
10+
import io.quarkus.qute.debug.agent.frames.RemoteStackFrame;
1011

1112
/**
1213
* Implementation of {@link EvalContext} used by the Qute debugger.

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/DebuggerTraceListener.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ public DebuggerTraceListener(DebuggeeAgent agent) {
3939
*/
4040
@Override
4141
public void onBeforeResolve(ResolveEvent event) {
42-
agent.onTemplateNode(event);
42+
agent.onBeforeResolve(event);
43+
}
44+
45+
@Override
46+
public void onAfterResolve(ResolveEvent event) {
47+
agent.onAfterResolve(event);
4348
}
4449

4550
/**

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/RemoteThread.java

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import io.quarkus.qute.debug.ThreadEvent;
1818
import io.quarkus.qute.debug.ThreadEvent.ThreadStatus;
1919
import io.quarkus.qute.debug.agent.breakpoints.RemoteBreakpoint;
20+
import io.quarkus.qute.debug.agent.frames.RemoteStackFrame;
21+
import io.quarkus.qute.debug.agent.frames.SectionFrameGroup;
2022
import io.quarkus.qute.trace.ResolveEvent;
2123

2224
/**
@@ -65,6 +67,8 @@ public class RemoteThread extends Thread {
6567
*/
6668
private transient final LinkedList<RemoteStackFrame> frames;
6769

70+
private transient final LinkedList<SectionFrameGroup> sectionFrameStack;
71+
6872
/**
6973
* Reference to the agent managing this thread.
7074
*/
@@ -88,6 +92,7 @@ public class RemoteThread extends Thread {
8892
public RemoteThread(java.lang.Thread thread, DebuggeeAgent agent) {
8993
this.lock = new Object();
9094
this.frames = new LinkedList<>();
95+
this.sectionFrameStack = new LinkedList<>();
9196
super.setId((int) thread.getId());
9297
super.setName(thread.getName());
9398
this.agent = agent;
@@ -158,35 +163,68 @@ public boolean isStopped() {
158163
}
159164

160165
/**
161-
* Called when a Qute template node is about to be resolved.
166+
* Called before a Qute template node is resolved.
167+
* <p>
168+
* Updates the current stack frames and handles section frame groups for
169+
* template sections (loops, conditionals, etc.).
170+
* </p>
171+
*
162172
* <p>
163-
* This method updates the stack frames and determines whether
164-
* execution should be suspended due to a breakpoint or step condition.
173+
* Sections include constructs like #for, #each, #if, and any other Qute
174+
* section nodes. Each section has its own SectionFrameGroup which tracks
175+
* the stack frames created while rendering that section.
165176
* </p>
166177
*
167-
* @param event the {@link ResolveEvent} representing the node resolution
178+
* @param event the ResolveEvent representing the node resolution
168179
*/
169-
public void onTemplateNode(ResolveEvent event) {
180+
public void onBeforeResolve(ResolveEvent event) {
170181
if (this.isStopped()) {
171182
return;
172183
}
173184

174185
var engine = event.getEngine();
175-
RemoteStackFrame frame = new RemoteStackFrame(event, getCurrentFrame(), agent.getSourceTemplateRegistry(engine),
176-
agent.getVariablesRegistry(), this);
186+
187+
// Create a new stack frame for the current node
188+
RemoteStackFrame frame = new RemoteStackFrame(
189+
event,
190+
getCurrentFrame(),
191+
agent.getSourceTemplateRegistry(engine),
192+
agent.getVariablesRegistry(),
193+
this);
194+
195+
// Handle section frames (loops / #for / #each / #if / other sections)
196+
var sectionGroup = sectionFrameStack.isEmpty() ? null : sectionFrameStack.getFirst();
197+
if (sectionGroup != null) {
198+
// Update the section frame group index if iteration has changed and detach old frames
199+
sectionGroup.detachFramesIfIndexChanged(event.getContext().getData(), frames);
200+
// Add the current frame to the section group
201+
sectionGroup.addFrame(frame);
202+
}
203+
204+
// If the current node is a section, push a new SectionFrameGroup to track its frames
205+
if (event.getTemplateNode().isSection()) {
206+
sectionFrameStack.addFirst(new SectionFrameGroup(event.getTemplateNode().asSection().getHelper()));
207+
}
208+
209+
// Add the frame to the main thread stack
177210
this.frames.addFirst(frame);
211+
178212
String templateId = frame.getTemplateId();
179213
URI sourceUri = frame.getTemplateUri();
180214
RemoteStackFrame previous = frame.getPrevious();
181215

216+
// Step handling: suspend if stop condition matches
182217
if (this.stopCondition != null && this.stopCondition.test(event.getTemplateNode())) {
183218
// suspend and wait because of step reason.
184219
this.suspendAndWait(StoppedReason.STEP);
185220
} else {
221+
// Breakpoint handling: suspend if a breakpoint matches this frame
186222
int lineNumber = frame.getLine();
187223
RemoteBreakpoint breakpoint = agent.getBreakpoint(sourceUri, templateId, lineNumber, engine);
188224
if (breakpoint != null // a breakpoint matches the frame line number
189-
&& (previous == null || !previous.getTemplateId().equals(templateId) || previous.getLine() != lineNumber
225+
&& (previous == null
226+
|| !previous.getTemplateId().equals(templateId)
227+
|| previous.getLine() != lineNumber
190228
|| event.getTemplateNode().isExpression())
191229
&& breakpoint.checkCondition(frame)) {
192230
// suspend and wait because of breakpoint reason.
@@ -195,6 +233,29 @@ public void onTemplateNode(ResolveEvent event) {
195233
}
196234
}
197235

236+
/**
237+
* Called after a Qute template node has been resolved.
238+
* <p>
239+
* Cleans up section frame groups if the resolved node was a section.
240+
* This includes loops (#for / #each), conditionals (#if), or any other
241+
* section nodes. Frames accumulated during the section are detached from
242+
* the main thread stack when the section ends.
243+
* </p>
244+
*
245+
* @param event the ResolveEvent representing the node resolution
246+
*/
247+
public void onAfterResolve(ResolveEvent event) {
248+
if (this.isStopped()) {
249+
return;
250+
}
251+
252+
// If the node is a section, pop its SectionFrameGroup and detach its frames
253+
if (event.getTemplateNode().isSection()) {
254+
var sectionGroup = sectionFrameStack.removeFirst();
255+
sectionGroup.detachFrames(frames);
256+
}
257+
}
258+
198259
/**
199260
* Suspends execution of the render thread and waits for debugger interaction,
200261
* such as "step", "resume", or expression evaluation.

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/breakpoints/RemoteBreakpoint.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import org.eclipse.lsp4j.debug.Source;
77

88
import io.quarkus.qute.TemplateNode;
9-
import io.quarkus.qute.debug.agent.RemoteStackFrame;
9+
import io.quarkus.qute.debug.agent.frames.RemoteStackFrame;
1010

1111
/**
1212
* Represents a remote breakpoint set by the client through the Debug Adapter Protocol (DAP).

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/completions/CompletionContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import org.eclipse.lsp4j.debug.CompletionItemType;
1212
import org.eclipse.lsp4j.debug.CompletionsResponse;
1313

14-
import io.quarkus.qute.debug.agent.RemoteStackFrame;
14+
import io.quarkus.qute.debug.agent.frames.RemoteStackFrame;
1515
import io.quarkus.qute.debug.agent.resolvers.ValueResolverContext;
1616

1717
/**

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/completions/CompletionSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import org.eclipse.lsp4j.debug.CompletionsResponse;
99

1010
import io.quarkus.qute.debug.agent.DebuggeeAgent;
11-
import io.quarkus.qute.debug.agent.RemoteStackFrame;
11+
import io.quarkus.qute.debug.agent.frames.RemoteStackFrame;
1212
import io.quarkus.qute.debug.agent.resolvers.ValueResolverRegistry;
1313

1414
/**

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/evaluations/EvaluationSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
1010

1111
import io.quarkus.qute.debug.agent.DebuggeeAgent;
12-
import io.quarkus.qute.debug.agent.RemoteStackFrame;
12+
import io.quarkus.qute.debug.agent.frames.RemoteStackFrame;
1313
import io.quarkus.qute.debug.agent.variables.VariablesHelper;
1414

1515
/**
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.quarkus.qute.debug.agent;
1+
package io.quarkus.qute.debug.agent.frames;
22

33
import java.net.URI;
44
import java.util.ArrayList;
@@ -13,6 +13,8 @@
1313
import io.quarkus.qute.EvalContext;
1414
import io.quarkus.qute.TemplateNode;
1515
import io.quarkus.qute.TextNode;
16+
import io.quarkus.qute.debug.agent.DebuggerEvalContext;
17+
import io.quarkus.qute.debug.agent.RemoteThread;
1618
import io.quarkus.qute.debug.agent.evaluations.ConditionalExpressionHelper;
1719
import io.quarkus.qute.debug.agent.scopes.GlobalsScope;
1820
import io.quarkus.qute.debug.agent.scopes.LocalsScope;
@@ -263,7 +265,7 @@ public Engine getEngine() {
263265
}
264266

265267
/** @return the current {@link ResolveEvent} associated with this frame */
266-
ResolveEvent getEvent() {
268+
public ResolveEvent getEvent() {
267269
return event;
268270
}
269271

0 commit comments

Comments
 (0)