X Tutup
Skip to content

Commit 2a63529

Browse files
committed
Experimental java.io.Closeable debugging
1 parent 02b2913 commit 2a63529

File tree

4 files changed

+202
-191
lines changed

4 files changed

+202
-191
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ Java Agent for debugging common libgdx related issues.
66

77
* UNDISPOSED (-Dgdxdbg.debug.undisposed=true)
88
- Enables debugging if a Disposable object is finalized without being properly disposed.
9-
* DOUBLE_DISPOSE (-Dgdxdbg.debug.double_dispose=false)
10-
- Enables debugging if a dispose method is called multiple times.
11-
- Not recommended to use, double dispose calls should be made safe if not already.
9+
* DEBUG_UNCLOSED (-Dgdxdbg.debug.unclosed=true)
10+
- Enables debugging if a java.io.Closeable object is finalized without being properly disposed.
11+
- Cannot currently debug classes which are loaded before java agent is loaded. :(
1212
* MODIFIABLE_CONSTANTS (-Dgdxdbg.debug.modifiable_constants=true)
1313
- Enables debugging if certain `modifiable constant`'s values change during runtime.
1414
- Things constants like Color.WHITE can be accidently modified, leading to unexpected results.

src/main/java/gdxdbg/DgbAgentClassFileTransformer.java

Lines changed: 8 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,29 @@
33
import java.io.ByteArrayInputStream;
44
import java.lang.instrument.ClassFileTransformer;
55
import java.lang.instrument.IllegalClassFormatException;
6-
import java.lang.reflect.Modifier;
76
import java.security.ProtectionDomain;
87

9-
import javassist.CannotCompileException;
8+
import gdxdbg.DisposableTransformer.ModifiedCloseable;
9+
import gdxdbg.DisposableTransformer.ModifiedDisposible;
1010
import javassist.ClassPool;
1111
import javassist.CtClass;
12-
import javassist.CtField;
13-
import javassist.CtMethod;
14-
import javassist.CtNewMethod;
1512
import javassist.LoaderClassPath;
16-
import javassist.NotFoundException;
1713

1814
/**
1915
* {@link ClassFileTransformer} which performs class transformations to add various debugging utilities.
2016
* @author Desu
2117
*/
2218
public class DgbAgentClassFileTransformer implements ClassFileTransformer
2319
{
24-
public static final String FIELD_NAME_EXCEPTION = "$$$disposeexception";
25-
public static final String FIELD_NAME_EXCEPTION_DOUBLE_DISPOSE = "$$$doubledisposeexception";
26-
20+
private DisposableTransformer disposableTransformer = new DisposableTransformer("dispose", "$$$disposeexception", "com.badlogic.gdx.utils.Disposable", ModifiedDisposible.class);
21+
private DisposableTransformer closeableTransformer = new DisposableTransformer("close", "$$$closeeexception", "java.io.Closeable", ModifiedCloseable.class);
2722
private ModifiableConstants modifiableConstants = new ModifiableConstants();
2823
private GLThreadVerification glThreadVerification = new GLThreadVerification();
29-
30-
private static CtClass[] EMPTY_CT_CLASSES = new CtClass[0];
3124

3225
@Override
3326
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
3427
{
35-
if(className.startsWith("java/") || className.startsWith("javax/"))
28+
if(!Properties.DEBUG_UNCLOSED && (className.startsWith("java/") || className.startsWith("javax/")))
3629
return null;
3730

3831
try
@@ -62,49 +55,13 @@ protected byte[] transformClass(ClassLoader loader, byte[] clazzBytes) throws Ex
6255
}
6356

6457
if(glThreadVerification.transform(clazz))
65-
{
6658
modified = true;
67-
}
6859

69-
boolean isDisposable = false;
70-
try
71-
{
72-
CtClass disposable = cp.get("com.badlogic.gdx.utils.Disposable");
73-
isDisposable = isDisposable(clazz, disposable);
74-
}
75-
catch(NotFoundException e)
76-
{
77-
// Don't care
78-
}
79-
80-
boolean foundValidDisposeMethod = false;
81-
if(isDisposable)
82-
{
83-
try
84-
{
85-
CtMethod method = clazz.getDeclaredMethod("dispose", EMPTY_CT_CLASSES);
86-
foundValidDisposeMethod = isDisposible(method);
87-
}
88-
catch(NotFoundException e)
89-
{
90-
// Don't care
91-
}
92-
}
60+
if(Properties.DEBUG_UNDISPOSED && disposableTransformer.transform(cp, clazz))
61+
modified = true;
9362

94-
if(foundValidDisposeMethod)
95-
{
96-
CtClass eo = cp.get(ModifiedDisposible.class.getName());
97-
for(CtClass i : clazz.getInterfaces())
98-
{
99-
if(i.equals(eo))
100-
{
101-
throw new RuntimeException("Class already implements ModifiedDisposible interface");
102-
}
103-
}
104-
105-
writeDisposibleObjectImpl(clazz);
63+
if(Properties.DEBUG_UNCLOSED && closeableTransformer.transform(cp, clazz))
10664
modified = true;
107-
}
10865

10966
if(modified)
11067
{
@@ -115,136 +72,4 @@ protected byte[] transformClass(ClassLoader loader, byte[] clazzBytes) throws Ex
11572

11673
return null;
11774
}
118-
119-
private boolean isDisposable(CtClass clazz, CtClass disposable) throws Exception
120-
{
121-
if(clazz == null)
122-
return false;
123-
124-
if(clazz.getName().startsWith("com.badlogic.gdx.graphics.g3d.particles.influencers.DynamicsModifier") || clazz.getName().startsWith("com.badlogic.gdx.graphics.g3d.particles.ParticleControllerComponent"))
125-
return false;
126-
127-
// These aren't necessarily required to be disposed, and their sub resources will warn us of issues
128-
if(clazz.getName().startsWith("com.badlogic.gdx.graphics.g2d.PixmapPacker") || clazz.getName().startsWith("com.badlogic.gdx.maps.Map"))
129-
return false;
130-
131-
if(clazz.equals(disposable))
132-
return true;
133-
134-
for(CtClass i : clazz.getInterfaces())
135-
{
136-
if(isDisposable(i, disposable))
137-
return true;
138-
}
139-
140-
if(isDisposable(clazz.getSuperclass(), disposable))
141-
return true;
142-
143-
return false;
144-
}
145-
146-
protected void writeDisposibleObjectImpl(CtClass clazz) throws NotFoundException, CannotCompileException
147-
{
148-
ClassPool cp = clazz.getClassPool();
149-
clazz.addInterface(cp.get(ModifiedDisposible.class.getName()));
150-
writeDisposibleObjectFields(clazz);
151-
writeDisposibleObjectMethods(clazz);
152-
}
153-
154-
private void writeDisposibleObjectFields(CtClass clazz) throws CannotCompileException, NotFoundException
155-
{
156-
ClassPool cp = clazz.getClassPool();
157-
158-
if(Properties.DEBUG_UNDISPOSED)
159-
{
160-
// add object that holds path from initialization
161-
CtField cbField = new CtField(cp.get(RuntimeException.class.getName()), FIELD_NAME_EXCEPTION, clazz);
162-
cbField.setModifiers(Modifier.PRIVATE | Modifier.TRANSIENT);
163-
clazz.addField(cbField, CtField.Initializer.byExpr("new RuntimeException(\"Undisposed \"+getClass().getName()+\" resource\");"));
164-
}
165-
166-
if(Properties.DEBUG_DOUBLE_DISPOSE)
167-
{
168-
// add object that holds path from dispose for double dispose calls
169-
CtField cbField = new CtField(cp.get(RuntimeException.class.getName()), FIELD_NAME_EXCEPTION_DOUBLE_DISPOSE, clazz);
170-
cbField.setModifiers(Modifier.PRIVATE | Modifier.TRANSIENT);
171-
clazz.addField(cbField);
172-
}
173-
}
174-
175-
private void writeDisposibleObjectMethods(CtClass clazz) throws NotFoundException, CannotCompileException
176-
{
177-
if(Properties.DEBUG_UNDISPOSED)
178-
{
179-
CtMethod meth = null;
180-
try
181-
{
182-
meth = clazz.getDeclaredMethod("finalize", EMPTY_CT_CLASSES);
183-
}
184-
catch(NotFoundException e)
185-
{
186-
//Ignored
187-
}
188-
189-
StringBuilder sb = new StringBuilder();
190-
if(clazz.getName().equals("com.badlogic.gdx.graphics.GLTexture"))
191-
{
192-
sb.append("if((this instanceof com.badlogic.gdx.graphics.Texture) || (this instanceof com.badlogic.gdx.graphics.Cubemap)){} else ");
193-
}
194-
sb.append("if("+FIELD_NAME_EXCEPTION+" != null) "+FIELD_NAME_EXCEPTION+".printStackTrace();");
195-
196-
if(meth == null)
197-
{
198-
if(Properties.TRACE)
199-
System.out.println("finalize not found creating it");
200-
CtMethod m = CtNewMethod.make("public void finalize() throws Throwable { "+sb.toString()+"; super.finalize(); } ", clazz);
201-
clazz.addMethod(m);
202-
}
203-
else
204-
{
205-
if(Properties.TRACE)
206-
System.out.println("modifing " + clazz.getName() + "'s finalize method");
207-
meth.insertBefore("{ "+sb.toString()+" }");
208-
}
209-
}
210-
211-
if(Properties.DEBUG_UNDISPOSED || Properties.DEBUG_DOUBLE_DISPOSE)
212-
{
213-
CtMethod meth = null;
214-
try
215-
{
216-
meth = clazz.getDeclaredMethod("dispose", EMPTY_CT_CLASSES);
217-
}
218-
catch(NotFoundException e)
219-
{
220-
//Ignored
221-
}
222-
223-
if(meth != null)
224-
{
225-
if(Properties.DEBUG_UNDISPOSED)
226-
meth.insertBefore("{ "+FIELD_NAME_EXCEPTION+" = null; }");
227-
228-
// Some are safe to double dispose
229-
if(Properties.DEBUG_DOUBLE_DISPOSE && !"com.badlogic.gdx.graphics.Texture".equals(clazz.getName()))
230-
meth.insertBefore("{ synchronized(this) { if("+FIELD_NAME_EXCEPTION_DOUBLE_DISPOSE+" != null) new RuntimeException(\"Double Dispose\", "+FIELD_NAME_EXCEPTION_DOUBLE_DISPOSE+").printStackTrace(); "
231-
+ FIELD_NAME_EXCEPTION_DOUBLE_DISPOSE+" = new RuntimeException(\"Previous Dispose\");"
232-
+ "} }");
233-
}
234-
}
235-
}
236-
237-
protected boolean isDisposible(CtMethod method)
238-
{
239-
int modifiers = method.getModifiers();
240-
if(Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers) || Modifier.isStatic(modifiers))
241-
return false;
242-
243-
return method.getName().equals("dispose");
244-
}
245-
246-
public static interface ModifiedDisposible
247-
{
248-
249-
}
25075
}

0 commit comments

Comments
 (0)
X Tutup