diff --git a/pom.xml b/pom.xml index b0d1f98..c6795ae 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.lwjglx lwjglx-debug - 1.0.5 + 1.0.6 LWJGLX/debug Debug LWJGL3 OpenGL applications 2017 diff --git a/src/org/lwjglx/debug/Agent.java b/src/org/lwjglx/debug/Agent.java index 3b0a01a..5883ec6 100644 --- a/src/org/lwjglx/debug/Agent.java +++ b/src/org/lwjglx/debug/Agent.java @@ -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); diff --git a/src/org/lwjglx/debug/InterceptClassGenerator.java b/src/org/lwjglx/debug/InterceptClassGenerator.java index 95cf4cb..410a128 100644 --- a/src/org/lwjglx/debug/InterceptClassGenerator.java +++ b/src/org/lwjglx/debug/InterceptClassGenerator.java @@ -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(); diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/CGL.java b/src/org/lwjglx/debug/org/lwjgl/opengl/CGL.java new file mode 100644 index 0000000..6b48c82 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/CGL.java @@ -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; + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/Context.java b/src/org/lwjglx/debug/org/lwjgl/opengl/Context.java index c88a568..2131d62 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/Context.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/Context.java @@ -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; diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GL.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GL.java index c405316..79983a4 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/GL.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GL.java @@ -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; @@ -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; @@ -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; diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GLX.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GLX.java new file mode 100644 index 0000000..740bbc6 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GLX.java @@ -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; + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GLX13.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GLX13.java new file mode 100644 index 0000000..3003314 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GLX13.java @@ -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; + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/WGL.java b/src/org/lwjglx/debug/org/lwjgl/opengl/WGL.java new file mode 100644 index 0000000..f1fb6de --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/WGL.java @@ -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; + } +} diff --git a/test/test/DebugIT.java b/test/test/DebugIT.java index 27859c5..318fcc4 100644 --- a/test/test/DebugIT.java +++ b/test/test/DebugIT.java @@ -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 { @@ -40,6 +45,7 @@ public class DebugIT { private static final Set CORE_PROFILE_TESTS = new HashSet<>(Arrays.asList( "testMethodReferences", + "testMethodReferencesWithTraceEnabled", "testNoVertexAttribPointerInCustomVAO", "testNoVertexAttribPointerInCustomVAOWithIndicesBuffer", "testBindVAOFromSharedContext", @@ -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, "", "()V", null, null); + ctor.visitCode(); + ctor.visitVarInsn(Opcodes.ALOAD, 0); + ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()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 supplier = (Supplier) 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> 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);