Skip to content

Commit 33ddac8

Browse files
committed
Filter FB/Java internals for better stack trace for exceptions in user's code.
1 parent 6de037e commit 33ddac8

File tree

6 files changed

+202
-35
lines changed

6 files changed

+202
-35
lines changed

src/fbjava-tests/src/main/java/example/Functions.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package example;
2020

2121
import java.math.BigDecimal;
22+
import java.security.AccessController;
23+
import java.security.PrivilegedAction;
2224
import java.sql.Blob;
2325
import java.sql.Connection;
2426
import java.sql.DriverManager;
@@ -174,4 +176,53 @@ public static String f21(String property)
174176
{
175177
return System.getProperty(property);
176178
}
179+
180+
public static String f22(String property)
181+
{
182+
return AccessController.doPrivileged(new PrivilegedAction<String>() {
183+
@Override
184+
public String run()
185+
{
186+
return System.getProperty(property);
187+
}
188+
});
189+
}
190+
191+
public static int f23() throws Exception
192+
{
193+
throw new Exception("f23");
194+
}
195+
196+
public static int f24() throws Exception
197+
{
198+
try
199+
{
200+
f23();
201+
return 0;
202+
}
203+
catch (Exception e)
204+
{
205+
throw new Exception("f24", e);
206+
}
207+
}
208+
209+
public static class C1
210+
{
211+
public static String s = System.getProperty("x");
212+
213+
public static int f25()
214+
{
215+
return 0;
216+
}
217+
}
218+
219+
public static class C2
220+
{
221+
public static String s = System.getProperty("java.version");
222+
223+
public static int f26()
224+
{
225+
return 0;
226+
}
227+
}
177228
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* FB/Java plugin
3+
*
4+
* Distributable under LGPL license.
5+
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
6+
*
7+
* This program is distributed in the hope that it will be useful,
8+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
* LGPL License for more details.
11+
*
12+
* This file was created by members of the Firebird development team.
13+
* All individual contributions remain the Copyright (C) of those
14+
* individuals. Contributors to this file are either listed here or
15+
* can be obtained from a git log command.
16+
*
17+
* All rights reserved.
18+
*/
19+
package org.firebirdsql.fbjava.impl;
20+
21+
22+
/***
23+
* A Callable which throws Throwable.
24+
*
25+
* @author asfernandes
26+
*/
27+
@FunctionalInterface
28+
interface CallableThrowable<V>
29+
{
30+
V call() throws Throwable;
31+
}

src/fbjava/src/main/java/org/firebirdsql/fbjava/impl/ExternalEngine.java

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.net.URLStreamHandler;
3131
import java.security.AccessControlContext;
3232
import java.security.Principal;
33+
import java.security.PrivilegedActionException;
3334
import java.security.PrivilegedExceptionAction;
3435
import java.security.ProtectionDomain;
3536
import java.sql.Blob;
@@ -40,7 +41,6 @@
4041
import java.util.Calendar;
4142
import java.util.HashMap;
4243
import java.util.Map;
43-
import java.util.concurrent.Callable;
4444
import java.util.concurrent.ConcurrentHashMap;
4545
import java.util.concurrent.atomic.AtomicInteger;
4646

