Skip to content

Commit cfd3cbd

Browse files
committed
Refactor library counting in package-dependencies tool for uniqueness
- Updated the PackageDependencyExtractor class to track unique classes for specified libraries using Sets instead of simple counts. - Enhanced the Markdown output to reflect counts of unique classes for each library, improving clarity on dependency analysis. - Revised README.md to describe the updated functionality regarding unique class counting. - Modified tests to validate the new unique counting logic and ensure accurate reporting of library dependencies.
1 parent 3fa3c63 commit cfd3cbd

File tree

3 files changed

+156
-83
lines changed

3 files changed

+156
-83
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The input JSONL files are typically generated from Jarviz-lib static analysis of
1616
- Generate statistics about dependencies
1717
- View the most depended-upon classes
1818
- Extract and analyze package dependencies
19-
- Count instances of specific libraries (struts, commons, log4j, cryptix) in dependencies, customizable via `--libraries` option
19+
- Count unique classes belonging to specific libraries (struts, commons, log4j, cryptix) found in dependencies, customizable via `--libraries` option
2020

2121
## Installation
2222

@@ -172,11 +172,11 @@ No cycles found.
172172

173173
The package dependencies extractor generates a Markdown file with the following sections:
174174

175-
1. **Specific Library Counts**: Tracks the number of dependencies where the targetClass contains specific libraries:
175+
1. **Specific Library Counts**: Tracks the number of unique classes depended upon where the `targetClass` contains specific library names:
176176
- By default: Struts, Commons, Log4j, Cryptix
177177
- Customizable via the `--libraries` option
178178

179-
This helps identify and quantify vulnerability exposure if any of these libraries have security issues.
179+
This helps identify and quantify vulnerability exposure by showing how many distinct classes from potentially vulnerable libraries are used.
180180

181181
2. **Base Packages**: A list of all base packages used by the project, grouped by:
182182
- External Dependencies (e.g., `java.lang`, `javax.servlet`)

