#include "SwiftOutputRewrite.h" #include "swift/extractor/SwiftExtractorConfiguration.h" #include #include #include #include #include #include #include #include #include // Creates a copy of the output file map and updated remapping table in place // It does not change the original map file as it is depended upon by the original compiler // Returns path to the newly created output file map on success, or None in a case of failure static std::optional rewriteOutputFileMap( const codeql::SwiftExtractorConfiguration& config, const std::string& outputFileMapPath, const std::vector& inputs, std::unordered_map& remapping) { auto newPath = config.tempArtifactDir + '/' + outputFileMapPath; // TODO: do not assume absolute path for the second parameter auto outputMapOrError = swift::OutputFileMap::loadFromPath(outputFileMapPath, ""); if (!outputMapOrError) { return std::nullopt; } auto oldOutputMap = outputMapOrError.get(); swift::OutputFileMap newOutputMap; std::vector keys; for (auto& key : inputs) { auto oldMap = oldOutputMap.getOutputMapForInput(key); if (!oldMap) { continue; } keys.push_back(key); auto& newMap = newOutputMap.getOrCreateOutputMapForInput(key); newMap.copyFrom(*oldMap); for (auto& entry : newMap) { auto oldPath = entry.getSecond(); auto newPath = config.tempArtifactDir + '/' + oldPath; entry.getSecond() = newPath; remapping[oldPath] = newPath; } } std::error_code ec; llvm::SmallString filepath(newPath); llvm::StringRef parent = llvm::sys::path::parent_path(filepath); if (std::error_code ec = llvm::sys::fs::create_directories(parent)) { std::cerr << "Cannot create relocated output map dir: '" << parent.str() << "': " << ec.message() << "\n"; return std::nullopt; } llvm::raw_fd_ostream fd(newPath, ec, llvm::sys::fs::OF_None); newOutputMap.write(fd, keys); return newPath; } // This is an Xcode-specific workaround to produce alias names for an existing .swiftmodule file. // In the case of Xcode, it calls the Swift compiler and asks it to produce a Swift module. // Once it's done, Xcode moves the .swiftmodule file in another location, and the location is // rather arbitrary. Here are examples of such locations: // Original file produced by the frontend: // DerivedData//Build/Intermediates.noindex/.build/-/.build/Objects-normal//.swiftmodule // where: // Project: name of a project, target, or scheme // BuildType: Debug, Release, etc. // Target: macOS, iphoneos, appletvsimulator, etc. // Arch: arm64, x86_64, etc. // // So far we observed that Xcode can move the module into different locations, and it's not // entirely clear how to deduce the destination from the context available for the extractor. // 1. First case: // DerivedData//Build/Products/-/.swiftmodule/.swiftmodule // DerivedData//Build/Products/-/.swiftmodule/.swiftmodule // 2. Second case: // DerivedData//Build/Products/-//.swiftmodule/.swiftmodule // DerivedData//Build/Products/-//.swiftmodule/.swiftmodule // 2. Third case: // DerivedData//Build/Products/-//.framework/Modules/.swiftmodule/.swiftmodule // DerivedData//Build/Products/-//.framework/Modules/.swiftmodule/.swiftmodule // The here is a normalized target triple (e.g. arm64-apple-iphoneos15.4 -> // arm64-apple-iphoneos). // // This method constructs those aliases for a module only if it comes from Xcode, which is detected // by the presence of an `Intermediates.noindex` directory in the module path. // // In the case of the Swift Package Manager (`swift build`) this is not needed. static std::vector computeModuleAliases(llvm::StringRef modulePath, const std::string& targetTriple) { if (modulePath.empty()) { return {}; } if (!modulePath.endswith(".swiftmodule")) { return {}; } llvm::SmallVector chunks; modulePath.split(chunks, '/'); size_t intermediatesDirIndex = 0; for (size_t i = 0; i < chunks.size(); i++) { if (chunks[i] == "Intermediates.noindex") { intermediatesDirIndex = i; break; } } // Not built by Xcode, skipping if (intermediatesDirIndex == 0) { return {}; } // e.g. Debug-iphoneos, Release-iphonesimulator, etc. auto destinationDir = chunks[intermediatesDirIndex + 2].str(); auto arch = chunks[intermediatesDirIndex + 5].str(); auto moduleNameWithExt = chunks.back(); auto moduleName = moduleNameWithExt.substr(0, moduleNameWithExt.find_last_of('.')); std::string relocatedModulePath = chunks[0].str(); for (size_t i = 1; i < intermediatesDirIndex; i++) { relocatedModulePath += '/' + chunks[i].str(); } relocatedModulePath += "/Products/"; relocatedModulePath += destinationDir + '/'; std::vector moduleLocations; std::string firstCase = relocatedModulePath; firstCase += moduleNameWithExt.str() + '/'; moduleLocations.push_back(firstCase); std::string secondCase = relocatedModulePath; secondCase += moduleName.str() + '/'; secondCase += moduleNameWithExt.str() + '/'; moduleLocations.push_back(secondCase); std::string thirdCase = relocatedModulePath; thirdCase += moduleName.str() + '/'; thirdCase += moduleName.str() + ".framework/Modules/"; thirdCase += moduleNameWithExt.str() + '/'; moduleLocations.push_back(thirdCase); std::vector aliases; for (auto& location : moduleLocations) { aliases.push_back(location + arch + ".swiftmodule"); if (!targetTriple.empty()) { llvm::Triple triple(targetTriple); auto moduleTriple = swift::getTargetSpecificModuleTriple(triple); aliases.push_back(location + moduleTriple.normalize() + ".swiftmodule"); } } return aliases; } namespace codeql { std::unordered_map rewriteOutputsInPlace( SwiftExtractorConfiguration& config, std::vector& CLIArgs) { std::unordered_map remapping; // TODO: handle filelists? std::unordered_set pathRewriteOptions({ "-emit-dependencies-path", "-emit-module-path", "-emit-module-doc-path", "-emit-module-source-info-path", "-emit-objc-header-path", "-emit-reference-dependencies-path", "-index-store-path", "-module-cache-path", "-o", "-pch-output-dir", "-serialize-diagnostics-path", }); std::unordered_set outputFileMaps( {"-supplementary-output-file-map", "-output-file-map"}); std::vector outputFileMapIndexes; std::vector maybeInput; std::string targetTriple; std::vector newLocations; for (size_t i = 0; i < CLIArgs.size(); i++) { if (pathRewriteOptions.count(CLIArgs[i])) { auto oldPath = CLIArgs[i + 1]; auto newPath = config.tempArtifactDir + '/' + oldPath; CLIArgs[++i] = newPath; newLocations.push_back(newPath); remapping[oldPath] = newPath; } else if (outputFileMaps.count(CLIArgs[i])) { // collect output map indexes for further rewriting and skip the following argument // We don't patch the map in place as we need to collect all the input files first outputFileMapIndexes.push_back(++i); } else if (CLIArgs[i] == "-target") { targetTriple = CLIArgs[++i]; } else if (CLIArgs[i][0] != '-') { // TODO: add support for input file lists? // We need to collect input file names to later use them to extract information from the // output file maps. maybeInput.push_back(CLIArgs[i]); } } for (auto index : outputFileMapIndexes) { auto oldPath = CLIArgs[index]; auto maybeNewPath = rewriteOutputFileMap(config, oldPath, maybeInput, remapping); if (maybeNewPath) { auto newPath = maybeNewPath.value(); CLIArgs[index] = newPath; remapping[oldPath] = newPath; } } // This doesn't really belong here, but we've got Xcode... for (auto& [oldPath, newPath] : remapping) { llvm::StringRef path(oldPath); auto aliases = computeModuleAliases(path, targetTriple); for (auto& alias : aliases) { remapping[alias] = newPath; } } return remapping; } void ensureNewPathsExist(const std::unordered_map& remapping) { for (auto& [_, newPath] : remapping) { llvm::SmallString filepath(newPath); llvm::StringRef parent = llvm::sys::path::parent_path(filepath); if (std::error_code ec = llvm::sys::fs::create_directories(parent)) { std::cerr << "Cannot create redirected directory: " << ec.message() << "\n"; } } } void storeRemappingForVFS(const SwiftExtractorConfiguration& config, const std::unordered_map& remapping) { // Only create remapping for the .swiftmodule files std::unordered_map modules; for (auto& [oldPath, newPath] : remapping) { if (llvm::StringRef(oldPath).endswith(".swiftmodule")) { modules[oldPath] = newPath; } } if (modules.empty()) { return; } if (std::error_code ec = llvm::sys::fs::create_directories(config.tempVFSDir)) { std::cerr << "Cannot create temp VFS directory: " << ec.message() << "\n"; return; } if (std::error_code ec = llvm::sys::fs::create_directories(config.VFSDir)) { std::cerr << "Cannot create VFS directory: " << ec.message() << "\n"; return; } // Constructing the VFS yaml file in a temp folder so that the other process doesn't read it // while it is not complete // TODO: Pick a more robust way to not collide with files from other processes auto tempVfsPath = config.tempVFSDir + '/' + std::to_string(getpid()) + "-vfs.yaml"; std::error_code ec; llvm::raw_fd_ostream fd(tempVfsPath, ec, llvm::sys::fs::OF_None); if (ec) { std::cerr << "Cannot create temp VFS file: '" << tempVfsPath << "': " << ec.message() << "\n"; return; } // TODO: there must be a better API than this // LLVM expects the version to be 0 fd << "{ version: 0,\n"; // This tells the FS not to fallback to the physical file system in case the remapped file is not // present fd << " fallthrough: false,\n"; fd << " roots: [\n"; for (auto& [oldPath, newPath] : modules) { fd << " {\n"; fd << " type: 'file',\n"; fd << " name: '" << oldPath << "\',\n"; fd << " external-contents: '" << newPath << "\'\n"; fd << " },\n"; } fd << " ]\n"; fd << "}\n"; fd.flush(); auto vfsPath = config.VFSDir + '/' + std::to_string(getpid()) + "-vfs.yaml"; if (std::error_code ec = llvm::sys::fs::rename(tempVfsPath, vfsPath)) { std::cerr << "Cannot move temp VFS file '" << tempVfsPath << "' -> '" << vfsPath << "': " << ec.message() << "\n"; return; } } std::vector collectVFSFiles(const SwiftExtractorConfiguration& config) { auto vfsDir = config.VFSDir + '/'; if (!llvm::sys::fs::exists(vfsDir)) { return {}; } std::vector overlays; std::error_code ec; llvm::sys::fs::directory_iterator it(vfsDir, ec); while (!ec && it != llvm::sys::fs::directory_iterator()) { llvm::StringRef path(it->path()); if (path.endswith("vfs.yaml")) { overlays.push_back(path.str()); } it.increment(ec); } return overlays; } } // namespace codeql