Javasist 在检测 org.h2.jdbc.JdbcPreparedStatement 的 setString 方法时抛出 javassist.CannotCompileException

Posted

技术标签:

【中文标题】Javasist 在检测 org.h2.jdbc.JdbcPreparedStatement 的 setString 方法时抛出 javassist.CannotCompileException【英文标题】:Javasist throws javassist.CannotCompileException when instrumenting setString method of org.h2.jdbc.JdbcPreparedStatement 【发布时间】:2015-10-13 12:30:29 【问题描述】:

嗯,在服务器运行时尝试检测 jdbc 方法。我已经通过检测 setString、setInt 方法和 executeQuery 方法进行了尝试,同时运行了一个简单的 mysql 查询,正如 JDBC 示例中给出的那样。当我通过注入以下行来检测 setString 方法时,它工作得很好。

private void injectSetVariableMethods(CtMethod method) 
        if (isInEnum(method.getName().toUpperCase(), SetMethods.class)) 
            try 
                method.insertAt(1, true,
                        "javaagent.JDBCPublisher.fillArrayList(String.valueOf($2), " +
                        "Thread.currentThread().getStackTrace()[1].getMethodName().toUpperCase());"
                );
             catch (CannotCompileException e) 
                e.printStackTrace();
            
        
    

但是现在,当我在使用 h2 的服务器上运行它时,它给出了以下异常。

javassist.CannotCompileException: by javassist.bytecode.BadBytecode: setString (ILjava/lang/String;)V in org.h2.jdbc.JdbcPreparedStatement: failed to resolve types
    at javassist.CtBehavior.insertAt(CtBehavior.java:1210)
    at javaagent.JDBCTransformer.injectSetVariableMethods(JDBCClassTransformer.java:212)
    at javaagent.JDBCTransformer.transform(JDBCClassTransformer.java:99)
    at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
    at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:424)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
    at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.defineClass(DefaultClassLoader.java:188)
    at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.defineClassHoldingLock(ClasspathManager.java:638)
    at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.defineClass(ClasspathManager.java:613)
    at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.findClassImpl(ClasspathManager.java:574)
    at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.findLocalClassImpl(ClasspathManager.java:492)
    at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.findLocalClass(ClasspathManager.java:465)
    at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.findLocalClass(DefaultClassLoader.java:216)
    at org.eclipse.osgi.internal.loader.BundleLoader.findLocalClass(BundleLoader.java:395)
    at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:464)
    at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:421)
    at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:412)
    at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:234)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:126)
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109)
    at org.carbon.ndatasource.rdbms.ConnectionRollbackOnReturnInterceptor.invoke(ConnectionRollbackOnReturnInterceptor.java:51)
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109)
    at org.apache.tomcat.jdbc.pool.interceptor.AbstractCreateStatementInterceptor.invoke(AbstractCreateStatementInterceptor.java:67)
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109)
    at org.apache.tomcat.jdbc.pool.interceptor.ConnectionState.invoke(ConnectionState.java:153)
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109)
    at org.apache.tomcat.jdbc.pool.TrapException.invoke(TrapException.java:41)
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109)
    at org.apache.tomcat.jdbc.pool.DisposableConnectionFacade.invoke(DisposableConnectionFacade.java:80)
    at com.sun.proxy.$Proxy18.prepareStatement(Unknown Source)
    at org.carbon.user.core.claim.dao.ClaimDAO.getDialectCount(ClaimDAO.java:160)
    at org.carbon.user.core.common.DefaultRealm.populateProfileAndClaimMaps(DefaultRealm.java:429)
    at org.carbon.user.core.common.DefaultRealm.init(DefaultRealm.java:105)
    at org.carbon.user.core.common.DefaultRealmService.initializeRealm(DefaultRealmService.java:230)
    at org.wso2.carbon.user.core.common.DefaultRealmService.<init>(DefaultRealmService.java:96)
    at org.wso2.carbon.user.core.common.DefaultRealmService.<init>(DefaultRealmService.java:109)
    at org.carbon.user.core.internal.Activator.startDeploy(Activator.java:68)
    at org.wso2.carbon.user.core.internal.BundleCheckActivator.start(BundleCheckActivator.java:61)
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl$1.run(BundleContextImpl.java:711)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:702)
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(BundleContextImpl.java:683)
    at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:381)
    at org.eclipse.osgi.framework.internal.core.AbstractBundle.resume(AbstractBundle.java:390)
    at org.eclipse.osgi.framework.internal.core.Framework.resumeBundle(Framework.java:1176)
    at org.eclipse.osgi.framework.internal.core.StartLevelManager.resumeBundles(StartLevelManager.java:559)
    at org.eclipse.osgi.framework.internal.core.StartLevelManager.resumeBundles(StartLevelManager.java:544)
    at org.eclipse.osgi.framework.internal.core.StartLevelManager.incFWSL(StartLevelManager.java:457)
    at org.eclipse.osgi.framework.internal.core.StartLevelManager.doSetStartLevel(StartLevelManager.java:243)
    at org.eclipse.osgi.framework.internal.core.StartLevelManager.dispatchEvent(StartLevelManager.java:438)
    at org.eclipse.osgi.framework.internal.core.StartLevelManager.dispatchEvent(StartLevelManager.java:1)
    at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)
    at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.run(EventManager.java:340)
