Skip to content

Commit

Permalink
improve inspection of component scope
Browse files Browse the repository at this point in the history
  • Loading branch information
softwareCobbler committed Feb 24, 2024
1 parent fe83700 commit 9b8a3ac
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 37 deletions.
31 changes: 31 additions & 0 deletions luceedebug/src/main/java/luceedebug/LuceeTransformer.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ public byte[] transform(ClassLoader loader,
else if (className.equals("lucee/runtime/type/scope/ClosureScope")) {
return instrumentClosureScope(classfileBuffer);
}
else if (className.equals("lucee/runtime/ComponentImpl")) {
if (loader == null) {
throw new RuntimeException("instrumention ComponentImpl but core loader not seen yet");
}
return instrumentComponentImpl(classfileBuffer, loader);
}
else if (className.equals("lucee/runtime/PageContextImpl")) {
GlobalIDebugManagerHolder.luceeCoreLoader = loader;

Expand Down Expand Up @@ -201,6 +207,31 @@ private byte[] instrumentClosureScope(final byte[] classfileBuffer) {
return null;
}
}

private byte[] instrumentComponentImpl(final byte[] classfileBuffer, ClassLoader loader) {
var classWriter = new ClassWriter(/*ClassWriter.COMPUTE_FRAMES |*/ ClassWriter.COMPUTE_MAXS) {
@Override
protected ClassLoader getClassLoader() {
return loader;
}
};

try {
var instrumenter = new luceedebug.instrumenter.ComponentImpl(Opcodes.ASM9, classWriter);
var classReader = new ClassReader(classfileBuffer);

classReader.accept(instrumenter, ClassReader.EXPAND_FRAMES);

return classWriter.toByteArray();
}
catch (Throwable e) {
System.err.println("[luceedebug] exception during attempted classfile rewrite");
System.err.println(e.getMessage());
e.printStackTrace();
System.exit(1);
return null;
}
}

private byte[] instrumentCfmOrCfc(final byte[] classfileBuffer, ClassReader reader, String className) {
byte[] stepInstrumentedBuffer = classfileBuffer;
Expand Down
91 changes: 58 additions & 33 deletions luceedebug/src/main/java/luceedebug/coreinject/CfEntityRef.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,59 @@

// CfValue
class CfEntityRef implements ICfEntityRef {
private final ValTracker valTracker;
private final DebugFrame frame;
public final Object obj;
public final long id;

public CfEntityRef(ValTracker valTracker, Object obj) {
this.valTracker = valTracker;
public CfEntityRef(DebugFrame frame, Object obj) {
this.frame = frame;
this.obj = obj;
this.id = valTracker.idempotentRegisterObject(obj).id;
this.id = frame.valTracker.idempotentRegisterObject(obj).id;
}

public long getID() {
return id;
}

static class MarkerTrait {
static class Scope {
public final Map<?,?> scopelike;
Scope(Map<?,?> scopelike) {
this.scopelike = scopelike;
}
}
}

/**
* @maybeNull_which --> null means "any type"
*/
public static IDebugEntity[] getAsDebugEntity(DebugFrame frame, Object obj, IDebugEntity.DebugEntityType maybeNull_which) {
return getAsDebugEntity(frame.valTracker, obj, maybeNull_which);
}

public static IDebugEntity[] getAsDebugEntity(ValTracker valTracker, Object obj, IDebugEntity.DebugEntityType maybeNull_which) {
final boolean namedOK = maybeNull_which == null || maybeNull_which == IDebugEntity.DebugEntityType.NAMED;
final boolean indexedOK = maybeNull_which == null || maybeNull_which == IDebugEntity.DebugEntityType.INDEXED;

if (obj instanceof Map && namedOK) {
/*
if (cfEntity.wrapped instanceof Component) {
if (this.flags.contains(Flags.isScope) || this.flags.contains(Flags.isIterableThisRef)) {
@SuppressWarnings("unchecked")
var m = (Map<String, Object>)cfEntity.wrapped;
return getAsMaplike(m);
}
else {
return new IDebugEntity[] {
maybeNull_asValue("this", cfEntity.wrapped, true, true),
maybeNull_asValue("variables", ((Component)cfEntity.wrapped).getComponentScope()),
maybeNull_asValue("static", ((Component)cfEntity.wrapped).staticScope())
};
}
}
*/
if (obj instanceof MarkerTrait.Scope && namedOK) {
@SuppressWarnings("unchecked")
var m = (Map<String, Object>)obj;
var m = (Map<String, Object>)(((MarkerTrait.Scope)obj).scopelike);
return getAsMaplike(valTracker, m);
}
else if (obj instanceof Map && namedOK) {
if (obj instanceof Component) {
return new IDebugEntity[] {
maybeNull_asValue(valTracker, "this", obj, true, true),
maybeNull_asValue(valTracker, "variables", ((Component)obj).getComponentScope()),
maybeNull_asValue(valTracker, "static", ((Component)obj).staticScope())
};
}
else {
@SuppressWarnings("unchecked")
var m = (Map<String, Object>)obj;
return getAsMaplike(valTracker, m);
}
}
else if (obj instanceof Array && indexedOK) {
return getAsCfArray(valTracker, (Array)obj);
}
Expand All @@ -74,18 +85,18 @@ private static IDebugEntity[] getAsMaplike(ValTracker valTracker, Map<String, Ob
final var skipNoisyComponentFunctions = true;

for (Map.Entry<String, Object> entry : entries) {
IDebugEntity val = maybeNull_asValue(valTracker, entry.getKey(), entry.getValue(), skipNoisyComponentFunctions);
IDebugEntity val = maybeNull_asValue(valTracker, entry.getKey(), entry.getValue(), skipNoisyComponentFunctions, false);
if (val != null) {
results.add(val);
}
}

{
DebugEntity val = new DebugEntity();
val.name = "__scopeID";
val.value = "" + valTracker.idempotentRegisterObject(map).id;
results.add(val);
}
// {
// DebugEntity val = new DebugEntity();
// val.name = "__luceedebugValueID";
// val.value = "" + valTracker.idempotentRegisterObject(map).id;
// results.add(val);
// }

results.sort(xscopeByName);

Expand All @@ -107,7 +118,7 @@ private static IDebugEntity[] getAsCfArray(ValTracker valTracker, Array array) {
}

public IDebugEntity maybeNull_asValue(String name) {
return maybeNull_asValue(valTracker, name, obj, true);
return maybeNull_asValue(frame.valTracker, name, obj, true, false);
}

/**
Expand All @@ -117,14 +128,20 @@ public IDebugEntity maybeNull_asValue(String name) {
* Maybe such things should be optionally included as per some configuration.
*/
private static IDebugEntity maybeNull_asValue(ValTracker valTracker, String name, Object obj) {
return maybeNull_asValue(valTracker, name, obj, true);
return maybeNull_asValue(valTracker, name, obj, true, false);
}

/**
* @markDiscoveredComponentsAsIterableThisRef if true, a Component will be marked as if it were any normal Map<String, Object>. This drives discovery of variables;
* showing the "top level" of a component we want to show its "inner scopes" (this, variables, and static)
*/
private static IDebugEntity maybeNull_asValue(ValTracker valTracker, String name, Object obj, boolean skipNoisyComponentFunctions) {
private static IDebugEntity maybeNull_asValue(
ValTracker valTracker,
String name,
Object obj,
boolean skipNoisyComponentFunctions,
boolean treatDiscoveredComponentsAsScopes
) {
DebugEntity val = new DebugEntity();
val.name = name;

Expand Down Expand Up @@ -194,12 +211,20 @@ else if (obj instanceof lucee.runtime.type.QueryImpl) {
else if (obj instanceof Map) {
if (obj instanceof Component) {
val.value = "cfc<" + ((Component)obj).getName() + ">";
if (treatDiscoveredComponentsAsScopes) {
var v = new MarkerTrait.Scope((Component)obj);
((ComponentScopeMarkerTraitShim)obj).__luceedebug__pinComponentScopeMarkerTrait(v);
val.variablesReference = valTracker.idempotentRegisterObject(v).id;
}
else {
val.variablesReference = valTracker.idempotentRegisterObject(obj).id;
}
}
else {
int len = ((Map<?,?>)obj).size();
val.value = "{} (" + len + " members)";
val.variablesReference = valTracker.idempotentRegisterObject(obj).id;
}
val.variablesReference = valTracker.idempotentRegisterObject(obj).id;
}
else {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package luceedebug.coreinject;

/**
* Intended to be an extension on lucee.runtime.ComponentImpl
* We need to disambiguate between a component meaning "container of a this/variables/static scope" and "literally a this scope"
* Because we want to show something like the following to the IDE:
*
* someObj : cfc<SomeCfcType>
* - variables -> {...}
* - this -> {...}
* - static -> {...}
*
* But, `someObj` literally is `this` in the above; so we need to know when to show the nested scope listing, and when to show
* the contents of the `this` scope.
*
* There is only a setter because we just use the setter to pin the disambiguating-wrapper object onto something with a
* reasonable lifetime to prevent it from being GC'd.
*/
public interface ComponentScopeMarkerTraitShim {
void __luceedebug__pinComponentScopeMarkerTrait(Object obj);
}
14 changes: 10 additions & 4 deletions luceedebug/src/main/java/luceedebug/coreinject/DebugFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.common.collect.MapMaker;

import luceedebug.*;
import luceedebug.coreinject.CfEntityRef.MarkerTrait;

public class DebugFrame implements IDebugFrame {
static private AtomicLong nextId = new AtomicLong(0);
Expand All @@ -27,7 +28,7 @@ public class DebugFrame implements IDebugFrame {
*/
static private boolean closureScopeGloballyDisabled = false;

private ValTracker valTracker;
public final ValTracker valTracker;

final private FrameContext frameContext_;
final private String sourceFilePath;
Expand Down Expand Up @@ -57,6 +58,9 @@ public class DebugFrame implements IDebugFrame {
// these should be made gc'able when this frame is collected
// We might want to place these results somewhere that is kept alive for the whole request?
private ArrayList<Object> refsToKeepAlive_ = new ArrayList<>();
void pin(Object obj) {
refsToKeepAlive_.add(obj);
}

// hold strong refs to scopes, because PageContext will swap them out as frames change (variables, local, this)
// (application, server and etc. maybe could be held as globals)
Expand Down Expand Up @@ -242,7 +246,9 @@ private static String tryGetFrameName(PageContext pageContext) {

private void checkedPutScopeRef(String name, Map<?,?> scope) {
if (scope != null && !(scope instanceof LocalNotSupportedScope)) {
scopes_.put(name, new CfEntityRef(valTracker, scope));
var v = new MarkerTrait.Scope(scope);
pin(v);
scopes_.put(name, new CfEntityRef(this, v));
}
}

Expand Down Expand Up @@ -315,7 +321,7 @@ public IDebugEntity[] getScopes() {
}

CfEntityRef trackEvalResult(Object obj) {
refsToKeepAlive_.add(obj);
return new CfEntityRef(valTracker, obj);
pin(obj);
return new CfEntityRef(this, obj);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package luceedebug.instrumenter;

import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;

public class ComponentImpl extends ClassVisitor {
public ComponentImpl(int api, ClassWriter cw) {
super(api, cw);
}

@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces
) {
final var augmentedInterfaces = new String[interfaces.length + 1];
for (int i = 0; i < interfaces.length; i++) {
augmentedInterfaces[i] = interfaces[i];
}
augmentedInterfaces[interfaces.length] = "luceedebug/coreinject/ComponentScopeMarkerTraitShim";

super.visit(version, access, name, signature, superName, augmentedInterfaces);
}

@Override
public void visitEnd() {
final var fieldName = "__luceedebug__pinned_componentScopeMarkerTrait";
visitField(org.objectweb.asm.Opcodes.ACC_PUBLIC, fieldName, "Ljava/lang/Object;", null, null);

final var name = "__luceedebug__pinComponentScopeMarkerTrait";
final var descriptor = "(Ljava/lang/Object;)V";
final var mv = visitMethod(org.objectweb.asm.Opcodes.ACC_PUBLIC, name, descriptor, null, null);
final var ga = new GeneratorAdapter(mv, org.objectweb.asm.Opcodes.ACC_PUBLIC, name, descriptor);

ga.loadThis();
ga.loadArg(0);
ga.putField(org.objectweb.asm.Type.getType("Llucee/runtime/ComponentImpl;"), fieldName, org.objectweb.asm.Type.getType("Ljava/lang/Object;"));
ga.visitInsn(org.objectweb.asm.Opcodes.RETURN);
ga.endMethod();
}
}

0 comments on commit 9b8a3ac

Please sign in to comment.