import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js"; import { FileJp, Program, Include, Call, FunctionJp, Joinpoint, StorageClass } from "@specs-feup/clava/api/Joinpoints.js"; import Query from "@specs-feup/lara/api/weaver/Query.js"; import { isCallToImplicitFunction } from "./CallUtils.js"; import { isExternalLinkageIdentifier } from "./IdentifierUtils.js"; import path from "path"; /** * Checks if a file compiles correctly after adding a statement by rebuilding it. * If rebuilding fails, the file is considered invalid with the new statement. * * @param fileJp - The file to validate. */ export function isValidFile(fileJp: FileJp, jpType?: typeof Joinpoint, index?: number) : boolean | Joinpoint | undefined { let result: boolean | Joinpoint = true; // Create a temporary copy of the file for validation const programJp = fileJp.parent as Program; let copyFile = ClavaJoinPoints.fileWithSource(`temp_misra_${fileJp.name}`, fileJp.code, fileJp.relativeFolderpath); copyFile = programJp.addFile(copyFile) as FileJp; try { const rebuiltFile = copyFile.rebuild(); if (jpType && index) { // If requested, return a specific join point inside the rebuilt file result = Query.searchFrom(rebuiltFile, jpType).get()[index]; } // Remove the temporary file const fileToRemove = Query.searchFrom(programJp, FileJp, {filepath: rebuiltFile.filepath}).first(); fileToRemove?.detach(); return result; } catch(error) { // On rebuild failure, delete copy file and return false copyFile.detach(); return false; } } /** * Checks if the rebuilt version of the file compiles and if the provided call is no longer implicit. * * @param fileJp The file to analyze * @param funcName The function name to search the call * @param callIndex The index of the call */ export function isValidFileWithExplicitCall(fileJp: FileJp, funcName: string, callIndex: number, checkNumParams: boolean = false): boolean { const programJp = fileJp.parent as Program; // Create a temporary copy of the file for validation let copyFile = ClavaJoinPoints.fileWithSource(`temp_misra_${fileJp.name}`, fileJp.code, fileJp.relativeFolderpath); copyFile = programJp.addFile(copyFile) as FileJp; try { // Rebuild the file to check validity const rebuiltFile = copyFile.rebuild(); const fileToRemove = Query.searchFrom(programJp, FileJp, {filepath: rebuiltFile.filepath}).first() as FileJp; // Locate the function call and check if it is implicit const callJp = Query.searchFrom(fileToRemove, Call, {name: funcName}).get().at(callIndex); let isExplicitCall = callJp !== undefined && !isCallToImplicitFunction(callJp); if (checkNumParams && isExplicitCall) { isExplicitCall = isExplicitCall && callJp!.args.length === callJp!.directCallee.params.length; } // Remove the temporary file fileToRemove?.detach(); return isExplicitCall; } catch(error) { // On rebuild failure, delete copy file and return false copyFile.detach(); return false; } } /** * Retrieves the list of header files included in the given file * * @param fileJp The file join point * @returns An array of strings with the names of the includes */ export function getIncludesOfFile(fileJp: FileJp): Set { return new Set(fileJp.includes.map(includeJp => includeJp.isAngled ? includeJp.name : path.basename(includeJp.name))); } /** * Removes a specific include directive from the given file, if it exists * * @param includeName The name of the include to remove * @param fileJp The file from which the include should be removed */ export function removeIncludeFromFile(includeName: string, fileJp: FileJp) { const include = Query.searchFrom(fileJp, Include, {name: includeName}).first(); include?.detach(); } /** * Returns all files in the program that include a given header file using the `#include` directive * * @param headerName - The name of the header file to search for * @returns An array of files that include the specified header */ export function findFilesReferencingHeader(headerName: string): FileJp[] { return Query.search(FileJp, (jp) =>{ return getIncludesOfFile(jp).has(headerName)}).get(); } /** * Returns all files in the program that contain at least one call to an implicit function * * @param programJp - The program to analyze * @returns A list of files with implicit function calls */ export function getFilesWithCallToImplicitFunction(programJp: Program): FileJp[] { const files = Query.searchFrom(programJp, FileJp).get(); return files.filter( (fileJp) => Query.searchFrom(fileJp, Call, (callJp) => isCallToImplicitFunction(callJp) ).get().length > 0 ); } /** * Inserts an extern declaration of the given function into the file. * * @param fileJp The file to modify. * @param functionJp The function to declare as extern. * @returns The inserted join point, or undefined if the function has no external linkage. */ export function addExternFunctionDecl(fileJp: FileJp, functionJp: FunctionJp): Joinpoint | undefined { if (!isExternalLinkageIdentifier(functionJp)) { return undefined; } let childAfterExtern: Joinpoint = fileJp.firstChild; while(childAfterExtern instanceof Include) { childAfterExtern = childAfterExtern.siblingsRight[0]; } const externStr = `extern ${functionJp.getDeclaration(true)};`; const externStmt = ClavaJoinPoints.stmtLiteral(externStr); const newExternStmt = childAfterExtern.insertBefore(externStmt); return newExternStmt; } /** * Returns all extern function declarations in the given file * @param fileJp The file join point * @returns An array of functions declared with 'extern' */ export function getExternFunctionDecls(fileJp: FileJp): FunctionJp[] { return Query.searchFrom(fileJp, FunctionJp, {storageClass: StorageClass.EXTERN}).get(); } /** * Returns function calls from a file that are defined in the provided library header. Optionally filters by function names. * * @param fileJp The file join point * @param libraryName Header filename * @param functionNames Optional list of function names to filter */ export function getCallsToLibrary(fileJp: FileJp, libraryName: string, functionNames: Set = new Set()): Call[] { return Query.searchFrom(fileJp, Call, (callJp) => callJp.function?.isInSystemHeader && callJp.function?.filepath.endsWith(libraryName) && (functionNames.size === 0 || functionNames.has(callJp.name)) ).get(); }