Caused by: javassist.bytecode.BadBytecode: setString (ILjava/lang/String;)V in org.h2.jdbc.JdbcPreparedStatement: failed to resolve types
    at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:111)
    at javassist.bytecode.MethodInfo.rebuildStackMap(MethodInfo.java:423)
    at javassist.bytecode.MethodInfo.rebuildStackMapIf6(MethodInfo.java:405)
    at javassist.CtBehavior.insertAt(CtBehavior.java:1200)
    ... 59 more
Caused by: javassist.bytecode.BadBytecode: failed to resolve types
    at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:169)
    at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:108)
    ... 62 more
Caused by: javassist.NotFoundException: org.h2.value.ValueNull
    at javassist.ClassPool.get(ClassPool.java:450)
    at javassist.bytecode.stackmap.TypeData$TypeVar.fixTypes2(TypeData.java:345)
    at javassist.bytecode.stackmap.TypeData$TypeVar.fixTypes(TypeData.java:330)
    at javassist.bytecode.stackmap.TypeData$TypeVar.dfs(TypeData.java:274)
    at javassist.bytecode.stackmap.MapMaker.fixTypes(MapMaker.java:394)
    at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:167)
    ... 63 more

我对 fillArrayList 方法所做的是,嗯,通过检查方法名称并为使用 setString 设置的值添加 '' 将这些值传递到 ArrayList。但看起来它在某个时候正在检测该方法,因为我正在使用“?”获得重新实现的查询。替换为相应的值(正常情况下带有 '' 和 int 的字符串)。但是一旦服务器启动,它就会抛出另一组同样涉及 h2 的异常。

    [2015-10-13 17:18:56,600] ERROR org.carbon.registry.core.jdbc.dao.JDBCLogsDAO -  Failed to get logs. General error: "java.lang.IndexOutOfBoundsException: Index: 1, Size: 1" [50000-140]
