Skip to content

Commit 0ad140c

Browse files
make captured scopes available in debug output
1 parent 6fd3c81 commit 0ad140c

File tree

4 files changed

+135
-3
lines changed

4 files changed

+135
-3
lines changed

luceedebug/src/main/java/luceedebug/LuceeTransformer.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
import org.objectweb.asm.*;
77

8-
import luceedebug.instrumenter.CfmOrCfc;
9-
108
import java.security.ProtectionDomain;
119
import java.util.ArrayList;
1210

@@ -86,6 +84,9 @@ public byte[] transform(ClassLoader loader,
8684
if (className.equals("org/apache/felix/framework/Felix")) {
8785
return instrumentFelix(classfileBuffer, loader);
8886
}
87+
else if (className.equals("lucee/runtime/type/scope/ClosureScope")) {
88+
return instrumentClosureScope(classfileBuffer);
89+
}
8990
else if (className.equals("lucee/runtime/PageContextImpl")) {
9091
GlobalIDebugManagerHolder.luceeCoreLoader = loader;
9192

@@ -180,6 +181,26 @@ private byte[] instrumentPageContextImpl(final byte[] classfileBuffer) {
180181
return null;
181182
}
182183
}
184+
185+
private byte[] instrumentClosureScope(final byte[] classfileBuffer) {
186+
var classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
187+
188+
try {
189+
var instrumenter = new luceedebug.instrumenter.ClosureScope(Opcodes.ASM9, classWriter);
190+
var classReader = new ClassReader(classfileBuffer);
191+
192+
classReader.accept(instrumenter, ClassReader.EXPAND_FRAMES);
193+
194+
return classWriter.toByteArray();
195+
}
196+
catch (Throwable e) {
197+
System.err.println("[luceedebug] exception during attempted classfile rewrite");
198+
System.err.println(e.getMessage());
199+
e.printStackTrace();
200+
System.exit(1);
201+
return null;
202+
}
203+
}
183204

184205
private byte[] instrumentCfmOrCfc(final byte[] classfileBuffer, ClassReader reader, String className) {
185206
byte[] stepInstrumentedBuffer = classfileBuffer;
@@ -191,7 +212,7 @@ protected ClassLoader getClassLoader() {
191212
};
192213

193214
try {
194-
var instrumenter = new CfmOrCfc(Opcodes.ASM9, classWriter, className);
215+
var instrumenter = new luceedebug.instrumenter.CfmOrCfc(Opcodes.ASM9, classWriter, className);
195216
var classReader = new ClassReader(stepInstrumentedBuffer);
196217

197218
classReader.accept(instrumenter, ClassReader.EXPAND_FRAMES);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package luceedebug.coreinject;
2+
3+
/**
4+
* Intended to be an extension on lucee.runtime.type.scope.ClosureScope, applied during classfile rewrites during agent startup.
5+
*/
6+
public interface ClosureScopeLocalScopeAccessorShim {
7+
lucee.runtime.type.scope.Scope getLocalScope();
8+
}

luceedebug/src/main/java/luceedebug/coreinject/DebugFrame.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import java.util.ArrayList;
1212
import java.util.HashMap;
13+
import java.util.IdentityHashMap;
1314
import java.util.LinkedHashMap;
1415
import java.util.concurrent.ConcurrentHashMap;
1516
import java.util.concurrent.ConcurrentMap;
@@ -25,6 +26,12 @@
2526
public class DebugFrame implements IDebugFrame {
2627
static private AtomicLong nextId = new AtomicLong(0);
2728

29+
/**
30+
* It's not 100% clear that our instrumentation to walk captured closure scopes will always be valid across all class loaders,
31+
* and we assume that if it fails once, we should disable it across the entire program.
32+
*/
33+
static private boolean closureScopeGloballyDisabled = false;
34+
2835
private ValTracker valTracker;
2936
private RefTracker<CfEntityRef> refTracker;
3037

@@ -82,6 +89,9 @@ static class FrameContext {
8289
final lucee.runtime.type.scope.Scope server;
8390
final lucee.runtime.type.scope.Scope url;
8491
final lucee.runtime.type.scope.Variables variables;
92+
93+
// lazy init because it (might?) be expensive to walk scope chains eagerly every frame
94+
private ArrayList<lucee.runtime.type.scope.ClosureScope> capturedScopeChain = null;
8595

8696
static private final ConcurrentMap<PageContext, Object> activeFrameLockByPageContext = new MapMaker()
8797
.weakKeys()
@@ -105,6 +115,36 @@ static class FrameContext {
105115
this.variables = getScopeOrNull(() -> pageContext.variablesScope());
106116
}
107117

118+
public ArrayList<lucee.runtime.type.scope.ClosureScope> getCapturedScopeChain() {
119+
if (capturedScopeChain == null) {
120+
capturedScopeChain = getCapturedScopeChain(variables);
121+
}
122+
return capturedScopeChain;
123+
}
124+
125+
private static ArrayList<lucee.runtime.type.scope.ClosureScope> getCapturedScopeChain(lucee.runtime.type.scope.Scope variables) {
126+
if (variables instanceof lucee.runtime.type.scope.ClosureScope) {
127+
final var setLike_seen = new IdentityHashMap<>();
128+
final var result = new ArrayList<lucee.runtime.type.scope.ClosureScope>();
129+
var scope = variables;
130+
while (scope instanceof lucee.runtime.type.scope.ClosureScope) {
131+
final var captured = (lucee.runtime.type.scope.ClosureScope)scope;
132+
if (setLike_seen.containsKey(captured)) {
133+
break;
134+
}
135+
else {
136+
setLike_seen.put(captured, true);
137+
}
138+
result.add(captured);
139+
scope = captured.getVariables();
140+
}
141+
return result;
142+
}
143+
else {
144+
return new ArrayList<>();
145+
}
146+
}
147+
108148
interface SupplierOrNull<T> {
109149
T get() throws Throwable;
110150
}
@@ -206,6 +246,25 @@ private void lazyInitScopeRefs() {
206246
checkedPutScopeRef("server", frameContext_.server);
207247
checkedPutScopeRef("url", frameContext_.url);
208248
checkedPutScopeRef("variables", frameContext_.variables);
249+
250+
if (!closureScopeGloballyDisabled) {
251+
final var scopeChain = frameContext_.getCapturedScopeChain();
252+
final int captureChainLen = scopeChain.size();
253+
try {
254+
for (int i = 0; i < captureChainLen; i++) {
255+
// this should always succeed, there's no casting into a luceedebug shim type
256+
checkedPutScopeRef("captured arguments " + i, scopeChain.get(i).getArgument());
257+
// this could potentially fail with a class cast exception
258+
checkedPutScopeRef("captured local " + i, ((ClosureScopeLocalScopeAccessorShim)scopeChain.get(i)).getLocalScope());
259+
}
260+
}
261+
catch (ClassCastException e) {
262+
// We'll be left with possibly some capture scopes in the list this time around,
263+
// but all subsequent calls to this method will be guarded by this assignment.
264+
closureScopeGloballyDisabled = true;
265+
return;
266+
}
267+
}
209268
}
210269

211270
/**
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package luceedebug.instrumenter;
2+
3+
import org.objectweb.asm.*;
4+
import org.objectweb.asm.commons.GeneratorAdapter;
5+
6+
/**
7+
* extend lucee.runtime.type.scope.ClosureScope to implement ClosureScopeLocalScopeAccessorShim
8+
*/
9+
10+
public class ClosureScope extends ClassVisitor {
11+
public ClosureScope(int api, ClassWriter cw) {
12+
super(api, cw);
13+
}
14+
15+
@Override
16+
public void visit(
17+
int version,
18+
int access,
19+
String name,
20+
String signature,
21+
String superName,
22+
String[] interfaces
23+
) {
24+
final var augmentedInterfaces = new String[interfaces.length + 1];
25+
for (int i = 0; i < interfaces.length; i++) {
26+
augmentedInterfaces[i] = interfaces[i];
27+
}
28+
augmentedInterfaces[interfaces.length] = "luceedebug/coreinject/ClosureScopeLocalScopeAccessorShim";
29+
30+
super.visit(version, access, name, signature, superName, augmentedInterfaces);
31+
}
32+
33+
@Override
34+
public void visitEnd() {
35+
final var name = "getLocalScope";
36+
final var descriptor = "()Llucee/runtime/type/scope/Scope;";
37+
final var mv = visitMethod(org.objectweb.asm.Opcodes.ACC_PUBLIC, name, descriptor, null, null);
38+
final var ga = new GeneratorAdapter(mv, org.objectweb.asm.Opcodes.ACC_PUBLIC, name, descriptor);
39+
ga.loadThis();
40+
ga.getField(org.objectweb.asm.Type.getType("Llucee/runtime/type/scope/ClosureScope;"), "local", org.objectweb.asm.Type.getType("Llucee/runtime/type/scope/Local;"));
41+
ga.returnValue();
42+
ga.endMethod();
43+
}
44+
}

0 commit comments

Comments
 (0)