agentsqripts
Version:
Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems
124 lines (113 loc) • 4.66 kB
JavaScript
/**
* @file Detect potential null/undefined access patterns using AST analysis
* @description Single responsibility: Identify member access on potentially null values
*
* This detector uses Abstract Syntax Tree (AST) traversal to identify potential runtime
* errors caused by accessing properties or methods on null or undefined values. It tracks
* variable nullability through the code flow and reports unsafe access patterns.
*
* Design rationale:
* - AST-based analysis provides precise source location information
* - State tracking through NullabilityTracker maintains variable flow analysis
* - Conservative approach flags potential issues to prevent runtime errors
* - Separate handling for different types of unsafe access patterns
*/
const walk = require('acorn-walk');
const { getLineNumber, getVariableName } = require('../../../utils/astParser');
/**
* Track variable nullability state through code execution flow
*
* This class maintains a stateful analysis of variable nullability as code is traversed.
* It tracks variable declarations, assignments, and initialization status to determine
* which variables may contain null or undefined values at access points.
*
* Implementation rationale:
* - Map data structure provides O(1) variable lookup performance
* - Separate tracking of nullable and initialized state handles different risk patterns
* - Conservative default assumptions prevent false negatives in safety analysis
* - Simple state model balances accuracy with performance for large codebases
*/
class NullabilityTracker {
constructor() {
this.variables = new Map(); // variable -> { nullable: boolean, initialized: boolean }
}
setVariable(name, nullable, initialized = true) {
this.variables.set(name, { nullable, initialized });
}
isNullable(name) {
const info = this.variables.get(name);
return info ? info.nullable : true; // Unknown variables are potentially nullable
}
isInitialized(name) {
const info = this.variables.get(name);
return info ? info.initialized : false;
}
}
/**
* Detect potential null/undefined access
* @param {Object} ast - AST tree
* @param {string} filePath - File path
* @returns {Array} Detected bugs
*/
function detectNullAccess(ast, filePath) {
const bugs = [];
const tracker = new NullabilityTracker();
walk.simple(ast, {
// Track variable declarations
VariableDeclarator(node) {
const name = node.id.name;
if (!node.init) {
// Uninitialized variable
tracker.setVariable(name, true, false);
} else if (node.init.type === 'Literal' && node.init.value === null) {
tracker.setVariable(name, true, true);
} else if (node.init.type === 'Identifier' && node.init.name === 'undefined') {
tracker.setVariable(name, true, true);
} else {
tracker.setVariable(name, false, true);
}
},
// Check member access on potentially null values
MemberExpression(node) {
const objectName = getVariableName(node.object);
if (objectName && tracker.isNullable(objectName)) {
bugs.push({
type: 'potential_null_access',
severity: 'MEDIUM',
category: 'Null Safety',
line: getLineNumber(node),
column: node.loc ? node.loc.start.column : 0,
description: `Potential null/undefined access on '${objectName}'`,
recommendation: `Add null check before accessing properties: ${objectName}?.property or ${objectName} && ${objectName}.property`,
effort: 1,
impact: 'medium',
file: filePath
});
}
},
// Check array access without bounds checking
MemberExpression(node) {
if (node.computed && node.property.type === 'Literal' &&
typeof node.property.value === 'number') {
const objectName = getVariableName(node.object);
if (objectName && objectName.includes('array') || objectName.includes('list')) {
// This is a heuristic - could be improved with type tracking
bugs.push({
type: 'array_bounds',
severity: 'LOW',
category: 'Null Safety',
line: getLineNumber(node),
column: node.loc ? node.loc.start.column : 0,
description: `Array access without bounds checking: ${objectName}[${node.property.value}]`,
recommendation: 'Consider checking array length before accessing by index',
effort: 1,
impact: 'low',
file: filePath
});
}
}
}
});
return bugs;
}
module.exports = detectNullAccess;