Skip to content

The custom edges added by solver.addCallEdge() are not present in the call graph.ย #211

@YunFy26

Description

@YunFy26

๐Ÿ“ 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().

Image

The analysis successfully enters DefaultSolver and in processCallEdge, callGraph.addEdge(edge) returns true

Image

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 edges

But 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

Please ignore the related log printouts and taint analysis information...

โ„น๏ธ Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions