Problem statement: I have an RPC client lib 1 that is used by many services with all code in a mono repo. This RPC client lib 1 throws a set of runtime exceptions (S1). Now there is another RPC client lib 2 that I try to migrate all services from using lib 1 to lib 2. However, RPC client lib 2 throws a different set of runtime exceptions (S2). I am trying to get a list of services that are going to be affected if the RPC client exception semantics change from S1 to S2.
Approach 1: read through code of all services that currently use RPC client lib 1. Obviously this approach is not scalable because there are many services and exception handling logic could be complex and not straightforward.
Approach 2: static analysis. However, I am not sure if any static analysis can detect the situation where an exception thrown by the RPC lib is caught at many levels above in the call stack.
Approach 3: explore if there is a way to register a callback to an exception and expect the callback to be invoked when the exception is caught. Ideally when the callback is invoked, it is provided with the information about where the exception is caught.
CodePudding user response:
Sounds like AspectJ pointcut "after catching" (not exising). Existing is "after throwing"
https://coderanch.com/t/498256/frameworks/AOP-pointcut-CATCH-block
https://www.eclipse.org/aspectj/doc/next/progguide/printable.html#the-handler-join-point
How to intercept method which handles its own exceptions using AspectJ
Spring AOP - Invoking advice from catch block
Maybe java agents/bytecode manipulation can be helpful
Using Instrumentation to record unhandled exception
You can consider to instrument catch blocks throughout the codebase with Scalameta
import scala.meta._
val transformer = new Transformer {
override def apply(tree: Tree): Tree = tree match {
case q"try $expr catch { ..case $cases } finally $expropt" =>
val cases1 = cases.map {
case p"case $pat if $expropt => $expr" =>
pat match {
case p"${pat: Pat.Var}: $tpe" =>
p"""case $pat: $tpe if $expropt =>
println("Exception " ${pat.name} " is caught")
$expr
"""
case p"$pat" =>
p"""case $pat if $expropt =>
println("Exception " ${pat.toString} " is caught")
$expr
"""
}
}
q"try $expr catch { ..case $cases1 } finally $expropt"
case _ => super.apply(tree)
}
}
transformer(q"""
try {
1 / 0
} catch {
case e: RuntimeException =>
println(e)
throw e
}
""")
//try {
// 1 / 0
//} catch {
// case e: RuntimeException =>
// println("Exception " e " is caught")
// {
// println(e)
// throw e
// }
//}
transformer(q"""
try {
1 / 0
} catch {
case _ =>
println("swallowed")
}
""")
//try {
// 1 / 0
//} catch {
// case _ =>
// println("Exception " "_" " is caught")
// println("swallowed")
//}
transformer(q"""
try {
1 / 0
} finally {
case _ =>
println("not catching")
}
""")
//try {
// 1 / 0
//} finally {
// case _ =>
// println("not catching")
//}
https://www.scala-sbt.org/1.x/docs/Howto-Generating-Files.html
Macro annotation to override toString of Scala function
How to merge multiple imports in scala?
For Java sources https://spoon.gforge.inria.fr can be a replacement for Scalameta.
CodePudding user response:
Solution 1 can work. If you are only worried about different semantics then it is sufficient to search in client code for occurrences of the exceptions defined by lib1. If client code catches a more generic exception (e.g. Exception or Throwable) then the precise semantics don't matter and you can leave that code alone.
Assumptions:
- Lib1's exceptions are specific and are not thrown by other code (e.g. not RuntimeException but Lib1Exception is okay).
- Lib1 and lib2 throw different exceptions but for the same reasons: if lib1 would throw an exception, lib2 would as well (and vice versa).