-
Notifications
You must be signed in to change notification settings - Fork 190
Description
๐ Overall Description
I'm using Tai-e to analyze Spring AOP functionality.
@Controller
public class UserController {
@Autowired
private OrderService orderService;
@RequestMapping("/createOrder")
public void createOrder() {
orderService.createOrder("1");
}
}
@Service
public class OrderService {
public void createOrder(String orderId) {
return;
}
}
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* org.example.springtarget.example.OrderService.createOrder(..))")
public void executionPointcut() {}
@Before("executionPointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
int abs = Math.abs(1);
System.out.println(abs);
System.out.println("Before: " + joinPoint.getSignature().getName());
}
@After("executionPointcut()")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("After: " + joinPoint.getSignature().getName());
}
}Spring dynamically creates proxy classes at runtime to intercept the createOrder()method, resulting in the actual call sequence: beforeAdvice() -> createOrder() -> afterAdvice().
I've implemented a plugin to analyze this call sequence:
public class AspectPlugin implements Plugin {
private static final Logger logger = LogManager.getLogger(AspectPlugin.class);
private Solver solver;
private final List<AspectClass> aspects = World.get().getResult(AspectAnalysis.ID);
private final Map<JMethod, Boolean> processedMethods = new HashMap<>();
@Override
public void setSolver(Solver solver) {
this.solver = solver;
}
@Override
public void onNewCSMethod(CSMethod csMethod) {
JMethod targetMethod = csMethod.getMethod();
if (processedMethods.containsKey(targetMethod)) {
return;
}
processedMethods.put(targetMethod, true);
for (AspectClass aspect : aspects) {
weaveAspectForMethod(csMethod, aspect);
}
}
private void weaveAspectForMethod(CSMethod csMethod, AspectClass aspect) {
JMethod currMethod = csMethod.getMethod();
Context context = csMethod.getContext();
CSManager csManager = solver.getCSManager();
for (var entry : aspect.getPointcutMethodMap().entrySet()) {
Pointcut pointcut = entry.getKey();
List<AspectMethod> aspectMethods = entry.getValue();
if (!PointcutMatcher.matches(pointcut, currMethod, aspect)) {
continue;
}
for (AspectMethod aspectMethod : aspectMethods) {
weaveAdvice(csMethod, aspectMethod, aspect, context, csManager);
}
}
}
private void weaveAdvice(CSMethod csTargetMethod, AspectMethod aspectMethod,
AspectClass aspect, Context context, CSManager csManager) {
JMethod adviceMethod = aspectMethod.getMethod();
CSCallSite virtualCallSite = createVirtualCallSite(csTargetMethod, aspectMethod, context, csManager);
Context adviceContext = solver.getContextSelector().selectContext(virtualCallSite, adviceMethod);
CSMethod csAdviceMethod = csManager.getCSMethod(adviceContext, adviceMethod);
AOPEdge aopEdge = new AOPEdge(virtualCallSite, csAdviceMethod, aspectMethod.getAdviceType());
solver.addCallEdge(aopEdge);
}
...
}In the weaveAdvice()method, I create AOPEdge and add it using solver.addCallEdge().
The analysis successfully enters DefaultSolver and in processCallEdge, callGraph.addEdge(edge) returns true
However, in the final call graph output, only the calls inside beforeAdvice():
<org.example.springtarget.example.LogAspect: void beforeAdvice(org.aspectj.lang.JoinPoint)>/java.lang.Math.abs/0 <java.lang.Math: int abs(int)>
<org.example.springtarget.example.UserController: void createOrder()>/org.example.springtarget.example.OrderService.createOrder/0 <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)>
// ... other edgesBut the expected AOP edges like:
[AOP-AFTER][DependencyInjectedBean{alloc=<org.example.springtarget.example.UserController: void createOrder()>:orderService,type=org.example.springtarget.example.OrderService}]:[AOP:AFTER] virtual invoke void afterAdvice(org.aspectj.lang.JoinPoint) from <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> -> [DependencyInjectedBean{alloc=<org.example.springtarget.example.UserController: void createOrder()>:orderService,type=org.example.springtarget.example.OrderService}]:<org.example.springtarget.example.LogAspect: void afterAdvice(org.aspectj.lang.JoinPoint)>are not present in the output. It seems like callGraph.addEdge(edge) is not working properly for AOPEdges?
๐ฏ Expected Behavior
Edges like these should be included in the call graph.
[AOP-AFTER][DependencyInjectedBean{alloc=<org.example.springtarget.example.UserController: void createOrder()>:orderService,type=org.example.springtarget.example.OrderService}]:[AOP:AFTER] virtual invoke void afterAdvice(org.aspectj.lang.JoinPoint) from <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> -> [DependencyInjectedBean{alloc=<org.example.springtarget.example.UserController: void createOrder()>:orderService,type=org.example.springtarget.example.OrderService}]:<org.example.springtarget.example.LogAspect: void afterAdvice(org.aspectj.lang.JoinPoint)>๐ Current Behavior
Edges like these are not included in the call graph.
๐ Reproducible Example
No response
โ๏ธ Tai-e Arguments
๐ Click here to see Tai-e Options
optionsFile: null
printHelp: false
classPath:
- ../SpringTarget
appClassPath:
- ../SpringTarget/target/classes
mainClass: null
inputClasses: []
javaVersion: 17
prependJVM: true
allowPhantom: true
worldBuilderClass: pascal.taie.frontend.soot.SootWorldBuilder
outputDir: output
preBuildIR: false
worldCacheMode: false
scope: APP
nativeModel: true
planFile: null
analyses:
ir-dumper: ;
routerAnalysis: ""
beanAnalysis: ""
injectPointsAnalysis: ""
aspectAnalysis: ""
cg: ""
pta: "plugins:[org.example.spring.plugin.ProcessDIPlugin, org.example.spring.plugin.ProcessEntryPlugin, org.example.spring.plugin.aop.AspectPlugin]"
onlyGenPlan: false
keepResult:
- $KEEP-ALL
๐ Click here to see Tai-e Analysis Plan
- id: ir-dumper
options: {}
- id: routerAnalysis
options: {}
- id: beanAnalysis
options: {}
- id: injectPointsAnalysis
options: {}
- id: aspectAnalysis
options: {}
- id: pta
options:
cs: 1-obj
only-app: true
implicit-entries: false
distinguish-string-constants: reflection
merge-string-objects: true
merge-string-builders: true
merge-exception-objects: true
handle-invokedynamic: true
propagate-types:
- reference
advanced: null
dump: false
dump-ci: false
dump-yaml: false
expected-file: null
reflection-inference: string-constant
reflection-log: null
taint-config: /Users/yuntsy/My/Projects/Java/WebAnalyzer/configs/taint-config.yml
taint-config-providers: []
taint-interactive-mode: false
plugins:
- org.example.spring.plugin.ProcessDIPlugin
- org.example.spring.plugin.ProcessEntryPlugin
- org.example.spring.plugin.aop.AspectPlugin
time-limit: -1
- id: cg
options:
algorithm: pta
dump: true
dump-methods: true
dump-call-edges: true
๐ Tai-e Log
๐ Click here to see Tai-e Log
Writing log to /Users/yuntsy/My/Projects/Java/WebAnalyzer/output/tai-e.log
java.version: 17.0.11
java.version.date: 2024-04-16
java.runtime.version: 17.0.11+7-LTS-207
java.vendor: Oracle Corporation
java.vendor.version: null
os.name: Mac OS X
os.version: 26.1
os.arch: aarch64
Tai-e Version: 0.5.2-SNAPSHOT
Tai-e Commit: 3f3b802611bf6b8187abc1785e6c5d08a4cf5a4a
Writing analysis plan to /Users/yuntsy/My/Projects/Java/WebAnalyzer/output/tai-e-plan.yml
WorldBuilder starts ...
Warning: main class was not given!
10231 classes with 100939 methods in the world
WorldBuilder finishes, elapsed time: 1.93s
ir-dumper starts ...
Dumping IR in /Users/yuntsy/My/Projects/Java/WebAnalyzer/output/tir
28 classes in scope (APP) of class analyses
ir-dumper finishes, elapsed time: 0.03s
routerAnalysis starts ...
Router analysis started at: 2025-11-11 20:03:31
Router analysis completed at: 2025-11-11 20:03:31 (Duration: 4ms)
routerAnalysis finishes, elapsed time: 0.00s
beanAnalysis starts ...
beanAnalysis finishes, elapsed time: 0.00s
injectPointsAnalysis starts ...
injectPointsAnalysis finishes, elapsed time: 0.00s
aspectAnalysis starts ...
Starting AOP aspect analysis...
aspectAnalysis finishes, elapsed time: 0.00s
pta starts ...
Loading taint config from /Users/yuntsy/My/Projects/Java/WebAnalyzer/configs/taint-config.yml
Cannot find source method '<javax.servlet.ServletRequestWrapper: java.lang.String getParameter(java.lang.String)>'
Cannot find source method '<org.example.springtest.CommandExecClass: java.lang.String index(jakarta.servlet.http.HttpServletRequest)>'
Cannot find sink method '<org.springframework.web.client.RestTemplate: org.springframework.http.ResponseEntity exchange(java.lang.String,org.springframework.http.HttpMethod,org.springframework.http.HttpEntity,java.lang.Class,java.lang.Object[])>'
Cannot find sink method '<org.example.springtest.FieldInjection: java.lang.String sayInjection()>'
TaintConfig:
sinks:
- { method: "<java.sql.Statement: java.sql.ResultSet executeQuery(java.lang.String)>", index: "0" }
- { method: "<java.sql.Connection: java.sql.PreparedStatement prepareStatement(java.lang.String)>", index: "0" }
transfers:
- { method: "<java.lang.String: java.lang.String concat(java.lang.String)>", from: "base", to: "result", type: "java.lang.String" }
- { method: "<java.lang.String: java.lang.String concat(java.lang.String)>", from: "0", to: "result", type: "java.lang.String" }
- { method: "<java.lang.String: char[] toCharArray()>", from: "base", to: "result", type: "char[]" }
- { method: "<java.lang.String: void <init>(char[])>", from: "0", to: "base", type: "java.lang.String" }
- { method: "<java.lang.String: void getChars(int,int,char[],int)>", from: "base", to: "2", type: "char[]" }
- { method: "<java.lang.String: java.lang.String format(java.lang.String,java.lang.Object[])>", from: "1[*]", to: "result", type: "java.lang.String" }
- { method: "<java.lang.StringBuffer: void <init>(java.lang.String)>", from: "0", to: "base", type: "java.lang.StringBuffer" }
- { method: "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.String)>", from: "0", to: "base", type: "java.lang.StringBuffer" }
- { method: "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.String)>", from: "0", to: "result", type: "java.lang.StringBuffer" }
- { method: "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.String)>", from: "base", to: "result", type: "java.lang.StringBuffer" }
- { method: "<java.lang.StringBuffer: java.lang.String toString()>", from: "base", to: "result", type: "java.lang.String" }
- { method: "<java.lang.StringBuilder: void <init>(java.lang.String)>", from: "0", to: "base", type: "java.lang.StringBuilder" }
- { method: "<java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.String)>", from: "0", to: "base", type: "java.lang.StringBuilder" }
- { method: "<java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.String)>", from: "0", to: "result", type: "java.lang.StringBuilder" }
- { method: "<java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.String)>", from: "base", to: "result", type: "java.lang.StringBuilder" }
- { method: "<java.lang.StringBuilder: java.lang.String toString()>", from: "base", to: "result", type: "java.lang.String" }
callSiteMode: true
Add entry point (empty params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String requestMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String defaultMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String getMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String postMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String putMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String deleteMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String patchMapping()>
Added entry point (with params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String pathVariable(java.lang.Long)>
Added entry point (with params): <org.example.springtarget.di.entry.controller.RestControllerEntry: java.lang.String requestParameter(java.lang.String)>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.ControllerEntry: java.lang.String requestMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.ControllerEntry: java.lang.String getMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.ControllerEntry: java.lang.String postMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.ControllerEntry: java.lang.String putMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.ControllerEntry: java.lang.String deleteMapping()>
Add entry point (empty params): <org.example.springtarget.di.entry.controller.ControllerEntry: java.lang.String patchMapping()>
Added entry point (with params): <org.example.springtarget.di.entry.controller.ControllerEntry: java.lang.String pathVariable(java.lang.Long)>
Added entry point (with params): <org.example.springtarget.di.entry.controller.ControllerEntry: java.lang.String requestParameter(java.lang.String)>
Add entry point (empty params): <org.example.springtarget.example.UserController: void createOrder()>
Added entry point (with params): <org.example.springtarget.example.UserController: void getUser(jakarta.servlet.http.HttpServletRequest)>
Add entry point (empty params): <org.example.springtarget.example.UserController: void getUserAndId()>
Add entry point (empty params): <org.example.springtarget.example.UserController: void getServiceBeanMessage()>
ๆนๆณ <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> ๅน้
ๅ็น: bean(orderService)
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: AROUND -> ็ฎๆ ๆนๆณ: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)>
ๆนๆณ <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> ๅน้
ๅ็น: @within(org.springframework.stereotype.Service)
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: AFTER -> ็ฎๆ ๆนๆณ: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)>
ๆนๆณ <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> ๅน้
ๅ็น: execution(* org.example.springtarget.example.OrderService.createOrder(..))
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: BEFORE -> ็ฎๆ ๆนๆณ: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)>
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: AFTER_RETURNING -> ็ฎๆ ๆนๆณ: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)>
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: AFTER_THROWING -> ็ฎๆ ๆนๆณ: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)>
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> -> <org.example.springtarget.example.LogAspect: java.lang.Object aroundAdvice(org.aspectj.lang.ProceedingJoinPoint)>, ้็ฅ็ฑปๅ: AROUND
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> -> <org.example.springtarget.example.LogAspect: void afterAdvice(org.aspectj.lang.JoinPoint)>, ้็ฅ็ฑปๅ: AFTER
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> -> <org.example.springtarget.example.LogAspect: void beforeAdvice(org.aspectj.lang.JoinPoint)>, ้็ฅ็ฑปๅ: BEFORE
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> -> <org.example.springtarget.example.LogAspect: void afterReturningAdvice(java.lang.Object)>, ้็ฅ็ฑปๅ: AFTER_RETURNING
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.example.OrderService: void createOrder(java.lang.String)> -> <org.example.springtarget.example.LogAspect: void afterThrowingAdvice(java.lang.Exception)>, ้็ฅ็ฑปๅ: AFTER_THROWING
ๆนๆณ <org.example.springtarget.example.UserServiceImpl: java.lang.String getUser()> ๅน้
ๅ็น: @within(org.springframework.stereotype.Service)
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: AFTER -> ็ฎๆ ๆนๆณ: <org.example.springtarget.example.UserServiceImpl: java.lang.String getUser()>
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.example.UserServiceImpl: java.lang.String getUser()> -> <org.example.springtarget.example.LogAspect: void afterAdvice(org.aspectj.lang.JoinPoint)>, ้็ฅ็ฑปๅ: AFTER
ๆนๆณ <org.example.springtarget.example.UserServiceAbstractImpl: java.lang.String getUser()> ๅน้
ๅ็น: @within(org.springframework.stereotype.Service)
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: AFTER -> ็ฎๆ ๆนๆณ: <org.example.springtarget.example.UserServiceAbstractImpl: java.lang.String getUser()>
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.example.UserServiceAbstractImpl: java.lang.String getUser()> -> <org.example.springtarget.example.LogAspect: void afterAdvice(org.aspectj.lang.JoinPoint)>, ้็ฅ็ฑปๅ: AFTER
ๆนๆณ <org.example.springtarget.example.UserServiceImpl: java.lang.String getId()> ๅน้
ๅ็น: @within(org.springframework.stereotype.Service)
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: AFTER -> ็ฎๆ ๆนๆณ: <org.example.springtarget.example.UserServiceImpl: java.lang.String getId()>
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.example.UserServiceImpl: java.lang.String getId()> -> <org.example.springtarget.example.LogAspect: void afterAdvice(org.aspectj.lang.JoinPoint)>, ้็ฅ็ฑปๅ: AFTER
ๆนๆณ <org.example.springtarget.di.beans.ServiceBeanImpl2: java.lang.String getMessage()> ๅน้
ๅ็น: @within(org.springframework.stereotype.Service)
็ปๅ
ฅๅ้ข: org.example.springtarget.example.LogAspect -> ้็ฅ็ฑปๅ: AFTER -> ็ฎๆ ๆนๆณ: <org.example.springtarget.di.beans.ServiceBeanImpl2: java.lang.String getMessage()>
โ ๅค็ AOP ่ฐ็จ่พน: <org.example.springtarget.di.beans.ServiceBeanImpl2: java.lang.String getMessage()> -> <org.example.springtarget.example.LogAspect: void afterAdvice(org.aspectj.lang.JoinPoint)>, ้็ฅ็ฑปๅ: AFTER
[Pointer analysis] elapsed time: 0.02s
Detected 0 taint flow(s):
TFGDumper starts ...
Source nodes:
Sink nodes:
Dumping /Users/yuntsy/My/Projects/Java/WebAnalyzer/output/taint-flow-graph.dot
TFGDumper finishes, elapsed time: 0.00s
-------------- Pointer analysis statistics: --------------
#var pointers: 71 (insens) / 91 (sens)
#objects: 27 (insens) / 27 (sens)
#var points-to: 54 (insens) / 60 (sens)
#static field points-to: 0 (sens)
#instance field points-to: 8 (sens)
#array points-to: 0 (sens)
#reachable methods: 39 (insens) / 45 (sens)
#call graph edges: 11 (insens) / 11 (sens)
----------------------------------------
1
pta finishes, elapsed time: 0.13s
cg starts ...
Call graph has 39 reachable methods and 11 edges
Dumping call graph to /Users/yuntsy/My/Projects/Java/WebAnalyzer/output/call-graph.dot
Dumping reachable methods to /Users/yuntsy/My/Projects/Java/WebAnalyzer/output/reachable-methods.txt
Dumping call edges to /Users/yuntsy/My/Projects/Java/WebAnalyzer/output/call-edges.txt
cg finishes, elapsed time: 0.01s
Tai-e finishes, elapsed time: 2.19s
โน๏ธ Additional Information
No response