UNPKG

@cyclonedx/cdxgen

Version:

Creates CycloneDX Software Bill of Materials (SBOM) from source or container image

713 lines (689 loc) 19.9 kB
const getStaticNumberValue = (astNode) => { if (!astNode) { return undefined; } if (astNode.type === "NumericLiteral") { return astNode.value; } if (astNode.type === "UnaryExpression" && astNode.operator === "-") { const argumentValue = getStaticNumberValue(astNode.argument); return typeof argumentValue === "number" ? -argumentValue : undefined; } return undefined; }; const getStaticPrimitiveValue = (astNode, getLiteralStringValue) => { if (!astNode) { return undefined; } const stringValue = getLiteralStringValue(astNode); if (stringValue !== undefined) { return stringValue; } const numberValue = getStaticNumberValue(astNode); if (numberValue !== undefined) { return numberValue; } if (astNode.type === "BooleanLiteral") { return astNode.value; } if (astNode.type === "NullLiteral") { return null; } return undefined; }; export const toResolvedValueArray = (value) => { if (value === undefined) { return []; } if (!Array.isArray(value)) { return [value]; } return value.flatMap((entry) => toResolvedValueArray(entry)); }; export const resolvedValueKey = (value) => { if (value && typeof value === "object") { return `object:${JSON.stringify(value)}`; } return `${typeof value}:${String(value)}`; }; export const mergeResolvedValues = (...values) => { const mergedValues = []; const seen = new Set(); for (const value of values) { for (const candidate of toResolvedValueArray(value)) { const key = resolvedValueKey(candidate); if (seen.has(key)) { continue; } seen.add(key); mergedValues.push(candidate); } } if (!mergedValues.length) { return undefined; } return mergedValues.length === 1 ? mergedValues[0] : mergedValues; }; export const filterResolvedValues = (value, predicate) => { return mergeResolvedValues( ...toResolvedValueArray(value).filter((candidate) => predicate(candidate)), ); }; export const hasOnlyResolvedValues = (value, predicate) => { const resolvedValues = toResolvedValueArray(value); return resolvedValues.length > 0 && resolvedValues.every(predicate); }; const applyStringTransformToResolvedValue = (value, transform) => { const transformedValues = []; for (const candidate of toResolvedValueArray(value)) { if (typeof candidate !== "string") { continue; } transformedValues.push(transform(candidate)); } return mergeResolvedValues(...transformedValues); }; export const getStaticObjectProperty = (objectValue, propertyName) => { if (Array.isArray(objectValue)) { return mergeResolvedValues( ...objectValue.map((entry) => getStaticObjectProperty(entry, propertyName), ), ); } if (!objectValue || typeof objectValue !== "object") { return undefined; } return objectValue[propertyName]; }; const intersectResolvedValues = (leftValue, rightValue) => { const rightValueKeys = new Set( toResolvedValueArray(rightValue).map((value) => resolvedValueKey(value)), ); return mergeResolvedValues( ...toResolvedValueArray(leftValue).filter((value) => rightValueKeys.has(resolvedValueKey(value)), ), ); }; const createStaticNarrowingMap = (identifierName, value) => { if (!identifierName || value === undefined) { return undefined; } return new Map([[identifierName, value]]); }; const mergeStaticNarrowingMapsForAnd = (leftMap, rightMap) => { if (!leftMap || !rightMap) { return undefined; } const mergedMap = new Map(leftMap); for (const [identifierName, value] of rightMap) { if (!mergedMap.has(identifierName)) { mergedMap.set(identifierName, value); continue; } const intersectedValue = intersectResolvedValues( mergedMap.get(identifierName), value, ); if (intersectedValue === undefined) { return undefined; } mergedMap.set(identifierName, intersectedValue); } return mergedMap; }; const mergeStaticNarrowingMapsForOr = (leftMap, rightMap) => { if (!leftMap || !rightMap || leftMap.size !== rightMap.size) { return undefined; } const mergedMap = new Map(); for (const [identifierName, value] of leftMap) { if (!rightMap.has(identifierName)) { return undefined; } mergedMap.set( identifierName, mergeResolvedValues(value, rightMap.get(identifierName)), ); } return mergedMap; }; const getConditionComparisonIdentifier = (astNode) => { return astNode?.type === "Identifier" ? astNode.name : undefined; }; const getConditionComparisonValue = (astNode, getLiteralStringValue) => { return getStaticPrimitiveValue(astNode, getLiteralStringValue); }; export const deriveStaticNarrowingsFromCondition = ( astNode, branchTaken, getLiteralStringValue, ) => { if (!astNode) { return undefined; } if ( [ "ParenthesizedExpression", "TSAsExpression", "TSNonNullExpression", "TSSatisfiesExpression", "TypeCastExpression", ].includes(astNode.type) ) { return deriveStaticNarrowingsFromCondition( astNode.expression, branchTaken, getLiteralStringValue, ); } if (astNode.type === "UnaryExpression" && astNode.operator === "!") { return deriveStaticNarrowingsFromCondition( astNode.argument, !branchTaken, getLiteralStringValue, ); } if (astNode.type === "LogicalExpression") { if (branchTaken && astNode.operator === "&&") { return mergeStaticNarrowingMapsForAnd( deriveStaticNarrowingsFromCondition( astNode.left, true, getLiteralStringValue, ), deriveStaticNarrowingsFromCondition( astNode.right, true, getLiteralStringValue, ), ); } if (branchTaken && astNode.operator === "||") { return mergeStaticNarrowingMapsForOr( deriveStaticNarrowingsFromCondition( astNode.left, true, getLiteralStringValue, ), deriveStaticNarrowingsFromCondition( astNode.right, true, getLiteralStringValue, ), ); } return undefined; } if (astNode.type !== "BinaryExpression") { return undefined; } const leftIdentifier = getConditionComparisonIdentifier(astNode.left); const rightIdentifier = getConditionComparisonIdentifier(astNode.right); const leftValue = getConditionComparisonValue( astNode.left, getLiteralStringValue, ); const rightValue = getConditionComparisonValue( astNode.right, getLiteralStringValue, ); const identifierName = leftIdentifier || rightIdentifier; const comparisonValue = leftIdentifier ? rightValue : leftValue; if (!identifierName || comparisonValue === undefined) { return undefined; } if (["===", "=="].includes(astNode.operator) && branchTaken) { return createStaticNarrowingMap(identifierName, comparisonValue); } if (["!==", "!="].includes(astNode.operator) && !branchTaken) { return createStaticNarrowingMap(identifierName, comparisonValue); } return undefined; }; export const resolveStaticValue = ( astNode, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth = 0, ) => { if (!astNode || depth > 6) { return undefined; } const primitiveValue = getStaticPrimitiveValue( astNode, getLiteralStringValue, ); if (primitiveValue !== undefined) { return primitiveValue; } if (astNode.type === "Identifier") { if (staticValueByName.has(astNode.name)) { return staticValueByName.get(astNode.name); } return undefined; } if ( [ "ParenthesizedExpression", "TSAsExpression", "TSNonNullExpression", "TSSatisfiesExpression", "TypeCastExpression", ].includes(astNode.type) ) { return resolveStaticValue( astNode.expression, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); } if (astNode.type === "ConditionalExpression") { const testValue = resolveStaticValue( astNode.test, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); const consequentValue = resolveStaticValue( astNode.consequent, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); const alternateValue = resolveStaticValue( astNode.alternate, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); if (typeof testValue === "boolean") { return testValue ? consequentValue : alternateValue; } return mergeResolvedValues(consequentValue, alternateValue); } if (astNode.type === "LogicalExpression") { const leftValue = resolveStaticValue( astNode.left, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); const rightValue = resolveStaticValue( astNode.right, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); if (astNode.operator === "||") { if (leftValue === undefined) { return rightValue; } if (hasOnlyResolvedValues(leftValue, (candidate) => Boolean(candidate))) { return leftValue; } if (hasOnlyResolvedValues(leftValue, (candidate) => !candidate)) { return rightValue; } return mergeResolvedValues( filterResolvedValues(leftValue, (candidate) => Boolean(candidate)), rightValue, ); } if (astNode.operator === "??") { if (leftValue === undefined) { return rightValue; } if ( hasOnlyResolvedValues( leftValue, (candidate) => candidate !== null && candidate !== undefined, ) ) { return leftValue; } if ( hasOnlyResolvedValues( leftValue, (candidate) => candidate === null || candidate === undefined, ) ) { return rightValue; } return mergeResolvedValues( filterResolvedValues( leftValue, (candidate) => candidate !== null && candidate !== undefined, ), rightValue, ); } if (astNode.operator === "&&") { if (leftValue === undefined) { return undefined; } if (hasOnlyResolvedValues(leftValue, (candidate) => !candidate)) { return leftValue; } if (hasOnlyResolvedValues(leftValue, (candidate) => Boolean(candidate))) { return rightValue; } return mergeResolvedValues( filterResolvedValues(leftValue, (candidate) => !candidate), rightValue, ); } } if (astNode.type === "ObjectExpression") { const objectValue = {}; for (const property of astNode.properties || []) { if (property.type !== "ObjectProperty") { continue; } const keyName = getMemberExpressionPropertyName(property.key); if (!keyName) { continue; } const resolvedValue = resolveStaticValue( property.value, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); if (resolvedValue !== undefined) { objectValue[keyName] = resolvedValue; } } return Object.keys(objectValue).length ? objectValue : undefined; } if ( astNode.type === "MemberExpression" || astNode.type === "OptionalMemberExpression" ) { const objectValue = resolveStaticValue( astNode.object, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); const propertyName = getMemberExpressionPropertyName(astNode.property); if (!propertyName) { return undefined; } return getStaticObjectProperty(objectValue, propertyName); } if (astNode.type === "CallExpression") { const callee = astNode.callee; if ( callee?.type !== "MemberExpression" && callee?.type !== "OptionalMemberExpression" ) { return undefined; } const methodName = getMemberExpressionPropertyName(callee.property); if (!methodName) { return undefined; } const targetValue = resolveStaticValue( callee.object, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); if (["toLowerCase", "toUpperCase", "trim"].includes(methodName)) { if ((astNode.arguments || []).length) { return undefined; } const transform = methodName === "toLowerCase" ? (value) => value.toLowerCase() : methodName === "toUpperCase" ? (value) => value.toUpperCase() : (value) => value.trim(); return applyStringTransformToResolvedValue(targetValue, transform); } if (["replace", "replaceAll"].includes(methodName)) { const searchValue = resolveStaticValue( astNode.arguments?.[0], staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); const replacementValue = resolveStaticValue( astNode.arguments?.[1], staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, depth + 1, ); if ( typeof searchValue !== "string" || typeof replacementValue !== "string" ) { return undefined; } return applyStringTransformToResolvedValue(targetValue, (value) => methodName === "replaceAll" ? value.replaceAll(searchValue, replacementValue) : value.replace(searchValue, replacementValue), ); } } return undefined; }; const statementDefinitelyAbrupt = (astNode) => { if (!astNode) { return false; } if ( [ "BreakStatement", "ContinueStatement", "ReturnStatement", "ThrowStatement", ].includes(astNode.type) ) { return true; } if (astNode.type === "BlockStatement") { return statementListFallsThrough(astNode.body || []) === false; } if (astNode.type === "IfStatement") { return ( statementDefinitelyAbrupt(astNode.consequent) && statementDefinitelyAbrupt(astNode.alternate) ); } return false; }; const statementListFallsThrough = (statements) => { const safeStatements = Array.isArray(statements) ? statements : []; if (!safeStatements.length) { return true; } return !statementDefinitelyAbrupt(safeStatements[safeStatements.length - 1]); }; const getSwitchDiscriminantIdentifier = (astNode) => { if (!astNode) { return undefined; } if ( [ "ParenthesizedExpression", "TSAsExpression", "TSNonNullExpression", "TSSatisfiesExpression", "TypeCastExpression", ].includes(astNode.type) ) { return getSwitchDiscriminantIdentifier(astNode.expression); } return astNode.type === "Identifier" ? astNode.name : undefined; }; const isStaticPrimitiveResolvedValue = (value) => { return ( ["boolean", "number", "string"].includes(typeof value) || value === null ); }; const hasOnlyStaticPrimitiveResolvedValues = (value) => { return hasOnlyResolvedValues(value, isStaticPrimitiveResolvedValue); }; export const deriveStaticNarrowingsFromSwitchCase = ( switchCaseNode, switchStatementNode, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, ) => { if (!switchCaseNode || !switchStatementNode) { return undefined; } const identifierName = getSwitchDiscriminantIdentifier( switchStatementNode.discriminant, ); if (!identifierName) { return undefined; } const switchCases = switchStatementNode.cases || []; const currentCaseIndex = switchCases.indexOf(switchCaseNode); if (currentCaseIndex === -1) { return undefined; } if (switchCaseNode.test === null) { const knownDiscriminantValue = staticValueByName.get(identifierName); if (!hasOnlyStaticPrimitiveResolvedValues(knownDiscriminantValue)) { return undefined; } const explicitCaseValues = []; for (const caseNode of switchCases) { if (caseNode.test === null) { continue; } const caseValue = resolveStaticValue( caseNode.test, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, ); if (!hasOnlyStaticPrimitiveResolvedValues(caseValue)) { return undefined; } explicitCaseValues.push(...toResolvedValueArray(caseValue)); } const explicitCaseKeys = new Set( explicitCaseValues.map((value) => resolvedValueKey(value)), ); const remainingValues = toResolvedValueArray(knownDiscriminantValue).filter( (value) => !explicitCaseKeys.has(resolvedValueKey(value)), ); const narrowedValue = mergeResolvedValues(...remainingValues); return createStaticNarrowingMap(identifierName, narrowedValue); } let chainStartIndex = currentCaseIndex; for ( let caseIndex = currentCaseIndex - 1; caseIndex >= 0 && statementListFallsThrough(switchCases[caseIndex].consequent); caseIndex -= 1 ) { chainStartIndex = caseIndex; } const caseValues = []; for ( let caseIndex = chainStartIndex; caseIndex <= currentCaseIndex; caseIndex += 1 ) { const caseNode = switchCases[caseIndex]; if (caseNode.test === null) { return undefined; } const caseValue = resolveStaticValue( caseNode.test, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, ); if (!hasOnlyStaticPrimitiveResolvedValues(caseValue)) { return undefined; } caseValues.push(caseValue); } const narrowedValue = mergeResolvedValues(...caseValues); return createStaticNarrowingMap(identifierName, narrowedValue); }; export const getScopedStaticValueByName = ( path, staticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, ) => { const scopedStaticValueByName = new Map(staticValueByName); const ancestorNarrowingContexts = []; let currentPath = path; while (currentPath?.parentPath) { const parentPath = currentPath.parentPath; if (parentPath.node?.type === "IfStatement") { const branchTaken = currentPath.key === "consequent" ? true : currentPath.key === "alternate" ? false : undefined; if (branchTaken !== undefined) { ancestorNarrowingContexts.push({ branchTaken, test: parentPath.node.test, type: "if", }); } } if ( parentPath.node?.type === "SwitchCase" && parentPath.parentPath?.node?.type === "SwitchStatement" ) { ancestorNarrowingContexts.push({ switchCaseNode: parentPath.node, switchStatementNode: parentPath.parentPath.node, type: "switch-case", }); } currentPath = parentPath; } ancestorNarrowingContexts.reverse().forEach((context) => { const narrowedValues = context.type === "if" ? deriveStaticNarrowingsFromCondition( context.test, context.branchTaken, getLiteralStringValue, ) : deriveStaticNarrowingsFromSwitchCase( context.switchCaseNode, context.switchStatementNode, scopedStaticValueByName, getLiteralStringValue, getMemberExpressionPropertyName, ); if (!narrowedValues?.size) { return; } narrowedValues.forEach((value, identifierName) => { scopedStaticValueByName.set(identifierName, value); }); }); return scopedStaticValueByName; };