org.h2.jdbc.JdbcSQLException: General error: "java.lang.IndexOutOfBoundsException: Index: 1, Size: 1" [50000-140]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:327)
    at org.h2.message.DbException.get(DbException.java:156)
    at org.h2.message.DbException.convert(DbException.java:279)
    at org.h2.message.DbException.toSQLException(DbException.java:252)
    at org.h2.message.TraceObject.logAndConvert(TraceObject.java:386)
    at org.h2.jdbc.JdbcPreparedStatement.executeQuery(JdbcPreparedStatement.java:104)
    at org.carbon.registry.core.jdbc.dao.JDBCLogsDAO.internalGetLogs(JDBCLogsDAO.java:427)
    at org.carbon.registry.core.jdbc.dao.JDBCLogsDAO.getLogList(JDBCLogsDAO.java:317)
    at org.carbon.registry.core.jdbc.EmbeddedRegistry.getLogs(EmbeddedRegistry.java:2332)
    at org.carbon.registry.core.caching.CacheBackedRegistry.getLogs(CacheBackedRegistry.java:402)
    at org.carbon.registry.core.session.UserRegistry.getLogsInternal(UserRegistry.java:1806)
    at org.carbon.registry.core.session.UserRegistry.access$3600(UserRegistry.java:60)
    at org.carbon.registry.core.session.UserRegistry$37.run(UserRegistry.java:1777)
    at org.carbon.registry.core.session.UserRegistry$37.run(UserRegistry.java:1774)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.carbon.registry.core.session.UserRegistry.getLogs(UserRegistry.java:1774)
    at org.carbon.registry.indexing.ResourceSubmitter.submitResource(ResourceSubmitter.java:119)
    at org.carbon.registry.indexing.ResourceSubmitter.run(ResourceSubmitter.java:76)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:304)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
    at java.util.ArrayList.rangeCheck(ArrayList.java:635)
    at java.util.ArrayList.get(ArrayList.java:411)
    at javaagent.JDBCPublisher.getArrayList(JDBCAgentPublisher.java:151)
    at javaagent.JDBCPublisher.modifyOriginalQuery(JDBCAgentPublisher.java:351)
    at org.h2.jdbc.JdbcPreparedStatement.executeQuery(JdbcPreparedStatement.java:84)
    ... 19 more
[2015-10-13 17:18:56,601]  WARN org.carbon.registry.indexing.ResourceSubmitter -  An error occurred while submitting resources for indexing
org.carbon.registry.core.exceptions.RegistryException: Failed to get logs. General error: "java.lang.IndexOutOfBoundsException: Index: 1, Size: 1" [50000-140]
    at org.carbon.registry.core.jdbc.dao.JDBCLogsDAO.internalGetLogs(JDBCLogsDAO.java:465)
    at org.carbon.registry.core.jdbc.dao.JDBCLogsDAO.getLogList(JDBCLogsDAO.java:317)
    at org.carbon.registry.core.jdbc.EmbeddedRegistry.getLogs(EmbeddedRegistry.java:2332)
    at org.carbon.registry.core.caching.CacheBackedRegistry.getLogs(CacheBackedRegistry.java:402)
    at org.carbon.registry.core.session.UserRegistry.getLogsInternal(UserRegistry.java:1806)
    at org.carbon.registry.core.session.UserRegistry.access$3600(UserRegistry.java:60)
    at org.wso2.carbon.registry.core.session.UserRegistry$37.run(UserRegistry.java:1777)
    at org.carbon.registry.core.session.UserRegistry$37.run(UserRegistry.java:1774)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.carbon.registry.core.session.UserRegistry.getLogs(UserRegistry.java:1774)
    at org.carbon.registry.indexing.ResourceSubmitter.submitResource(ResourceSubmitter.java:119)
    at org.carbon.registry.indexing.ResourceSubmitter.run(ResourceSubmitter.java:76)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:304)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.h2.jdbc.JdbcSQLException: General error: "java.lang.IndexOutOfBoundsException: Index: 1, Size: 1" [50000-140]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:327)
    at org.h2.message.DbException.get(DbException.java:156)
    at org.h2.message.DbException.convert(DbException.java:279)
    at org.h2.message.DbException.toSQLException(DbException.java:252)
    at org.h2.message.TraceObject.logAndConvert(TraceObject.java:386)
    at org.h2.jdbc.JdbcPreparedStatement.executeQuery(JdbcPreparedStatement.java:104)
    at org.wso2.carbon.registry.core.jdbc.dao.JDBCLogsDAO.internalGetLogs(JDBCLogsDAO.java:427)
    ... 18 more
Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
    at java.util.ArrayList.rangeCheck(ArrayList.java:635)
    at java.util.ArrayList.get(ArrayList.java:411)
    at javaagent.JDBCPublisher.getArrayList(JDBCAgentPublisher.java:151)
    at javaagent.JDBCPublisher.modifyOriginalQuery(JDBCAgentPublisher.java:351)
    at org.h2.jdbc.JdbcPreparedStatement.executeQuery(JdbcPreparedStatement.java:84)
    ... 19 more