package-dependencies.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ interface PackageInfo {
2626

2727
// Interface for tracking specific library counts
2828
interface LibraryCounts {
29-
[key: string]: number;
29+
[key: string]: Set<string>;
3030
}
3131

3232
class PackageDependencyExtractor {
@@ -46,7 +46,7 @@ class PackageDependencyExtractor {
4646

4747
// Initialize counter for each library
4848
this.librariesToCount.forEach(lib => {
49-
this.libraryCounts[lib] = 0;
49+
this.libraryCounts[lib] = new Set<string>();
5050
});
5151
}
5252

@@ -108,14 +108,15 @@ class PackageDependencyExtractor {
108108
this.countSpecificLibraries(targetClass);
109109
}
110110

111-
// Method to count instances of specific libraries
111+
// Method to count unique classes for specific libraries
112112
private countSpecificLibraries(targetClass: string): void {
113113
const lcTargetClass = targetClass.toLowerCase();
114114

115115
// Check for each library in the target class
116116
this.librariesToCount.forEach(library => {
117117
if (lcTargetClass.includes(library)) {
118-
this.libraryCounts[library]++;
118+
// Add the original targetClass to the Set for uniqueness
119+
this.libraryCounts[library].add(targetClass);
119120
}
120121
});
121122
}
@@ -223,13 +224,13 @@ class PackageDependencyExtractor {
223224

224225
// Add section for specific library counts
225226
markdownContent += '## Specific Library Counts\n\n';
226-
markdownContent += 'These counts represent the number of dependencies where the `targetClass` field in the JSONL data contains each specific library name. This helps quantify how many times your application code depends on classes from these libraries, which is useful for identifying vulnerability exposure.\n\n';
227-
markdownContent += '| Library | Count |\n';
228-
markdownContent += '|---------|-------|\n';
227+
markdownContent += 'These counts represent the number of unique `targetClass` values in the JSONL data that contain each specific library name. This helps quantify how many distinct classes from these libraries your application code depends on, which is useful for identifying vulnerability exposure.\n\n';
228+
markdownContent += '| Library | Count (Unique Classes) |\n';
229+
markdownContent += '|---------|------------------------|\n';
229230

230231
// Display counts for each library dynamically
231232
Object.keys(this.libraryCounts).forEach(library => {
232-
markdownContent += `| ${library.charAt(0).toUpperCase() + library.slice(1)} | ${this.libraryCounts[library]} |\n`;
233+
markdownContent += `| ${library.charAt(0).toUpperCase() + library.slice(1)} | ${this.libraryCounts[library].size} |\n`;
233234
});
234235
markdownContent += '\n';
235236

@@ -329,16 +330,20 @@ class PackageDependencyExtractor {
329330
// Log the library counts to console
330331
console.log('\nSpecific Library Counts:');
331332
Object.keys(this.libraryCounts).forEach(library => {
332-
console.log(`- ${library.charAt(0).toUpperCase() + library.slice(1)}: ${this.libraryCounts[library]}`);
333+
console.log(`- ${library.charAt(0).toUpperCase() + library.slice(1)}: ${this.libraryCounts[library].size}`);
333334
});
334335
}
335336

336-
// Getter for library counts (useful for testing)
337-
getLibraryCounts(): LibraryCounts {
338-
return this.libraryCounts;
337+
// Method to get the library counts (Set sizes)
338+
getLibraryCounts(): { [key: string]: number } {
339+
const counts: { [key: string]: number } = {};
340+
Object.keys(this.libraryCounts).forEach(lib => {
341+
counts[lib] = this.libraryCounts[lib].size;
342+
});
343+
return counts;
339344
}
340345

341-
// Getter for libraries being counted
346+
// Method to get the list of libraries being counted
342347
getLibrariesToCount(): string[] {
343348
return [...this.librariesToCount];
344349
}

tests/package-dependencies.test.ts

Lines changed: 135 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ class PackageDependencyExtractor {
99
dependencyMap: Map<string, Set<string>> = new Map();
1010
artifactMap: Map<string, Set<string>> = new Map();
1111
basePackageDependencyMap: Map<string, Set<string>> = new Map();
12-
// Add property to track library counts
13-
libraryCounts: { [key: string]: number } = {};
12+
// Update library counts property to use Set for uniqueness
13+
libraryCounts: { [key: string]: Set<string> } = {}; // Changed from number to Set<string>
1414
librariesToCount: string[] = ['struts', 'commons', 'log4j', 'cryptix']; // Default libraries
1515

1616
constructor(librariesToCount?: string) {
@@ -20,7 +20,7 @@ class PackageDependencyExtractor {
2020

2121
// Initialize counter for each library
2222
this.librariesToCount.forEach(lib => {
23-
this.libraryCounts[lib] = 0;
23+
this.libraryCounts[lib] = new Set<string>(); // Initialize with an empty Set
2424
});
2525
}
2626

@@ -199,7 +199,7 @@ class PackageDependencyExtractor {
199199

200200
// Display counts for each library dynamically
201201
Object.keys(this.libraryCounts).forEach(library => {
202-
markdownContent += `| ${library.charAt(0).toUpperCase() + library.slice(1)} | ${this.libraryCounts[library]} |\n`;
202+
markdownContent += `| ${library.charAt(0).toUpperCase() + library.slice(1)} | ${this.libraryCounts[library].size} |\n`; // Use .size in tests too
203203
});
204204
markdownContent += '\n';
205205

@@ -297,21 +297,25 @@ class PackageDependencyExtractor {
297297
fs.writeFileSync(outputFile, markdownContent);
298298
}
299299

300-
// Method to count instances of specific libraries
300+
// Method to count unique classes for specific libraries (Updated for tests)
301301
countSpecificLibraries(targetClass: string): void {
302302
const lcTargetClass = targetClass.toLowerCase();
303303

304-
// Check for each library in the target class
305304
this.librariesToCount.forEach(library => {
306305
if (lcTargetClass.includes(library)) {
307-
this.libraryCounts[library]++;
306+
// Add the original targetClass to the Set for uniqueness
307+
this.libraryCounts[library].add(targetClass);
308308
}
309309
});
310310
}
311311

312-
// Getter for library counts (useful for testing)
313-
getLibraryCounts() {
314-
return this.libraryCounts;
312+
// Method to get the library counts (Set sizes) for testing
313+
getLibraryCounts(): { [key: string]: number } { // Return type changed to number for the size
314+
const counts: { [key: string]: number } = {};
315+
Object.keys(this.libraryCounts).forEach(lib => {
316+
counts[lib] = this.libraryCounts[lib].size;
317+
});
318+
return counts;
315319
}
316320

317321
// Getter for libraries being counted
@@ -670,105 +674,169 @@ describe('PackageDependencyExtractor', () => {
670674
});
671675

672676
// Add tests for library counts
673-
describe('countSpecificLibraries', () => {
677+
describe('Specific Library Counting', () => {
678+
let extractor: PackageDependencyExtractor;
679+
680+
beforeEach(() => {
681+
extractor = new PackageDependencyExtractor();
682+
});
683+
674684
test('should count struts in targetClass', () => {
675-
const extractor = new PackageDependencyExtractor();
676685
extractor.countSpecificLibraries('org.apache.struts.actions.Action');
677-
expect(extractor.libraryCounts['struts']).toBe(1);
686+
expect(extractor.libraryCounts['struts'].size).toBe(1); // Check size
678687
});
679688

680689
test('should count commons in targetClass', () => {
681-
const extractor = new PackageDependencyExtractor();
682690
extractor.countSpecificLibraries('org.apache.commons.lang.StringUtils');
683-
expect(extractor.libraryCounts['commons']).toBe(1);
691+
expect(extractor.libraryCounts['commons'].size).toBe(1); // Check size
684692
});
685693

686694
test('should count log4j in targetClass', () => {
687-
const extractor = new PackageDependencyExtractor();
688695
extractor.countSpecificLibraries('org.apache.log4j.Logger');
689-
expect(extractor.libraryCounts['log4j']).toBe(1);
696+
expect(extractor.libraryCounts['log4j'].size).toBe(1); // Check size
690697
});
691698

692699
test('should count cryptix in targetClass', () => {
693-
const extractor = new PackageDependencyExtractor();
694700
extractor.countSpecificLibraries('cryptix.provider.Cipher');
695-
expect(extractor.libraryCounts['cryptix']).toBe(1);
701+
expect(extractor.libraryCounts['cryptix'].size).toBe(1); // Check size
696702
});
697703

698-
test('should handle case insensitivity', () => {
699-
const extractor = new PackageDependencyExtractor();
704+
test('should handle case-insensitivity', () => {
700705
extractor.countSpecificLibraries('org.apache.STRUTS.Action');
701706
extractor.countSpecificLibraries('org.apache.COMMONS.FileUtils');
702707
extractor.countSpecificLibraries('org.apache.LOG4J.Logger');
703708
extractor.countSpecificLibraries('CRYPTIX.provider.Cipher');
704709

705-
expect(extractor.libraryCounts['struts']).toBe(1);
706-
expect(extractor.libraryCounts['commons']).toBe(1);
707-
expect(extractor.libraryCounts['log4j']).toBe(1);
708-
expect(extractor.libraryCounts['cryptix']).toBe(1);
710+
expect(extractor.libraryCounts['struts'].size).toBe(1);
711+
expect(extractor.libraryCounts['commons'].size).toBe(1);
712+
expect(extractor.libraryCounts['log4j'].size).toBe(1);
713+
expect(extractor.libraryCounts['cryptix'].size).toBe(1);
709714
});
710-
711-
test('should handle multiple libraries in one targetClass', () => {
712-
const extractor = new PackageDependencyExtractor();
715+
716+
test('should handle multiple libraries in one class name', () => {
717+
// Example: a class name containing both 'struts' and 'commons'
713718
extractor.countSpecificLibraries('org.apache.struts.commons.util');
714719

715-
expect(extractor.libraryCounts['struts']).toBe(1);
716-
expect(extractor.libraryCounts['commons']).toBe(1);
717-
expect(extractor.libraryCounts['log4j']).toBe(0);
718-
expect(extractor.libraryCounts['cryptix']).toBe(0);
720+
expect(extractor.libraryCounts['struts'].size).toBe(1);
721+
expect(extractor.libraryCounts['commons'].size).toBe(1);
722+
expect(extractor.libraryCounts['log4j'].size).toBe(0);
723+
expect(extractor.libraryCounts['cryptix'].size).toBe(0);
719724
});
720725

721-
test('should increment counts correctly for multiple calls', () => {
722-
const extractor = new PackageDependencyExtractor();
726+
test('should count unique classes correctly', () => {
723727
extractor.countSpecificLibraries('org.apache.struts.Action');
724728
extractor.countSpecificLibraries('org.apache.struts.actions.DispatchAction');
725729
extractor.countSpecificLibraries('org.apache.commons.lang.StringUtils');
726730
extractor.countSpecificLibraries('org.apache.commons.io.FileUtils');
731+
// Add the same classes again
732+
extractor.countSpecificLibraries('org.apache.struts.Action'); // Duplicate
733+
extractor.countSpecificLibraries('org.apache.commons.lang.StringUtils'); // Duplicate
727734

728-
expect(extractor.libraryCounts['struts']).toBe(2);
729-
expect(extractor.libraryCounts['commons']).toBe(2);
735+
expect(extractor.libraryCounts['struts'].size).toBe(2); // Should be 2 unique classes
736+
expect(extractor.libraryCounts['commons'].size).toBe(2); // Should be 2 unique classes
730737
});
731738

732-
test('should support custom libraries list', () => {
739+
test('should not count libraries not in the list', () => {
733740
const extractor = new PackageDependencyExtractor('spring,hibernate,tomcat');
734741
extractor.countSpecificLibraries('org.springframework.context.ApplicationContext');
735742
extractor.countSpecificLibraries('org.hibernate.Session');
736743

737-
expect(extractor.libraryCounts['spring']).toBe(1);
738-
expect(extractor.libraryCounts['hibernate']).toBe(1);
739-
expect(extractor.libraryCounts['tomcat']).toBe(0);
740-
// The default libraries shouldn't be counted
744+
expect(extractor.libraryCounts['spring'].size).toBe(1);
745+
expect(extractor.libraryCounts['hibernate'].size).toBe(1);
746+
expect(extractor.libraryCounts['tomcat'].size).toBe(0);
747+
expect(extractor.libraryCounts['struts']).toBeUndefined();
748+
});
749+
750+
test('should use custom libraries when provided', () => {
751+
const extractor = new PackageDependencyExtractor('spring,hibernate,tomcat');
752+
extractor.countSpecificLibraries('org.springframework.context.ApplicationContext');
753+
extractor.countSpecificLibraries('org.hibernate.Session');
754+
755+
expect(extractor.libraryCounts['spring'].size).toBe(1);
756+
expect(extractor.libraryCounts['hibernate'].size).toBe(1);
757+
expect(extractor.libraryCounts['tomcat'].size).toBe(0);
741758
expect(extractor.libraryCounts['struts']).toBeUndefined();
742759
});
743760
});
744761

745-
// Add test for processRecord with library counting
746-
test('should count libraries when processing a record', () => {
747-
const extractor = new PackageDependencyExtractor();
748-
const record = {
749-
appSetName: 'TestApp',
750-
applicationName: 'TestApp',
751-
artifactFileName: 'test.jar',
752-
artifactId: 'test',
753-
artifactGroup: 'com.test',
754-
artifactVersion: '1.0.0',
755-
sourceClass: 'com.test.TestClass',
756-
sourceMethod: 'testMethod',
757-
targetClass: 'org.apache.struts.Action',
758-
targetMethod: 'execute'
759-
};
760-
761-
extractor.processRecord(record);
762-
expect(extractor.libraryCounts['struts']).toBe(1);
763-
764-
// Process another record with a different library
765-
const record2 = {
766-
...record,
767-
targetClass: 'org.apache.commons.lang.StringUtils'
768-
};
762+
describe('Integration with processRecord', () => {
763+
let extractor: PackageDependencyExtractor;
769764

770-
extractor.processRecord(record2);
771-
expect(extractor.libraryCounts['commons']).toBe(1);
765+
beforeEach(() => {
766+
extractor = new PackageDependencyExtractor();
767+
});
768+
769+
test('should count struts library via processRecord', () => {
770+
const record = {
771+
appSetName: 'TestApp',
772+
applicationName: 'TestApp',
773+
artifactFileName: 'test.jar',
774+
artifactId: 'test',
775+
artifactGroup: 'com.test',
776+
artifactVersion: '1.0.0',
777+
sourceClass: 'com.test.TestClass',
778+
sourceMethod: 'testMethod',
779+
targetClass: 'org.apache.struts.Action',
780+
targetMethod: 'execute'
781+
};
782+
783+
extractor.processRecord(record);
784+
expect(extractor.libraryCounts['struts'].size).toBe(1);
785+
});
786+
787+
test('should count commons library via processRecord', () => {
788+
const record = {
789+
appSetName: 'TestApp',
790+
applicationName: 'TestApp',
791+
artifactFileName: 'test.jar',
792+
artifactId: 'test',
793+
artifactGroup: 'com.test',
794+
artifactVersion: '1.0.0',
795+
sourceClass: 'com.test.TestClass',
796+
sourceMethod: 'testMethod',
797+
targetClass: 'org.apache.commons.lang.StringUtils',
798+
targetMethod: 'execute'
799+
};
800+
801+
extractor.processRecord(record);
802+
expect(extractor.libraryCounts['commons'].size).toBe(1);
803+
});
804+
805+
test('should correctly count unique classes with multiple identical records', () => {
806+
const recordStruts = {
807+
appSetName: 'TestApp',
808+
applicationName: 'TestApp',
809+
artifactFileName: 'test.jar',
810+
artifactId: 'test',
811+
artifactGroup: 'com.test',
812+
artifactVersion: '1.0.0',
813+
sourceClass: 'com.test.TestClass',
814+
sourceMethod: 'testMethod',
815+
targetClass: 'org.apache.struts.Action',
816+
targetMethod: 'execute'
817+
};
818+
819+
const recordCommons = {
820+
appSetName: 'TestApp',
821+
applicationName: 'TestApp',
822+
artifactFileName: 'test.jar',
823+
artifactId: 'test',
824+
artifactGroup: 'com.test',
825+
artifactVersion: '1.0.0',
826+
sourceClass: 'com.test.TestClass',
827+
sourceMethod: 'testMethod',
828+
targetClass: 'org.apache.commons.lang.StringUtils',
829+
targetMethod: 'execute'
830+
};
831+
832+
extractor.processRecord(recordStruts);
833+
extractor.processRecord(recordCommons);
834+
extractor.processRecord(recordStruts);
835+
extractor.processRecord(recordCommons);
836+
837+
expect(extractor.libraryCounts['struts'].size).toBe(1);
838+
expect(extractor.libraryCounts['commons'].size).toBe(1);
839+
});
772840
});
773841

774842
// Add tests for getLibrariesToCount

0 commit comments

Comments
 (0)