File indexing completed on 2024-11-24 05:05:27
0001 /* 0002 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk> 0003 // SPDX-License-Identifier: Apache-2.0 0004 // 0005 // Licensed under the Apache License, Version 2.0 (the "License"); 0006 // you may not use this file except in compliance with the License. 0007 // You may obtain a copy of the License at 0008 // 0009 // http://www.apache.org/licenses/LICENSE-2.0 0010 // 0011 // Unless required by applicable law or agreed to in writing, software 0012 // distributed under the License is distributed on an "AS IS" BASIS, 0013 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0014 // See the License for the specific language governing permissions and 0015 // limitations under the License. 0016 */ 0017 0018 #include <catch2-local-includes.h> 0019 #include <ct_lvtclp_cpp_tool.h> 0020 #include <ct_lvtclp_logicaldepscanner.h> 0021 #include <ct_lvtclp_testutil.h> 0022 #include <ct_lvtmdb_componentobject.h> 0023 #include <ct_lvtmdb_functionobject.h> 0024 #include <ct_lvtmdb_packageobject.h> 0025 #include <fortran/ct_lvtclp_fortran_c_interop.h> 0026 #include <fortran/ct_lvtclp_fortran_tool.h> 0027 #include <test-project-paths.h> 0028 0029 #include <memory> 0030 0031 using namespace Codethink::lvtclp; 0032 using namespace Codethink::lvtmdb; 0033 using namespace clang::tooling; 0034 0035 class CompilationDatabaseForTesting : public CompilationDatabase { 0036 public: 0037 CompilationDatabaseForTesting(std::vector<std::filesystem::path> const& files, 0038 std::vector<std::filesystem::path> const& includePaths = {}): 0039 files(files), includePaths(includePaths) 0040 { 0041 } 0042 0043 ~CompilationDatabaseForTesting() = default; 0044 0045 std::vector<CompileCommand> getCompileCommands(clang::StringRef FilePath) const 0046 { 0047 throw std::runtime_error("Not implemented."); 0048 } 0049 0050 std::vector<std::string> getAllFiles() const 0051 { 0052 auto rawFilesAsStr = std::vector<std::string>{}; 0053 for (auto const& f : this->files) { 0054 rawFilesAsStr.push_back(f.string()); 0055 } 0056 return rawFilesAsStr; 0057 } 0058 0059 std::vector<CompileCommand> getAllCompileCommands() const 0060 { 0061 auto cmds = std::vector<CompileCommand>{}; 0062 auto defaultCommandLine = std::vector<std::string>{}; 0063 for (auto const& includePath : this->includePaths) { 0064 defaultCommandLine.push_back("-I" + includePath.string()); 0065 } 0066 for (auto const& f : this->files) { 0067 auto cmd = CompileCommand{}; 0068 cmd.Directory = ""; // Intentionally left blank 0069 cmd.Filename = f; 0070 cmd.CommandLine = defaultCommandLine; 0071 cmd.Output = f.string() + ".o"; 0072 cmd.Heuristic = "Test cmd generated with CompilationDatabaseForTesting"; 0073 cmds.push_back(cmd); 0074 } 0075 return cmds; 0076 } 0077 0078 private: 0079 std::vector<std::filesystem::path> files; 0080 std::vector<std::filesystem::path> includePaths; 0081 }; 0082 0083 TEST_CASE("Simple fortran project") 0084 { 0085 auto const PREFIX = std::string{TEST_PRJ_PATH}; 0086 auto fileList = std::vector<std::filesystem::path>{{PREFIX + "/fortran_basics/a.f"}}; 0087 auto tool = fortran::Tool{std::make_unique<CompilationDatabaseForTesting>(fileList)}; 0088 tool.runFull(); 0089 0090 auto locks = std::vector<Lockable::ROLock>{}; 0091 auto l = [&](Lockable::ROLock&& lock) { 0092 locks.emplace_back(std::move(lock)); 0093 }; 0094 auto& memDb = tool.getObjectStore(); 0095 l(memDb.readOnlyLock()); 0096 0097 // Although only one file is being parsed, we do get information from the others. 0098 REQUIRE(memDb.getAllFiles().size() == 2); 0099 0100 auto *componentA = memDb.getComponent("fortran_basics/a"); 0101 REQUIRE(componentA); 0102 l(componentA->readOnlyLock()); 0103 0104 auto *componentB = memDb.getComponent("fortran_basics/b"); 0105 REQUIRE(componentB); 0106 l(componentB->readOnlyLock()); 0107 0108 auto *package = componentA->package(); 0109 REQUIRE(package); 0110 l(package->readOnlyLock()); 0111 0112 REQUIRE(componentA->name() == "a"); 0113 REQUIRE(componentB->name() == "b"); 0114 REQUIRE(package->name() == "fortran_basics"); 0115 0116 auto *funcCal1 = memDb.getFunction( 0117 /*qualifiedName=*/"cal1", 0118 /*signature=*/"", 0119 /*templateParameters=*/"", 0120 /*returnType=*/""); 0121 REQUIRE(funcCal1); 0122 l(funcCal1->readOnlyLock()); 0123 0124 auto *funcCal2 = memDb.getFunction( 0125 /*qualifiedName=*/"cal2", 0126 /*signature=*/"", 0127 /*templateParameters=*/"", 0128 /*returnType=*/""); 0129 REQUIRE(funcCal2); 0130 l(funcCal2->readOnlyLock()); 0131 0132 auto *funcCal3 = memDb.getFunction( 0133 /*qualifiedName=*/"cal3", 0134 /*signature=*/"", 0135 /*templateParameters=*/"", 0136 /*returnType=*/""); 0137 REQUIRE(funcCal3); 0138 l(funcCal3->readOnlyLock()); 0139 0140 auto *funcCalF = memDb.getFunction( 0141 /*qualifiedName=*/"cal_f", 0142 /*signature=*/"", 0143 /*templateParameters=*/"", 0144 /*returnType=*/""); 0145 REQUIRE(funcCalF); 0146 l(funcCalF->readOnlyLock()); 0147 0148 REQUIRE(funcCal1->callees().size() == 3); 0149 REQUIRE(funcCal1->callers().size() == 0); 0150 0151 REQUIRE(funcCal2->callees().size() == 0); 0152 REQUIRE(funcCal2->callers().size() == 1); 0153 0154 REQUIRE(funcCal3->callees().size() == 0); 0155 REQUIRE(funcCal3->callers().size() == 0); 0156 0157 REQUIRE(funcCalF->callees().size() == 0); 0158 REQUIRE(funcCalF->callers().size() == 1); 0159 } 0160 0161 TEST_CASE("Mixed fortran and C project") 0162 { 0163 auto const PREFIX = std::string{TEST_PRJ_PATH} + "/fortran_c_mixed"; 0164 auto sharedMemDb = std::make_shared<ObjectStore>(); 0165 0166 auto fileList = std::vector<std::filesystem::path>{{PREFIX + "/mixedprj/a.f"}, 0167 {PREFIX + "/mixedprj/b.f"}, 0168 // C files will be ignored by Fortran parser. 0169 {PREFIX + "/mixedprj/c.c"}, 0170 {PREFIX + "/mixedprj/main.c"}}; 0171 auto includePaths = std::vector<std::filesystem::path>{{PREFIX + "/otherprj"}}; 0172 auto fortranTool = fortran::Tool{std::make_unique<CompilationDatabaseForTesting>(fileList, includePaths)}; 0173 fortranTool.setSharedMemDb(sharedMemDb); 0174 0175 auto staticCompilationDb = 0176 StaticCompilationDatabase{{{PREFIX + "/mixedprj/c.c", "c.o"}, {PREFIX + "/mixedprj/main.c", "main.o"}}, 0177 "placeholder", 0178 {"-I" + PREFIX + "/mixedprj/", "-std=c++17"}, 0179 PREFIX}; 0180 auto cTool = CppTool( 0181 /*sourcePath=*/PREFIX, 0182 /*db=*/staticCompilationDb, 0183 /*databasePath=*/"unused"); 0184 cTool.setSharedMemDb(sharedMemDb); 0185 0186 REQUIRE(cTool.runFull()); 0187 REQUIRE(fortranTool.runFull()); 0188 0189 auto getAllFunctionsForComponent = [](auto *component) { 0190 auto allFuncs = std::map<std::string, FunctionObject *>{}; 0191 for (auto *file : component->files()) { 0192 auto _fileLock = file->readOnlyLock(); 0193 for (auto *func : file->globalFunctions()) { 0194 auto _funcLock = func->readOnlyLock(); 0195 allFuncs[func->qualifiedName()] = func; 0196 } 0197 } 0198 return allFuncs; 0199 }; 0200 0201 { 0202 auto locks = std::vector<Lockable::ROLock>{}; 0203 auto l = [&](Lockable::ROLock&& lock) { 0204 locks.emplace_back(std::move(lock)); 0205 }; 0206 l(sharedMemDb->readOnlyLock()); 0207 0208 // All fortran files + C files 0209 REQUIRE(sharedMemDb->getAllFiles().size() == 7); 0210 0211 auto cComponent = sharedMemDb->getComponent("mixedprj/c"); 0212 REQUIRE(cComponent); 0213 l(cComponent->readOnlyLock()); 0214 auto cFuncs = getAllFunctionsForComponent(cComponent); 0215 REQUIRE(cFuncs.size() == 2); 0216 REQUIRE(cFuncs.at("cal_c")); 0217 // c_func_ has the "_" suffix AND it is defined within C code. 0218 REQUIRE(cFuncs.at("c_func_")); 0219 0220 auto otherComponent = sharedMemDb->getComponent("mixedprj/other"); 0221 REQUIRE(otherComponent); 0222 l(otherComponent->readOnlyLock()); 0223 auto otherFuncs = getAllFunctionsForComponent(otherComponent); 0224 0225 // NOTE: 'other_func' is a function declared in 'other.h', but it is never defined. 0226 // 'cal1_' is a also never defined, but since it has the '_' suffix, it is assumed to 0227 // be defined in Fortran, thus it is persisted on the 'other' component. 0228 REQUIRE(otherFuncs.size() == 1); 0229 REQUIRE(otherFuncs.at("cal1_")); 0230 l(otherFuncs.at("cal1_")->readOnlyLock()); 0231 0232 auto aComponent = sharedMemDb->getComponent("mixedprj/a"); 0233 REQUIRE(aComponent); 0234 l(aComponent->readOnlyLock()); 0235 auto aFuncs = getAllFunctionsForComponent(aComponent); 0236 REQUIRE(aFuncs.size() == 2); 0237 REQUIRE(aFuncs.contains("cal1")); 0238 REQUIRE(aFuncs.contains("cal2")); 0239 0240 auto bComponent = sharedMemDb->getComponent("mixedprj/b"); 0241 REQUIRE(bComponent); 0242 l(bComponent->readOnlyLock()); 0243 auto bFuncs = getAllFunctionsForComponent(bComponent); 0244 REQUIRE(bFuncs.size() == 1); 0245 REQUIRE(bFuncs.contains("cal3")); 0246 0247 auto innerComponent = sharedMemDb->getComponent("otherprj/inner"); 0248 REQUIRE(innerComponent); 0249 l(innerComponent->readOnlyLock()); 0250 auto innerFuncs = getAllFunctionsForComponent(innerComponent); 0251 REQUIRE(innerFuncs.size() == 1); 0252 REQUIRE(innerFuncs.contains("innersubroutine")); 0253 0254 auto mixedprjPackage = sharedMemDb->getPackage("mixedprj"); 0255 auto otherprjPackage = sharedMemDb->getPackage("otherprj"); 0256 l(mixedprjPackage->readOnlyLock()); 0257 l(otherprjPackage->readOnlyLock()); 0258 auto& mixedprjFwdDeps = mixedprjPackage->forwardDependencies(); 0259 auto& otherprjRevDeps = otherprjPackage->reverseDependencies(); 0260 REQUIRE(std::find(mixedprjFwdDeps.begin(), mixedprjFwdDeps.end(), otherprjPackage) != mixedprjFwdDeps.end()); 0261 REQUIRE(std::find(otherprjRevDeps.begin(), otherprjRevDeps.end(), mixedprjPackage) != otherprjRevDeps.end()); 0262 0263 // Checks BEFORE Fortran <-> C interop solver run 0264 { 0265 // There should be 0 callees from C to Fortran code 0266 REQUIRE(otherFuncs.at("cal1_")->callees().empty()); 0267 0268 // No dependency between external Fortran to C components 0269 auto& otherFwdDeps = otherComponent->forwardDependencies(); 0270 REQUIRE(std::find(otherFwdDeps.begin(), otherFwdDeps.end(), aComponent) == otherFwdDeps.end()); 0271 auto& aRevDeps = aComponent->reverseDependencies(); 0272 REQUIRE(std::find(aRevDeps.begin(), aRevDeps.end(), otherComponent) == aRevDeps.end()); 0273 } 0274 } 0275 0276 Codethink::lvtclp::fortran::solveFortranToCInteropDeps(*sharedMemDb); 0277 0278 { 0279 auto locks = std::vector<Lockable::ROLock>{}; 0280 auto l = [&](Lockable::ROLock&& lock) { 0281 locks.emplace_back(std::move(lock)); 0282 }; 0283 l(sharedMemDb->readOnlyLock()); 0284 0285 auto otherComponent = sharedMemDb->getComponent("mixedprj/other"); 0286 REQUIRE(otherComponent); 0287 l(otherComponent->readOnlyLock()); 0288 auto otherFuncs = getAllFunctionsForComponent(otherComponent); 0289 REQUIRE(otherFuncs.size() == 1); 0290 REQUIRE(otherFuncs.at("cal1_")); 0291 l(otherFuncs.at("cal1_")->readOnlyLock()); 0292 0293 auto aComponent = sharedMemDb->getComponent("mixedprj/a"); 0294 REQUIRE(aComponent); 0295 l(aComponent->readOnlyLock()); 0296 0297 // Checks AFTER Fortran <-> C interop solver run 0298 { 0299 // The fortran dependency is resolved, so there is one callee dependency 0300 REQUIRE(otherFuncs.at("cal1_")->callees().size() == 1); 0301 auto *fortranCal1 = otherFuncs.at("cal1_")->callees()[0]; 0302 l(fortranCal1->readOnlyLock()); 0303 REQUIRE(fortranCal1->qualifiedName() == "cal1"); 0304 0305 // Check component dependency propagation 0306 auto& otherFwdDeps = otherComponent->forwardDependencies(); 0307 REQUIRE(std::find(otherFwdDeps.begin(), otherFwdDeps.end(), aComponent) != otherFwdDeps.end()); 0308 auto& aRevDeps = aComponent->reverseDependencies(); 0309 REQUIRE(std::find(aRevDeps.begin(), aRevDeps.end(), otherComponent) != aRevDeps.end()); 0310 } 0311 } 0312 }