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 }