@@ -18,11 +18,12 @@ import path from "path";
18
18
import fg from "fast-glob" ;
19
19
import fs from "fs" ;
20
20
import log from "loglevel" ;
21
- import { spawn , spawnSync } from "node:child_process" ;
22
- import { JApplication } from "../../models/java" ;
23
- import { JApplicationType , JCompilationUnitType } from "../../models/java/types" ;
21
+ import { spawnSync } from "node:child_process" ;
22
+ import { JApplication } from "../../models/java" ;
23
+ import * as types from "../../models/java/types" ;
24
24
import os from "os" ;
25
25
import JSONStream from "JSONStream" ;
26
+ import crypto from "crypto" ;
26
27
27
28
enum AnalysisLevel {
28
29
SYMBOL_TABLE = "1" ,
@@ -38,8 +39,8 @@ const analysisLevelMap: Record<string, AnalysisLevel> = {
38
39
39
40
export class JavaAnalysis {
40
41
private readonly projectDir : string | null ;
41
- private readonly analysisLevel : AnalysisLevel ;
42
- application ?: JApplicationType ;
42
+ private analysisLevel : AnalysisLevel ;
43
+ application ?: types . JApplicationType ;
43
44
44
45
constructor ( options : { projectDir : string | null ; analysisLevel : string } ) {
45
46
this . projectDir = options . projectDir ;
@@ -64,26 +65,24 @@ export class JavaAnalysis {
64
65
/**
65
66
* Initialize the application by running the codeanalyzer and parsing the output.
66
67
* @private
67
- * @returns {Promise<JApplicationType> } A promise that resolves to the parsed application data
68
+ * @returns {Promise<types. JApplicationType> } A promise that resolves to the parsed application data
68
69
* @throws {Error } If the project directory is not specified or if codeanalyzer fails
69
70
*/
70
- private async _initialize_application ( ) : Promise < JApplicationType > {
71
- return new Promise < JApplicationType > ( ( resolve , reject ) => {
71
+ private async _initialize_application ( ) : Promise < types . JApplicationType > {
72
+ return new Promise < types . JApplicationType > ( ( resolve , reject ) => {
72
73
if ( ! this . projectDir ) {
73
74
return reject ( new Error ( "Project directory not specified" ) ) ;
74
75
}
75
76
76
77
const projectPath = path . resolve ( this . projectDir ) ;
77
- /**
78
- * I kept running into OOM issues when running the codeanalyzer output is piped to stream.
79
- * So I decided to write the output to a temporary file and then read the file.
80
- */
81
- // Create a temporary file to store the codeanalyzer output
82
- const crypto = require ( 'crypto' ) ;
78
+ // Create a temporary file to store the codeanalyzer output
83
79
const tmpFilePath = path . join ( os . tmpdir ( ) , `${ Date . now ( ) } -${ crypto . randomUUID ( ) } ` ) ;
84
80
const command = [ ...this . getCodeAnalyzerExec ( ) , "-i" , projectPath , '-o' , tmpFilePath , `--analysis-level=${ this . analysisLevel } ` , '--verbose' ] ;
81
+ // Check if command is valid
82
+ if ( ! command [ 0 ] ) {
83
+ return reject ( new Error ( "Codeanalyzer command not found" ) ) ;
84
+ }
85
85
log . debug ( command . join ( " " ) ) ;
86
-
87
86
const result = spawnSync ( command [ 0 ] , command . slice ( 1 ) , {
88
87
stdio : [ "ignore" , "pipe" , "inherit" ] ,
89
88
} ) ;
@@ -99,9 +98,9 @@ export class JavaAnalysis {
99
98
// Read the analysis result from the temporary file
100
99
try {
101
100
const stream = fs . createReadStream ( path . join ( tmpFilePath , 'analysis.json' ) ) . pipe ( JSONStream . parse ( ) ) ;
102
- const result = { } as JApplicationType ;
101
+ const result = { } as types . JApplicationType ;
103
102
104
- stream . on ( 'data' , ( data ) => {
103
+ stream . on ( 'data' , ( data : unknown ) => {
105
104
Object . assign ( result , JApplication . parse ( data ) ) ;
106
105
} ) ;
107
106
@@ -110,10 +109,10 @@ export class JavaAnalysis {
110
109
fs . rm ( tmpFilePath , { recursive : true , force : true } , ( err ) => {
111
110
if ( err ) log . warn ( `Failed to delete temporary file: ${ tmpFilePath } ` , err ) ;
112
111
} ) ;
113
- resolve ( result as JApplicationType ) ;
112
+ resolve ( result as types . JApplicationType ) ;
114
113
} ) ;
115
114
116
- stream . on ( 'error' , ( err ) => {
115
+ stream . on ( 'error' , ( err : any ) => {
117
116
reject ( err ) ;
118
117
} ) ;
119
118
} catch ( error ) {
@@ -135,17 +134,28 @@ export class JavaAnalysis {
135
134
*
136
135
* The application view denoted by this application structure is crucial for further fine-grained analysis APIs.
137
136
* If the application is not already initialized, it will be initialized first.
138
- * @returns {Promise<JApplicationType> } A promise that resolves to the application data
137
+ * @returns {Promise<types. JApplicationType> } A promise that resolves to the application data
139
138
*/
140
- public async getApplication ( ) : Promise < JApplicationType > {
139
+ public async getApplication ( ) : Promise < types . JApplicationType > {
141
140
if ( ! this . application ) {
142
141
this . application = await this . _initialize_application ( ) ;
143
142
}
144
143
return this . application ;
145
144
}
146
145
147
- public async getSymbolTable ( ) : Promise < Record < string , JCompilationUnitType > > {
146
+ public async getSymbolTable ( ) : Promise < Record < string , types . JCompilationUnitType > > {
148
147
return ( await this . getApplication ( ) ) . symbol_table ;
149
148
}
149
+
150
+ public async getCallGraph ( ) : Promise < JCallGraph > {
151
+ const application = await this . getApplication ( ) ;
152
+ if ( application . call_graph === undefined || application . call_graph === null ) {
153
+ log . debug ( "Re-initializing application with call graph" ) ;
154
+ this . analysisLevel = AnalysisLevel . CALL_GRAPH ;
155
+ this . application = await this . _initialize_application ( ) ;
156
+ }
157
+
158
+ }
159
+
150
160
}
151
161
0 commit comments