@@ -1152,8 +1152,9 @@ private Routine getRoutine(IStatus status, IExternalContext context, IRoutineMet
11521152
String className = entryPoint.substring(0, methodStart - 1).trim();
11531153
String methodName = entryPoint.substring(methodStart, paramsStart).trim();
11541154

1155-
Class<?> clazz = runInClassLoader(context.getUserName(),
1155+
Class<?> clazz = runInClassLoader(context.getUserName(), className, methodName,
11561156
() -> Class.forName(className, true, sharedData.classLoader));
1157+
11571158
Routine routine = new Routine(this);
11581159
ArrayList<Class<?>> paramTypes = new ArrayList<>();
11591160

@@ -1378,7 +1379,8 @@ private char peekChar(String s, int[] pos) throws FbException
13781379
return s.charAt(pos[0]);
13791380
}
13801381

1381-
<T> T runInClassLoader(String userName, Callable<T> callable) throws Exception
1382+
<T> T runInClassLoader(String userName, String className, String methodName, CallableThrowable<T> callable)
1383+
throws Throwable
13821384
{
13831385
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
13841386
try
@@ -1393,13 +1395,71 @@ <T> T runInClassLoader(String userName, Callable<T> callable) throws Exception
13931395

13941396
AccessControlContext acc = new AccessControlContext(protectionDomains);
13951397

1396-
return Subject.doAsPrivileged(subj, new PrivilegedExceptionAction<T>() {
1397-
@Override
1398-
public T run() throws Exception
1398+
try
1399+
{
1400+
return Subject.doAsPrivileged(subj, new PrivilegedExceptionAction<T>() {
1401+
@Override
1402+
public T run() throws Exception
1403+
{
1404+
try
1405+
{
1406+
return callable.call();
1407+
}
1408+
catch (Throwable t)
1409+
{
1410+
// We cannot pass a Throwable with PrivilegedExceptionAction, so we enclose it with an
1411+
// Exception. This is the reason we call getClause() two times below.
1412+
throw new Exception(t);
1413+
}
1414+
}
1415+
}, acc);
1416+
}
1417+
catch (PrivilegedActionException privilegedException)
1418+
{
1419+
// Lets filter the stack trace to remove garbage from user POV. We start removing the
1420+
// privilegedException trace, then we remove up to the user class and method name.
1421+
// From all frames.
1422+
1423+
Throwable userException = privilegedException.getCause().getCause(); // explained above
1424+
StackTraceElement[] privilegedTrace = privilegedException.getStackTrace();
1425+
1426+
for (Throwable currentException = userException;
1427+
currentException != null;
1428+
currentException = currentException.getCause())
13991429
{
1400-
return callable.call();
1430+
StackTraceElement[] currentTrace = currentException.getStackTrace();
1431+
1432+
for (int i = currentTrace.length - 1, j = privilegedTrace.length - 1; i >= 0; --i)
1433+
{
1434+
if (!currentTrace[i].equals(privilegedTrace[j]))
1435+
break;
1436+
1437+
if (j-- == 0)
1438+
{
1439+
int k = i;
1440+
1441+
while (--k >= 0 &&
1442+
!(currentTrace[k].getClassName().equals(className) &&
1443+
(currentTrace[k].getMethodName().equals(methodName) ||
1444+
"<clinit>".equals(currentTrace[k].getMethodName()))))
1445+
{
1446+
}
1447+
1448+
if (k < 0 &&
1449+
!(currentException instanceof ExceptionInInitializerError ||
1450+
currentException instanceof NoClassDefFoundError))
1451+
{
1452+
k = i - 1;
1453+
}
1454+
1455+
currentException.setStackTrace(Arrays.copyOf(currentTrace, k + 1));
1456+
break;
1457+
}
1458+
}
14011459
}
1402-
}, acc);
1460+
1461+
throw userException;
1462+
}
14031463
}
14041464
finally
14051465
{

src/fbjava/src/main/java/org/firebirdsql/fbjava/impl/ExternalProcedure.java

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ public IExternalResultSet open(IStatus status, IExternalContext context, Pointer
9393
{
9494
String userName = context.getUserName();
9595

96+
//// FIXME: Stack trace filtering is not working fully correct here.
97+
9698
class ExtResultSet implements IExternalResultSetIntf
9799
{
98100
private IExternalResultSet wrapper;
@@ -102,10 +104,12 @@ public void dispose()
102104
{
103105
try
104106
{
105-
routine.engine.runInClassLoader(userName, () -> {
106-
rs.close();
107-
return null;
108-
});
107+
routine.engine.runInClassLoader(userName,
108+
routine.method.getDeclaringClass().getName(), routine.method.getName(),
109+
() -> {
110+
rs.close();
111+
return null;
112+
});
109113
}
110114
catch (Throwable t)
111115
{
@@ -122,20 +126,22 @@ public boolean fetch(IStatus status) throws FbException
122126

123127
try
124128
{
125-
return routine.engine.runInClassLoader(userName, () -> {
126-
if (rs.fetch())
127-
{
128-
for (int i = inCount; i < inOut.length; ++i)
129-
inOut2[i] = Array.get(inOut[i], 0);
130-
131-
routine.putInMessage(status, context, routine.outputParameters,
132-
inOut2, inCount, outMsg);
133-
134-
return true;
135-
}
136-
else
137-
return false;
138-
});
129+
return routine.engine.runInClassLoader(userName,
130+
routine.method.getDeclaringClass().getName(), routine.method.getName(),
131+
() -> {
132+
if (rs.fetch())
133+
{
134+
for (int i = inCount; i < inOut.length; ++i)
135+
inOut2[i] = Array.get(inOut[i], 0);
136+
137+
routine.putInMessage(status, context, routine.outputParameters,
138+
inOut2, inCount, outMsg);
139+
140+
return true;
141+
}
142+
else
143+
return false;
144+
});
139145
}
140146
catch (Throwable t)
141147
{

src/fbjava/src/main/java/org/firebirdsql/fbjava/impl/FbException.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
*/
1919
package org.firebirdsql.fbjava.impl;
2020

21+
import java.io.PrintWriter;
22+
import java.io.StringWriter;
23+
2124
import org.firebirdsql.fbjava.impl.FbClientLibrary.IStatus;
2225
import org.firebirdsql.gds.ISCConstants;
2326

@@ -38,19 +41,25 @@ public FbException(String msg)
3841
super(msg);
3942
}
4043

41-
public static void rethrow(Throwable t) throws FbException
44+
public FbException(String msg, Throwable t)
4245
{
43-
t.printStackTrace(System.out); //// FIXME:
46+
super(msg, t);
47+
}
4448

45-
if (t instanceof FbException)
46-
throw (FbException) t;
47-
else
48-
throw new FbException(t);
49+
public static void rethrow(Throwable t) throws FbException
50+
{
51+
throw new FbException(null, t);
4952
}
5053

5154
public static void catchException(IStatus status, Throwable t)
5255
{
53-
String msg = t.getMessage();
56+
while (t != null && t instanceof FbException && t.getMessage() == null)
57+
t = t.getCause();
58+
59+
StringWriter sw = new StringWriter();
60+
PrintWriter pw = new PrintWriter(sw);
61+
t.printStackTrace(pw);
62+
String msg = sw.toString();
5463

5564
try (CloseableMemory memory = new CloseableMemory(msg.length() + 1))
5665
{

src/fbjava/src/main/java/org/firebirdsql/fbjava/impl/Routine.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,18 @@ void putInMessage(IStatus status, IExternalContext context, List<Parameter> para
112112
}
113113
}
114114

115-
Object run(IExternalContext context, Object[] args) throws Exception
115+
Object run(IExternalContext context, Object[] args) throws Throwable
116116
{
117-
return engine.runInClassLoader(context.getUserName(), () -> method.invoke(null, args));
117+
return engine.runInClassLoader(context.getUserName(), method.getDeclaringClass().getName(), method.getName(),
118+
() -> {
119+
try
120+
{
121+
return method.invoke(null, args);
122+
}
123+
catch (Exception | ExceptionInInitializerError t)
124+
{
125+
throw t.getCause();
126+
}
127+
});
118128
}
119129
}

0 commit comments

Comments
 (0)