它使用正确分配的 sql 查询重复抛出一堆 IndexOutOfBoundExceptions。是什么造成了这个问题.....我应该怎么做才能纠正这个问题?

【问题讨论】:

根据日志,我猜getStackTrace() 返回的数组少于两个条目,因此您无法从索引[1] 中检索值。 但是无法编译异常的原因是什么?我认为当时出现问题会导致最后一个异常。 能否请您添加有关javaagent.JDBCTransformer.injectSetVariableMethods(JDBCClassTransformer.java:212) 和您在此处传递的对象injectSetVariableMethods(CtMethod method) 的更多信息。 那么传递到那里的对象就是它在每次迭代中经历的 CtMethod。关于方法,它只是在方法中添加一行。它将给定的两个值传递给 fillArrayList 方法,该方法会将这些值添加到 arrayList。 【参考方案1】:

在下面找到一个精简的示例,该示例打印每次调用方法 org.h2.jdbc.JdbcPreparedStatement.setString(int, String) 的值。

假设以下目录结构和内容:

./instrumented/
h2-1.4.186.jar
javassist-3.7.ga.jar
SetStringDemo.java

示例的执行

javac -cp javassist-3.7.ga.jar;h2-1.4.186.jar SetStringDemo.java
java -cp .;instrumented/;javassist-3.7.ga.jar;h2-1.4.186.jar SetStringDemo

输出

instrument class org.h2.jdbc.JdbcPreparedStatement
create test database and insert some rows         
idx: 2  value: 'name 0'                           
idx: 2  value: 'name 1'                           
idx: 2  value: 'name 2'                           
idx: 2  value: 'name 3'                           
idx: 2  value: 'name 4'                           

所以问题很可能出在您对班级进行检测的方式上。

用于示例的代码。

import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class SetStringDemo 

    // exception handling left out for the PoC
    public static void main(String[] args) throws Exception 

        if (Files.deleteIfExists(
                Paths.get("instrumented/org/h2/jdbc/JdbcPreparedStatement.class")
        )) 
            System.out.println("previously instrumented class removed");
        

        System.out.println("instrument class org.h2.jdbc.JdbcPreparedStatement");
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get("org.h2.jdbc.JdbcPreparedStatement");
        CtMethod method = clazz.getDeclaredMethod("setString");
        method.insertAt(1,
                "System.out.println(\"idx: \" + $1 + \"  value: '\" + $2 + \"'\");"
        );
        clazz.writeFile("instrumented/");

        System.out.println("create test database and insert some rows");
        try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:", "sa", "")) 
            String createTable = "CREATE TABLE TEST_TABLE(ID INT, NAME VARCHAR(1024))";
            conn.createStatement().executeUpdate(createTable);

            String insertSql = "INSERT INTO TEST_TABLE VALUES(?, ?)";
            try (PreparedStatement insertStmnt = conn.prepareStatement(insertSql)) 
                for (int i = 0; i < 5; i++) 
                    insertStmnt.setInt(1, i);
                    insertStmnt.setString(2, "name " + i);
                    insertStmnt.executeUpdate();
                
            
        
    

【讨论】:

但是这个检测文件夹是什么? @ransi 它仅用于存储检测类的 PoC。 1) 使 PoC 尽可能简单,2) 有可能检查仪器是否像预期的那样。我假设您正在使用代理来检测您的课程。对于这个 PoC 来说,这无关紧要。但会增加不必要的复杂性。

以上是关于Javasist 在检测 org.h2.jdbc.JdbcPreparedStatement 的 setString 方法时抛出 javassist.CannotCompileException的主要内容,如果未能解决你的问题,请参考以下文章

具有正确 DDL sql 的 H2 org.h2.jdbc.JdbcSQLException:错误代码 = [42000-196]

org.h2.jdbc.JdbcSQLException:在使用 H2 数据库进行测试期间未找到列“Id”

org.h2.jdbc.JdbcSQLSyntaxErrorException h2 数据库 java

引起:org.h2.jdbc.JdbcSQLException: 数据转换错误转换

org.h2.jdbc.JdbcSQLNonTransientException:对象已关闭 [90007-200]

为啥返回 org.h2.jdbc.JdbcSQLException?