Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.lwjglx</groupId>
<artifactId>lwjglx-debug</artifactId>
<version>1.0.5</version>
<version>1.0.6</version>
<name>LWJGLX/debug</name>
<description>Debug LWJGL3 OpenGL applications</description>
<inceptionYear>2017</inceptionYear>
Expand Down
48 changes: 25 additions & 23 deletions src/org/lwjglx/debug/Agent.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,31 +188,33 @@ public void visitInvokeDynamicInsn(String dynamicname, String descriptor, Handle
Object secondArgument = bootstrapMethodArguments[1];
if (secondArgument instanceof Handle) {
Handle h = (Handle) secondArgument;
String owner = h.getOwner();
String name = h.getName();
String desc = h.getDesc();
if (owner.startsWith("org/lwjgl/") && !excluded(owner, name)) {
String key = owner + "." + name + desc;
InterceptedCall call = calls.get(key);
if (call == null) {
/* Resolve declaring class */
String resolvedOwner = resolveOwner(owner, name, desc);
/* Rewrite a GLnnC call to GLnn to be able to intercept the call */
if (resolvedOwner.matches(".*/GL(\\d\\d)C$")) {
resolvedOwner = resolvedOwner.substring(0, resolvedOwner.length() - 1);
if (h.getTag() == H_INVOKESTATIC) {
String owner = h.getOwner();
String name = h.getName();
String desc = h.getDesc();
if (owner.startsWith("org/lwjgl/") && !excluded(owner, name)) {
String key = owner + "." + name + desc;
InterceptedCall call = calls.get(key);
if (call == null) {
/* Resolve declaring class */
String resolvedOwner = resolveOwner(owner, name, desc);
/* Rewrite a GLnnC call to GLnn to be able to intercept the call */
if (resolvedOwner.matches(".*/GL(\\d\\d)C$")) {
resolvedOwner = resolvedOwner.substring(0, resolvedOwner.length() - 1);
}
call = new InterceptedCall(owner, resolvedOwner, name, desc);
String methodName;
methodName = name + call.index;
call.generatedMethodName = methodName;
calls.put(key, call);
}
call = new InterceptedCall(owner, resolvedOwner, name, desc);
String methodName;
methodName = name + call.index;
call.generatedMethodName = methodName;
calls.put(key, call);
Log.maxLineNumberLength = Math.max(Log.maxLineNumberLength, (int) (Math.log10(lastLineNumber) + 1));
// modify invokedynamic handle
Handle newHandle = new Handle(H_INVOKESTATIC, proxyName, call.generatedMethodName, call.desc, false);
bootstrapMethodArguments[1] = newHandle;
modifications.needsProxyClass = true;
}
Log.maxLineNumberLength = Math.max(Log.maxLineNumberLength, (int) (Math.log10(lastLineNumber) + 1));
// modify invokedynamic handle
Handle newHandle = new Handle(H_INVOKESTATIC, proxyName, call.generatedMethodName, call.desc, false);
bootstrapMethodArguments[1] = newHandle;
modifications.needsProxyClass = true;
}
}
}
}
super.visitInvokeDynamicInsn(dynamicname, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
Expand Down
17 changes: 17 additions & 0 deletions src/org/lwjglx/debug/InterceptClassGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,23 @@ public static Class<?> generate(ClassLoader classLoader, String proxyInternalNam
}
mv.visitMaxs(-1, -1);
mv.visitEnd();

if (TRACE.enabled) {
MethodVisitor mvOverload = cw.visitMethod(ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, call.generatedMethodName, call.desc, null, null);
mvOverload.visitCode();
Type[] paramTypes = Type.getArgumentTypes(call.desc);
int var = 0;
for (Type paramType : paramTypes) {
mvOverload.visitVarInsn(paramType.getOpcode(ILOAD), var);
var += paramType.getSize();
}
Util.ldcI(mvOverload, -1);
mvOverload.visitMethodInsn(INVOKESTATIC, proxyInternalName, call.generatedMethodName, effectiveDesc, false);
Type retType = Type.getReturnType(call.desc);
mvOverload.visitInsn(retType.getOpcode(IRETURN));
mvOverload.visitMaxs(-1, -1);
mvOverload.visitEnd();
}
}
cw.visitEnd();
byte[] arr = cw.toByteArray();
Expand Down
23 changes: 23 additions & 0 deletions src/org/lwjglx/debug/org/lwjgl/opengl/CGL.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.lwjglx.debug.org.lwjgl.opengl;

import static org.lwjglx.debug.org.lwjgl.opengl.Context.*;

public class CGL {

public static int CGLSetCurrentContext(long context) {
int result = org.lwjgl.opengl.CGL.CGLSetCurrentContext(context);
if (result == org.lwjgl.opengl.CGL.kCGLNoError) {
if (context == 0L) {
CURRENT_CONTEXT.remove();
} else {
Context ctx = CONTEXTS.get(context);
if (ctx == null) {
Context.create(context, 0L);
ctx = CONTEXTS.get(context);
}
CURRENT_CONTEXT.set(ctx);
}
}
return result;
}
}
10 changes: 10 additions & 0 deletions src/org/lwjglx/debug/org/lwjgl/opengl/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ public static Context currentContext() {
return ctx;
}

public static Context createCurrent() {
org.lwjglx.debug.Log.warn("No OpenGL context has been made current through recognized API methods. Created a fallback context.", new Throwable(), 3);
Context ctx = new Context();
ctx.counter = CONTEXT_COUNTER.getAndIncrement();
ctx.shareGroup = new ShareGroup();
ctx.shareGroup.contexts.add(ctx);
CURRENT_CONTEXT.set(ctx);
return ctx;
}

public static void create(long window, long share) {
Context ctx = new Context();
ctx.window = window;
Expand Down
13 changes: 11 additions & 2 deletions src/org/lwjglx/debug/org/lwjgl/opengl/GL.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ public static org.lwjgl.opengl.GLCapabilities createCapabilities() {
if (Properties.VALIDATE.enabled) {
callback = GLUtil.setupDebugMessageCallback();
}
Context context = Context.currentContext();
Context context = CURRENT_CONTEXT.get();
if (context == null) {
context = Context.createCurrent();
}
context.caps = caps;
context.debugCallback = callback;
int GL_MAX_VERTEX_ATTRIBS = 16;
Expand All @@ -53,7 +56,10 @@ public static org.lwjgl.opengl.GLCapabilities createCapabilities(boolean forward
if (Properties.VALIDATE.enabled) {
callback = GLUtil.setupDebugMessageCallback();
}
Context context = Context.currentContext();
Context context = CURRENT_CONTEXT.get();
if (context == null) {
context = Context.createCurrent();
}
context.caps = caps;
context.debugCallback = callback;
int GL_MAX_VERTEX_ATTRIBS = 16;
Expand All @@ -69,6 +75,9 @@ public static org.lwjgl.opengl.GLCapabilities createCapabilities(boolean forward
public static void setCapabilities(org.lwjgl.opengl.GLCapabilities caps) {
org.lwjgl.opengl.GL.setCapabilities(caps);
Context context = CURRENT_CONTEXT.get();
if (context == null && caps != null) {
context = Context.createCurrent();
}
if (context != null) {
/* Can happen when calling setCapabilities(null) after glfwDestroyWindow()/glfwMakeContextCurrent(0L) */
context.caps = caps;
Expand Down
23 changes: 23 additions & 0 deletions src/org/lwjglx/debug/org/lwjgl/opengl/GLX.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.lwjglx.debug.org.lwjgl.opengl;

import static org.lwjglx.debug.org.lwjgl.opengl.Context.*;

public class GLX {

public static boolean glXMakeCurrent(long display, long drawable, long ctx) {
boolean result = org.lwjgl.opengl.GLX.glXMakeCurrent(display, drawable, ctx);
if (result) {
if (ctx == 0L) {
CURRENT_CONTEXT.remove();
} else {
Context context = CONTEXTS.get(ctx);
if (context == null) {
Context.create(ctx, 0L);
context = CONTEXTS.get(ctx);
}
CURRENT_CONTEXT.set(context);
}
}
return result;
}
}
23 changes: 23 additions & 0 deletions src/org/lwjglx/debug/org/lwjgl/opengl/GLX13.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.lwjglx.debug.org.lwjgl.opengl;

import static org.lwjglx.debug.org.lwjgl.opengl.Context.*;

public class GLX13 {

public static boolean glXMakeContextCurrent(long display, long draw, long read, long ctx) {
boolean result = org.lwjgl.opengl.GLX13.glXMakeContextCurrent(display, draw, read, ctx);
if (result) {
if (ctx == 0L) {
CURRENT_CONTEXT.remove();
} else {
Context context = CONTEXTS.get(ctx);
if (context == null) {
Context.create(ctx, 0L);
context = CONTEXTS.get(ctx);
}
CURRENT_CONTEXT.set(context);
}
}
return result;
}
}
23 changes: 23 additions & 0 deletions src/org/lwjglx/debug/org/lwjgl/opengl/WGL.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.lwjglx.debug.org.lwjgl.opengl;

import static org.lwjglx.debug.org.lwjgl.opengl.Context.*;

public class WGL {

public static boolean wglMakeCurrent(java.nio.IntBuffer buffer, long hdc, long hglrc) {
boolean result = org.lwjgl.opengl.WGL.wglMakeCurrent(buffer, hdc, hglrc);
if (result) {
if (hglrc == 0L) {
CURRENT_CONTEXT.remove();
} else {
Context context = CONTEXTS.get(hglrc);
if (context == null) {
Context.create(hglrc, 0L);
context = CONTEXTS.get(hglrc);
}
CURRENT_CONTEXT.set(context);
}
}
return result;
}
}
131 changes: 131 additions & 0 deletions test/test/DebugIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
import org.lwjgl.system.*;
import org.lwjglx.debug.Properties;
import org.lwjglx.debug.org.lwjgl.opengl.Context;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class DebugIT {

Expand All @@ -40,6 +45,7 @@ public class DebugIT {

private static final Set<String> CORE_PROFILE_TESTS = new HashSet<>(Arrays.asList(
"testMethodReferences",
"testMethodReferencesWithTraceEnabled",
"testNoVertexAttribPointerInCustomVAO",
"testNoVertexAttribPointerInCustomVAOWithIndicesBuffer",
"testBindVAOFromSharedContext",
Expand Down Expand Up @@ -254,6 +260,131 @@ public void testMethodReferences() {
glEnableVertexAttribArray(3);
}

private static class CustomClassLoader extends ClassLoader {
CustomClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> define(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}

@Test
public void testMethodReferencesWithTraceEnabled() throws Exception {
boolean oldTrace = Properties.TRACE.enabled;
Properties.TRACE.enabled = true;
try {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, "test/DynamicTraceTestClass", null, "java/lang/Object", null);

MethodVisitor ctor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
ctor.visitCode();
ctor.visitVarInsn(Opcodes.ALOAD, 0);
ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
ctor.visitInsn(Opcodes.RETURN);
ctor.visitMaxs(-1, -1);
ctor.visitEnd();

MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "getSupplier", "()Ljava/util/function/Supplier;", null, null);
mv.visitCode();

Handle bootstrapMethod = new Handle(
Opcodes.H_INVOKESTATIC,
"java/lang/invoke/LambdaMetafactory",
"metafactory",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
false
);

mv.visitInvokeDynamicInsn(
"get",
"()Ljava/util/function/Supplier;",
bootstrapMethod,
Type.getType("()Ljava/lang/Object;"),
new Handle(
Opcodes.H_INVOKESTATIC,
"org/lwjgl/opengl/GL32C",
"glGenVertexArrays",
"()I",
false
),
Type.getType("()Ljava/lang/Integer;")
);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
cw.visitEnd();
byte[] bytes = cw.toByteArray();

CustomClassLoader loader = new CustomClassLoader(getClass().getClassLoader());
Class<?> clazz = loader.define("test.DynamicTraceTestClass", bytes);
java.lang.reflect.Method getSupplier = clazz.getMethod("getSupplier");
@SuppressWarnings("unchecked")
Supplier<Integer> supplier = (Supplier<Integer>) getSupplier.invoke(null);

window = glfwCreateWindow(800, 600, "", 0L, 0L);
glfwMakeContextCurrent(window);
createCapabilities();
glBindVertexArray(supplier.get());
glEnableVertexAttribArray(3);
} finally {
Properties.TRACE.enabled = oldTrace;
}
}

@Test
public void testInstanceMethodReferenceNotIntercepted() {
long monitor = glfwGetPrimaryMonitor();
if (monitor == 0L) {
return;
}
org.lwjgl.glfw.GLFWVidMode.Buffer modes = glfwGetVideoModes(monitor);
if (modes == null) {
return;
}
java.util.function.Function<org.lwjgl.glfw.GLFWVidMode.Buffer, java.util.stream.Stream<org.lwjgl.glfw.GLFWVidMode>> streamFunc = org.lwjgl.glfw.GLFWVidMode.Buffer::stream;
assertNotNull(streamFunc.apply(modes));
}

@Test
public void testFallbackContextCreation() {
window = glfwCreateWindow(800, 600, "", 0L, 0L);
glfwMakeContextCurrent(window);

org.lwjglx.debug.org.lwjgl.opengl.Context.CURRENT_CONTEXT.remove();

createCapabilities();

assertNotNull(org.lwjglx.debug.org.lwjgl.opengl.Context.CURRENT_CONTEXT.get(), "Fallback context should be created automatically");
}

@Test
public void testMacOSCGLContextInterception() {
if (!isMac) {
return;
}
try {
window = glfwCreateWindow(800, 600, "", 0L, 0L);
glfwMakeContextCurrent(window);
createCapabilities();

long cglCtx = org.lwjgl.glfw.GLFWNativeNSGL.glfwGetNSGLContext(window);
if (cglCtx == 0L) {
return;
}

org.lwjglx.debug.org.lwjgl.opengl.Context.CURRENT_CONTEXT.remove();

int result = org.lwjgl.opengl.CGL.CGLSetCurrentContext(cglCtx);
if (result == org.lwjgl.opengl.CGL.kCGLNoError) {
assertNotNull(org.lwjglx.debug.org.lwjgl.opengl.Context.CURRENT_CONTEXT.get());
assertEquals(cglCtx, org.lwjglx.debug.org.lwjgl.opengl.Context.CURRENT_CONTEXT.get().window);
}
} catch (UnsatisfiedLinkError e) {
// Ignored if CGL native library is not loaded
}
}

@Test
public void testNoVertexAttribPointerInCustomVAO() {
window = glfwCreateWindow(800, 600, "", 0L, 0L);
Expand Down