From 598e83191f9814066ba701f16ca19197de8ab801 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 30 Nov 2024 17:12:11 +0300 Subject: [PATCH 1/8] [0.0.23] Upgrade LLVM to 19.1.4 Added base implementation of CodeEvaluator class which able to evaluate constexpr code. Small refactoring in CodeAnalyzer. Fixed possible bug with id0.hpp file when it contents could be corrupted. Used better approach from LLVM with virtual files. --- .github/workflows/build.yml | 6 +- ...CollectConstexprVariableEvalResultAction.h | 20 ++ LLVM/include/RG3/LLVM/CodeEvaluator.h | 42 +++ .../RG3/LLVM/CompilerInstanceFactory.h | 23 ++ .../CollectConstexprVariableEvalResult.h | 22 ++ ...llectConstexprVariableEvalResultAction.cpp | 15 + LLVM/source/CodeAnalyzer.cpp | 292 +----------------- LLVM/source/CodeEvaluator.cpp | 93 ++++++ LLVM/source/CompilerInstanceFactory.cpp | 289 +++++++++++++++++ .../CollectConstexprVariableEvalResult.cpp | 70 +++++ Tests/Unit/source/Tests_CodeEvaluator.cpp | 57 ++++ 11 files changed, 642 insertions(+), 287 deletions(-) create mode 100644 LLVM/include/RG3/LLVM/Actions/CollectConstexprVariableEvalResultAction.h create mode 100644 LLVM/include/RG3/LLVM/CodeEvaluator.h create mode 100644 LLVM/include/RG3/LLVM/CompilerInstanceFactory.h create mode 100644 LLVM/include/RG3/LLVM/Consumers/CollectConstexprVariableEvalResult.h create mode 100644 LLVM/source/Actions/CollectConstexprVariableEvalResultAction.cpp create mode 100644 LLVM/source/CodeEvaluator.cpp create mode 100644 LLVM/source/CompilerInstanceFactory.cpp create mode 100644 LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp create mode 100644 Tests/Unit/source/Tests_CodeEvaluator.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24616df..fdae71f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: cxx: "cl", boost_toolset: msvc, cmake_generator: "Visual Studio 17 2022", - llvm_tag: "llvmorg-18.1.8", + llvm_tag: "llvmorg-19.1.4", python_version: "3.10", os_version: "2022", artifact_id: "RG3_Windows" @@ -35,7 +35,7 @@ jobs: cxx: "g++-13", boost_toolset: gcc, cmake_generator: "Ninja", - llvm_tag: "llvmorg-18.1.8", + llvm_tag: "llvmorg-19.1.4", python_version: "3.10", os_version: "24.04", artifact_id: "RG3_Linux" @@ -48,7 +48,7 @@ jobs: cxx: "clang++", boost_toolset: gcc, cmake_generator: "Ninja", - llvm_tag: "llvmorg-18.1.8", + llvm_tag: "llvmorg-19.1.4", python_version: "3.10", os_version: "13", artifact_id: "RG3_macOS" diff --git a/LLVM/include/RG3/LLVM/Actions/CollectConstexprVariableEvalResultAction.h b/LLVM/include/RG3/LLVM/Actions/CollectConstexprVariableEvalResultAction.h new file mode 100644 index 0000000..53a3091 --- /dev/null +++ b/LLVM/include/RG3/LLVM/Actions/CollectConstexprVariableEvalResultAction.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include +#include + + + +namespace rg3::llvm::actions +{ + struct CollectConstexprVariableEvalResultAction : public clang::ASTFrontendAction + { + std::unordered_set aExpectedVariables {}; + std::unordered_map* pEvaluatedVariables { nullptr }; + + std::unique_ptr CreateASTConsumer(clang::CompilerInstance& /*compilerInstance*/, clang::StringRef /*file*/) override; + }; +} \ No newline at end of file diff --git a/LLVM/include/RG3/LLVM/CodeEvaluator.h b/LLVM/include/RG3/LLVM/CodeEvaluator.h new file mode 100644 index 0000000..b7a5277 --- /dev/null +++ b/LLVM/include/RG3/LLVM/CodeEvaluator.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace rg3::llvm +{ + using VariableValue = std::variant; + + struct CodeEvaluateResult + { + AnalyzerResult::CompilerIssuesVector vIssues; + std::unordered_map mOutputs; + + explicit operator bool() const noexcept; + }; + + class CodeEvaluator + { + public: + CodeEvaluator(); + CodeEvaluator(CompilerConfig compilerConfig); + + void setCompilerEnvironment(const CompilerEnvironment& env); + CompilerConfig& getCompilerConfig(); + + CodeEvaluateResult evaluateCode(const std::string& sCode, const std::vector& aCaptureOutputVariables); + + private: + std::optional m_env; + CompilerConfig m_compilerConfig; + std::string m_sSourceCode {}; + }; +} \ No newline at end of file diff --git a/LLVM/include/RG3/LLVM/CompilerInstanceFactory.h b/LLVM/include/RG3/LLVM/CompilerInstanceFactory.h new file mode 100644 index 0000000..8171498 --- /dev/null +++ b/LLVM/include/RG3/LLVM/CompilerInstanceFactory.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include +#include + +#include +#include +#include + + +namespace rg3::llvm +{ + struct CompilerInstanceFactory + { + static void makeInstance( + clang::CompilerInstance* pOutInstance, + const std::variant& sInput, + const CompilerConfig& sCompilerConfig, + const CompilerEnvironment* pCompilerEnv = nullptr); + }; +} \ No newline at end of file diff --git a/LLVM/include/RG3/LLVM/Consumers/CollectConstexprVariableEvalResult.h b/LLVM/include/RG3/LLVM/Consumers/CollectConstexprVariableEvalResult.h new file mode 100644 index 0000000..52c183f --- /dev/null +++ b/LLVM/include/RG3/LLVM/Consumers/CollectConstexprVariableEvalResult.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace rg3::llvm::consumers +{ + struct CollectConstexprVariableEvalResult : public clang::ASTConsumer + { + std::unordered_set aExpectedVariables {}; + std::unordered_map* pEvaluatedVariables { nullptr }; + + public: + CollectConstexprVariableEvalResult(); + + void HandleTranslationUnit(clang::ASTContext& ctx) override; + }; +} \ No newline at end of file diff --git a/LLVM/source/Actions/CollectConstexprVariableEvalResultAction.cpp b/LLVM/source/Actions/CollectConstexprVariableEvalResultAction.cpp new file mode 100644 index 0000000..0face2f --- /dev/null +++ b/LLVM/source/Actions/CollectConstexprVariableEvalResultAction.cpp @@ -0,0 +1,15 @@ +#include +#include + + +namespace rg3::llvm::actions +{ + std::unique_ptr CollectConstexprVariableEvalResultAction::CreateASTConsumer(clang::CompilerInstance&, clang::StringRef) + { + auto pConsumer = std::make_unique(); + pConsumer->aExpectedVariables = aExpectedVariables; + pConsumer->pEvaluatedVariables = pEvaluatedVariables; + + return std::move(pConsumer); + } +} \ No newline at end of file diff --git a/LLVM/source/CodeAnalyzer.cpp b/LLVM/source/CodeAnalyzer.cpp index 403962b..1d2daa6 100644 --- a/LLVM/source/CodeAnalyzer.cpp +++ b/LLVM/source/CodeAnalyzer.cpp @@ -1,81 +1,17 @@ #include #include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include /// Auto-generated by CMake - #include #include namespace rg3::llvm { - struct Visitor - { - clang::FrontendOptions& compilerOptions; - - void operator()(const std::filesystem::path& path) - { - std::string absolutePath = std::filesystem::absolute(path).string(); - - compilerOptions.Inputs.push_back( - clang::FrontendInputFile( - absolutePath, - clang::InputKind( - clang::Language::CXX, - clang::InputKind::Format::Source, - false, // NOT preprocessed - clang::InputKind::HeaderUnitKind::HeaderUnit_User, - true // is Header = true - ), - false // IsSystem = false - ) - ); - } - - void operator()(const std::string& buffer) - { - compilerOptions.Inputs.push_back( - clang::FrontendInputFile( - ::llvm::MemoryBufferRef( - ::llvm::StringRef(buffer.data()), - ::llvm::StringRef("id0.hpp") - ), - clang::InputKind( - clang::Language::CXX, - clang::InputKind::Format::Source, - false, // NOT preprocessed - clang::InputKind::HeaderUnitKind::HeaderUnit_User, - true // is Header = true - ), - false // IsSystem = false - ) - ); - } - }; - AnalyzerResult::operator bool() const { return std::count_if( @@ -145,8 +81,9 @@ namespace rg3::llvm AnalyzerResult CodeAnalyzer::analyze() { AnalyzerResult result; - const CompilerEnvironment* pCompilerEnv = nullptr; - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CompilerEnvironment* pCompilerEnv = nullptr; + // Run platform env detector if (!m_env.has_value()) { @@ -163,228 +100,15 @@ namespace rg3::llvm } pCompilerEnv = &m_env.value(); + clang::CompilerInstance compilerInstance {}; + CompilerInstanceFactory::makeInstance(&compilerInstance, m_source, m_compilerConfig, pCompilerEnv); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - clang::CompilerInstance compilerInstance; - compilerInstance.createDiagnostics(); - - // Create custom diagnostics consumer and pass it into CompilerInstance + // Add diagnostics consumer { auto errorCollector = std::make_unique(result); compilerInstance.getDiagnostics().setClient(errorCollector.release(), false); } - // Set up FileManager and SourceManager - compilerInstance.createFileManager(); - compilerInstance.createSourceManager(compilerInstance.getFileManager()); - - std::vector vProxyArgs = m_compilerConfig.vCompilerArgs; -#ifdef _WIN32 - vProxyArgs.emplace_back("-fms-extensions"); - vProxyArgs.emplace_back("-fdelayed-template-parsing"); - vProxyArgs.emplace_back("-fms-compatibility-version=19"); -#endif - - if (auto it = std::find(vProxyArgs.begin(), vProxyArgs.end(), "-x"); it != vProxyArgs.end()) - { - // need to remove this iter and next - auto next = std::next(it); - - if (next != std::end(vProxyArgs)) - { - // remove next - vProxyArgs.erase(next); - } - - // and remove this - vProxyArgs.erase(it); - } - - // We will append "-x c++-header" option always - vProxyArgs.emplace_back("-x"); - vProxyArgs.emplace_back("c++-header"); - - std::vector vCompilerArgs; - vCompilerArgs.resize(vProxyArgs.size()); - - for (size_t i = 0; i < vProxyArgs.size(); i++) - { - vCompilerArgs[i] = vProxyArgs[i].c_str(); - } - - // Set up CompilerInvocation - auto invocation = std::make_shared(); - clang::CompilerInvocation::CreateFromArgs( - *invocation, - ::llvm::ArrayRef(vCompilerArgs.data(), vCompilerArgs.size()), - compilerInstance.getDiagnostics() - ); - - // Use C++20 - clang::LangStandard::Kind langKind; - auto& langOptions = invocation->getLangOpts(); - - langOptions.CPlusPlus = 1; - - switch (m_compilerConfig.cppStandard) - { - case CxxStandard::CC_11: - langOptions.CPlusPlus11 = 1; - langKind = clang::LangStandard::Kind::lang_cxx11; - break; - case CxxStandard::CC_14: - langOptions.CPlusPlus14 = 1; - langKind = clang::LangStandard::Kind::lang_cxx14; - break; - case CxxStandard::CC_17: - langOptions.CPlusPlus17 = 1; - langKind = clang::LangStandard::Kind::lang_cxx17; - break; - case CxxStandard::CC_20: - langOptions.CPlusPlus20 = 1; - langKind = clang::LangStandard::Kind::lang_cxx20; - break; - case CxxStandard::CC_23: - langOptions.CPlusPlus23 = 1; - langKind = clang::LangStandard::Kind::lang_cxx23; - break; - case CxxStandard::CC_26: - langOptions.CPlusPlus26 = 1; - langKind = clang::LangStandard::Kind::lang_cxx26; - break; - default: - langOptions.CPlusPlus11 = 1; - langKind = clang::LangStandard::Kind::lang_cxx11; - break; - } - - langOptions.LangStd = langKind; - langOptions.IsHeaderFile = true; // NOTE: Maybe we should use flag here? - -#ifdef _WIN32 - compilerInstance.getPreprocessorOpts().addMacroDef("_MSC_VER=1932"); - compilerInstance.getPreprocessorOpts().addMacroDef("_MSC_FULL_VER=193231329"); - compilerInstance.getPreprocessorOpts().addMacroDef("_MSC_EXTENSIONS"); - - /** - * Workaround: it's workaround for MSVC 2022 with yvals_core.h which send static assert when Clang version less than 17.x.x - * Example : static assertion failed: error STL1000: Unexpected compiler version, expected Clang 17.0.0 or newer. at C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.40.33807\include\yvals_core.h:898 - * - * This macro block static assertion and should help us, but this part of code MUST be removed after RG3 migrates to latest LLVM & Clang - */ - compilerInstance.getPreprocessorOpts().addMacroDef("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH=1"); -#endif - -#ifdef __APPLE__ - // This should be enough? - compilerInstance.getPreprocessorOpts().addMacroDef("__GCC_HAVE_DWARF2_CFI_ASM=1"); -#endif - - std::shared_ptr targetOpts = nullptr; - - // Setup triple -#if !defined(__APPLE__) - if (pCompilerEnv->triple.empty()) - { - // Use default triple - targetOpts = std::make_shared(); - targetOpts->Triple = ::llvm::sys::getDefaultTargetTriple(); - clang::TargetInfo* targetInfo = clang::TargetInfo::CreateTargetInfo(compilerInstance.getDiagnostics(), targetOpts); - compilerInstance.setTarget(targetInfo); - - auto triple = targetInfo->getTriple(); - - std::vector vIncs; - clang::LangOptions::setLangDefaults(langOptions, clang::Language::CXX, triple, vIncs, langKind); - } - else - { - targetOpts = std::make_shared(); - targetOpts->Triple = ::llvm::Triple::normalize(pCompilerEnv->triple); - clang::TargetInfo* targetInfo = clang::TargetInfo::CreateTargetInfo(compilerInstance.getDiagnostics(), targetOpts); - compilerInstance.setTarget(targetInfo); - - auto triple = targetInfo->getTriple(); - - std::vector vIncs; - clang::LangOptions::setLangDefaults(langOptions, clang::Language::CXX, triple, vIncs, langKind); - } -#else - // On Apple we should use default triple instead of detect it at runtime - ::llvm::Triple triple(::llvm::sys::getDefaultTargetTriple()); - compilerInstance.getTargetOpts().Triple = triple.str(); - compilerInstance.setTarget(clang::TargetInfo::CreateTargetInfo(compilerInstance.getDiagnostics(), std::make_shared(compilerInstance.getTargetOpts()))); -#endif - - compilerInstance.setInvocation(invocation); - - // Set up FrontendOptions - clang::FrontendOptions &opts = compilerInstance.getFrontendOpts(); - opts.ProgramAction = clang::frontend::ParseSyntaxOnly; - opts.SkipFunctionBodies = static_cast(m_compilerConfig.bSkipFunctionBodies); - - opts.Inputs.clear(); - - // Prepare compiler instance - { - Visitor v { opts }; - std::visit(v, m_source); - } - - // Set macros - clang::PreprocessorOptions& preprocessorOptions = compilerInstance.getPreprocessorOpts(); - for (const auto& compilerDef : m_compilerConfig.vCompilerDefs) - { - preprocessorOptions.addMacroDef(compilerDef); - } - - // Add builtins - preprocessorOptions.addMacroDef("__RG3__=1"); - preprocessorOptions.addMacroDef("__RG3_COMMIT__=\"" RG3_BUILD_HASH "\""); - preprocessorOptions.addMacroDef("__RG3_BUILD_DATE__=\"" __DATE__ "\""); - -#ifdef __APPLE__ - // For apple only. They cares about GNUC? Idk & I don't care - preprocessorOptions.addMacroDef("__GNUC__=4"); -#endif - - // Setup header dirs source - clang::HeaderSearchOptions& headerSearchOptions = compilerInstance.getHeaderSearchOpts(); - { - for (const auto& sysInc : pCompilerEnv->config.vSystemIncludes) - { - const auto absolutePath = std::filesystem::absolute(sysInc.sFsLocation); - - clang::frontend::IncludeDirGroup group = clang::frontend::IncludeDirGroup::Angled; - - if (sysInc.eKind == IncludeKind::IK_SYSROOT) - { - // ignore sysroot here - continue; - } - - if (sysInc.eKind == IncludeKind::IK_SYSTEM) - { - group = clang::frontend::IncludeDirGroup::System; - } - - if (sysInc.eKind == IncludeKind::IK_C_SYSTEM) - { - group = clang::frontend::IncludeDirGroup::ExternCSystem; - } - - headerSearchOptions.AddPath(absolutePath.string(), group, false, true); - } - - for (const auto& incInfo : m_compilerConfig.vIncludes) - { - // Convert path to absolute - const auto absolutePath = std::filesystem::absolute(incInfo.sFsLocation); - headerSearchOptions.AddPath(absolutePath.string(), clang::frontend::IncludeDirGroup::Angled, false, true); - } - } - // Run actions { rg3::llvm::actions::ExtractTypesFromTUAction findTypesAction { result.vFoundTypes, m_compilerConfig }; diff --git a/LLVM/source/CodeEvaluator.cpp b/LLVM/source/CodeEvaluator.cpp new file mode 100644 index 0000000..0d04f44 --- /dev/null +++ b/LLVM/source/CodeEvaluator.cpp @@ -0,0 +1,93 @@ +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include + + +namespace rg3::llvm +{ + CodeEvaluateResult::operator bool() const noexcept + { + return std::count_if( + vIssues.begin(), + vIssues.end(), + [](const AnalyzerResult::CompilerIssue& issue) -> bool { + return issue.kind != AnalyzerResult::CompilerIssue::IssueKind::IK_INFO && + issue.kind != AnalyzerResult::CompilerIssue::IssueKind::IK_NONE; + }) == 0; + } + + CodeEvaluator::CodeEvaluator() = default; + + CodeEvaluator::CodeEvaluator(rg3::llvm::CompilerConfig compilerConfig) + : m_compilerConfig(std::move(compilerConfig)) + { + } + + void CodeEvaluator::setCompilerEnvironment(const rg3::llvm::CompilerEnvironment& env) + { + m_env = env; + } + + CompilerConfig& CodeEvaluator::getCompilerConfig() + { + return m_compilerConfig; + } + + CodeEvaluateResult CodeEvaluator::evaluateCode(const std::string& sCode, const std::vector& aCaptureOutputVariables) + { + CodeEvaluateResult sResult {}; + AnalyzerResult sTempResult {}; + CompilerEnvironment* pCompilerEnv = nullptr; + + m_sSourceCode = sCode; + + // Run platform env detector + if (!m_env.has_value()) + { + const auto compilerEnvironment = CompilerConfigDetector::detectSystemCompilerEnvironment(); + if (auto pEnvFailure = std::get_if(&compilerEnvironment)) + { + // Fatal error + sResult.vIssues.emplace_back(AnalyzerResult::CompilerIssue { AnalyzerResult::CompilerIssue::IssueKind::IK_ERROR, m_sSourceCode, 0, 0, pEnvFailure->message }); + return sResult; + } + + // Override env + m_env = *std::get_if(&compilerEnvironment); + } + + pCompilerEnv = &m_env.value(); + clang::CompilerInstance compilerInstance {}; + CompilerInstanceFactory::makeInstance(&compilerInstance, m_sSourceCode, m_compilerConfig, pCompilerEnv); + + // Add diagnostics consumer + { + auto errorCollector = std::make_unique(sTempResult); + compilerInstance.getDiagnostics().setClient(errorCollector.release(), false); + } + + // Run actions + { + rg3::llvm::actions::CollectConstexprVariableEvalResultAction collectConstexprVariableEvalResultAction {}; + collectConstexprVariableEvalResultAction.aExpectedVariables = std::unordered_set { aCaptureOutputVariables.begin(), aCaptureOutputVariables.end() }; + collectConstexprVariableEvalResultAction.pEvaluatedVariables = &sResult.mOutputs; + + compilerInstance.ExecuteAction(collectConstexprVariableEvalResultAction); + } + + // Copy result + std::copy(sTempResult.vIssues.begin(), sTempResult.vIssues.end(), std::back_inserter(sResult.vIssues)); + + return sResult; + } +} \ No newline at end of file diff --git a/LLVM/source/CompilerInstanceFactory.cpp b/LLVM/source/CompilerInstanceFactory.cpp new file mode 100644 index 0000000..e1f0989 --- /dev/null +++ b/LLVM/source/CompilerInstanceFactory.cpp @@ -0,0 +1,289 @@ +#include +#include /// Auto-generated by CMake + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace rg3::llvm +{ + struct Visitor + { + clang::FrontendOptions& compilerOptions; + clang::CompilerInstance* pCompilerInstance { nullptr }; + + void operator()(const std::filesystem::path& path) + { + std::string absolutePath = std::filesystem::absolute(path).string(); + + compilerOptions.Inputs.push_back( + clang::FrontendInputFile( + absolutePath, + clang::InputKind( + clang::Language::CXX, + clang::InputKind::Format::Source, + false, // NOT preprocessed + clang::InputKind::HeaderUnitKind::HeaderUnit_User, + true // is Header = true + ), + false // IsSystem = false + ) + ); + } + + void operator()(const std::string& buffer) + { + std::string sanitizedBuffer; + std::remove_copy_if( + buffer.begin(), buffer.end(), + std::back_inserter(sanitizedBuffer), + [](char c) { return c == '\0'; } + ); + + auto pMemBuffer = ::llvm::MemoryBuffer::getMemBufferCopy(sanitizedBuffer, "id0.hpp"); + pCompilerInstance->getPreprocessorOpts().addRemappedFile("id0.hpp", pMemBuffer.release()); + + compilerOptions.Inputs.push_back(clang::FrontendInputFile("id0.hpp", clang::Language::CXX)); + } + }; + + void CompilerInstanceFactory::makeInstance(clang::CompilerInstance* pOutInstance, + const std::variant& sInput, + const rg3::llvm::CompilerConfig& sCompilerConfig, + const rg3::llvm::CompilerEnvironment* pCompilerEnv) + { + pOutInstance->createDiagnostics(); + + // Set up FileManager and SourceManager + pOutInstance->createFileManager(); + pOutInstance->createSourceManager(pOutInstance->getFileManager()); + + std::vector vProxyArgs = sCompilerConfig.vCompilerArgs; +#ifdef _WIN32 + vProxyArgs.emplace_back("-fms-extensions"); + vProxyArgs.emplace_back("-fdelayed-template-parsing"); + vProxyArgs.emplace_back("-fms-compatibility-version=19"); +#endif + + if (auto it = std::find(vProxyArgs.begin(), vProxyArgs.end(), "-x"); it != vProxyArgs.end()) + { + // need to remove this iter and next + auto next = std::next(it); + + if (next != std::end(vProxyArgs)) + { + // remove next + vProxyArgs.erase(next); + } + + // and remove this + vProxyArgs.erase(it); + } + + // We will append "-x c++-header" option always + vProxyArgs.emplace_back("-x"); + vProxyArgs.emplace_back("c++-header"); + + std::vector vCompilerArgs; + vCompilerArgs.resize(vProxyArgs.size()); + + for (size_t i = 0; i < vProxyArgs.size(); i++) + { + vCompilerArgs[i] = vProxyArgs[i].c_str(); + } + + // Set up CompilerInvocation + auto invocation = std::make_shared(); + clang::CompilerInvocation::CreateFromArgs( + *invocation, + ::llvm::ArrayRef(vCompilerArgs.data(), vCompilerArgs.size()), + pOutInstance->getDiagnostics() + ); + + // Use C++20 + clang::LangStandard::Kind langKind; + auto& langOptions = invocation->getLangOpts(); + + langOptions.CPlusPlus = 1; + + switch (sCompilerConfig.cppStandard) + { + case CxxStandard::CC_11: + langOptions.CPlusPlus11 = 1; + langKind = clang::LangStandard::Kind::lang_cxx11; + break; + case CxxStandard::CC_14: + langOptions.CPlusPlus14 = 1; + langKind = clang::LangStandard::Kind::lang_cxx14; + break; + case CxxStandard::CC_17: + langOptions.CPlusPlus17 = 1; + langKind = clang::LangStandard::Kind::lang_cxx17; + break; + case CxxStandard::CC_20: + langOptions.CPlusPlus20 = 1; + langKind = clang::LangStandard::Kind::lang_cxx20; + break; + case CxxStandard::CC_23: + langOptions.CPlusPlus23 = 1; + langKind = clang::LangStandard::Kind::lang_cxx23; + break; + case CxxStandard::CC_26: + langOptions.CPlusPlus26 = 1; + langKind = clang::LangStandard::Kind::lang_cxx26; + break; + default: + langOptions.CPlusPlus11 = 1; + langKind = clang::LangStandard::Kind::lang_cxx11; + break; + } + + langOptions.LangStd = langKind; + langOptions.IsHeaderFile = true; // NOTE: Maybe we should use flag here? + +#ifdef _WIN32 + pOutInstance->getPreprocessorOpts().addMacroDef("_MSC_VER=1932"); + pOutInstance->getPreprocessorOpts().addMacroDef("_MSC_FULL_VER=193231329"); + pOutInstance->getPreprocessorOpts().addMacroDef("_MSC_EXTENSIONS"); + + /** + * Workaround: it's workaround for MSVC 2022 with yvals_core.h which send static assert when Clang version less than 17.x.x + * Example : static assertion failed: error STL1000: Unexpected compiler version, expected Clang 17.0.0 or newer. at C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.40.33807\include\yvals_core.h:898 + * + * This macro block static assertion and should help us, but this part of code MUST be removed after RG3 migrates to latest LLVM & Clang + */ + pOutInstance->getPreprocessorOpts().addMacroDef("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH=1"); +#endif + +#ifdef __APPLE__ + // This should be enough? + pOutInstance->getPreprocessorOpts().addMacroDef("__GCC_HAVE_DWARF2_CFI_ASM=1"); +#endif + + std::shared_ptr targetOpts = nullptr; + + // Setup triple +#if !defined(__APPLE__) + if (pCompilerEnv->triple.empty()) + { + // Use default triple + targetOpts = std::make_shared(); + targetOpts->Triple = ::llvm::sys::getDefaultTargetTriple(); + clang::TargetInfo* targetInfo = clang::TargetInfo::CreateTargetInfo(pOutInstance->getDiagnostics(), targetOpts); + pOutInstance->setTarget(targetInfo); + + auto triple = targetInfo->getTriple(); + + std::vector vIncs; + clang::LangOptions::setLangDefaults(langOptions, clang::Language::CXX, triple, vIncs, langKind); + } + else + { + targetOpts = std::make_shared(); + targetOpts->Triple = ::llvm::Triple::normalize(pCompilerEnv->triple); + clang::TargetInfo* targetInfo = clang::TargetInfo::CreateTargetInfo(pOutInstance->getDiagnostics(), targetOpts); + pOutInstance->setTarget(targetInfo); + + auto triple = targetInfo->getTriple(); + + std::vector vIncs; + clang::LangOptions::setLangDefaults(langOptions, clang::Language::CXX, triple, vIncs, langKind); + } +#else + // On Apple we should use default triple instead of detect it at runtime + ::llvm::Triple triple(::llvm::sys::getDefaultTargetTriple()); + pOutInstance->getTargetOpts().Triple = triple.str(); + pOutInstance->setTarget(clang::TargetInfo::CreateTargetInfo(pOutInstance->getDiagnostics(), std::make_shared(pOutInstance->getTargetOpts()))); +#endif + + pOutInstance->setInvocation(invocation); + + // Set up FrontendOptions + clang::FrontendOptions &opts = pOutInstance->getFrontendOpts(); + opts.ProgramAction = clang::frontend::ParseSyntaxOnly; + opts.SkipFunctionBodies = static_cast(sCompilerConfig.bSkipFunctionBodies); + + opts.Inputs.clear(); + + // Prepare compiler instance + { + Visitor v { opts, pOutInstance }; + std::visit(v, sInput); + } + + // Set macros + clang::PreprocessorOptions& preprocessorOptions = pOutInstance->getPreprocessorOpts(); + for (const auto& compilerDef : sCompilerConfig.vCompilerDefs) + { + preprocessorOptions.addMacroDef(compilerDef); + } + + // Add builtins + preprocessorOptions.addMacroDef("__RG3__=1"); + preprocessorOptions.addMacroDef("__RG3_COMMIT__=\"" RG3_BUILD_HASH "\""); + preprocessorOptions.addMacroDef("__RG3_BUILD_DATE__=\"" __DATE__ "\""); + +#ifdef __APPLE__ + // For apple only. They cares about GNUC? Idk & I don't care + preprocessorOptions.addMacroDef("__GNUC__=4"); +#endif + + // Setup header dirs source + clang::HeaderSearchOptions& headerSearchOptions = pOutInstance->getHeaderSearchOpts(); + { + for (const auto& sysInc : pCompilerEnv->config.vSystemIncludes) + { + const auto absolutePath = std::filesystem::absolute(sysInc.sFsLocation); + + clang::frontend::IncludeDirGroup group = clang::frontend::IncludeDirGroup::Angled; + + if (sysInc.eKind == IncludeKind::IK_SYSROOT) + { + // ignore sysroot here + continue; + } + + if (sysInc.eKind == IncludeKind::IK_SYSTEM) + { + group = clang::frontend::IncludeDirGroup::System; + } + + if (sysInc.eKind == IncludeKind::IK_C_SYSTEM) + { + group = clang::frontend::IncludeDirGroup::ExternCSystem; + } + + headerSearchOptions.AddPath(absolutePath.string(), group, false, true); + } + + for (const auto& incInfo : sCompilerConfig.vIncludes) + { + // Convert path to absolute + const auto absolutePath = std::filesystem::absolute(incInfo.sFsLocation); + headerSearchOptions.AddPath(absolutePath.string(), clang::frontend::IncludeDirGroup::Angled, false, true); + } + } + + // small self check + assert(pOutInstance->hasDiagnostics() && "Diagnostics not set up!"); + assert(pOutInstance->hasTarget() && "Target not set up!"); + assert(pOutInstance->hasFileManager() && "FileManager not set up!"); + } +} \ No newline at end of file diff --git a/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp b/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp new file mode 100644 index 0000000..8334b88 --- /dev/null +++ b/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include + + +namespace rg3::llvm::consumers +{ + class ConstexprVisitor : public clang::RecursiveASTVisitor { + private: + std::unordered_set m_aExpectedVariables {}; + std::unordered_map* m_pEvaluatedVariables { nullptr }; + clang::ASTContext& m_sContext; + + public: + explicit ConstexprVisitor(clang::ASTContext& context, std::unordered_map* pEvaluatedValues, const std::unordered_set& aExpectedVariables) + : m_sContext(context), m_aExpectedVariables(aExpectedVariables), m_pEvaluatedVariables(pEvaluatedValues) + { + } + + bool VisitVarDecl(clang::VarDecl* pVarDecl) + { + std::string sName = pVarDecl->getNameAsString(); + + if (pVarDecl->isConstexpr() && m_aExpectedVariables.contains(sName)) + { + auto* pEvaluated = pVarDecl->getEvaluatedValue(); + if (pEvaluated) + { + if (pVarDecl->getType()->isBooleanType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getInt().getBoolValue(); + } + else if (pVarDecl->getType()->isSignedIntegerType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getInt().getExtValue(); + } + else if (pVarDecl->getType()->isUnsignedIntegerType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getInt().getZExtValue(); + } + else if (pVarDecl->getType()->isFloatingType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getFloat().convertToFloat(); + } + else if (pVarDecl->getType()->isDoubleType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getFloat().convertToDouble(); + } + else if (pVarDecl->getType()->isPointerType() && pVarDecl->getType()->getPointeeType()->isCharType()) + { + if (auto pStrValue = ::llvm::dyn_cast(pEvaluated->getLValueBase().get())) + { + (*m_pEvaluatedVariables)[sName] = pStrValue->getString().str(); + } + } + } + } + return true; // Continue traversal + } + }; + + CollectConstexprVariableEvalResult::CollectConstexprVariableEvalResult() = default; + + void CollectConstexprVariableEvalResult::HandleTranslationUnit(clang::ASTContext& ctx) + { + ConstexprVisitor visitor { ctx, pEvaluatedVariables, aExpectedVariables }; + visitor.TraverseDecl(ctx.getTranslationUnitDecl()); + } +} \ No newline at end of file diff --git a/Tests/Unit/source/Tests_CodeEvaluator.cpp b/Tests/Unit/source/Tests_CodeEvaluator.cpp new file mode 100644 index 0000000..a273e46 --- /dev/null +++ b/Tests/Unit/source/Tests_CodeEvaluator.cpp @@ -0,0 +1,57 @@ +#include + +#include +#include + + +class Tests_CodeEvaluator : public ::testing::Test +{ + protected: + void SetUp() override + { + g_Eval = std::make_unique(); + g_Eval->getCompilerConfig().cppStandard = rg3::llvm::CxxStandard::CC_20; + g_Eval->getCompilerConfig().bSkipFunctionBodies = true; + } + + void TearDown() override + { + g_Eval = nullptr; + } + + protected: + std::unique_ptr g_Eval { nullptr }; +}; + + +TEST_F(Tests_CodeEvaluator, SimpleUsage) +{ + auto res = g_Eval->evaluateCode("static constexpr int aResult = 32 * 2;", { "aResult" }); + ASSERT_TRUE(res.vIssues.empty()) << "No issues expected to be here"; + ASSERT_EQ(res.mOutputs.size(), 1) << "Expected to have 1 output"; + ASSERT_TRUE(res.mOutputs.contains("aResult")) << "aResult should be here"; + ASSERT_TRUE(std::holds_alternative(res.mOutputs["aResult"])); + ASSERT_EQ(std::get(res.mOutputs["aResult"]), 64) << "Must be 64!"; +} + +TEST_F(Tests_CodeEvaluator, ClassInheritanceConstexprTest) +{ + auto res = g_Eval->evaluateCode(R"( +#include + +struct MyCoolBaseClass {}; + +struct MyDniweClass {}; +struct MyBuddyClass : MyCoolBaseClass {}; + +constexpr bool g_bDniweInherited = std::is_base_of_v; +constexpr bool g_bBuddyInherited = std::is_base_of_v; +)", { "g_bDniweInherited", "g_bBuddyInherited" }); + + ASSERT_TRUE(res.vIssues.empty()) << "No issues expected to be here"; + ASSERT_EQ(res.mOutputs.size(), 2) << "Expected to have 2 outputs"; + ASSERT_TRUE(res.mOutputs.contains("g_bDniweInherited")) << "g_bDniweInherited should be here"; + ASSERT_TRUE(res.mOutputs.contains("g_bBuddyInherited")) << "g_bBuddyInherited should be here"; + ASSERT_TRUE(std::get(res.mOutputs["g_bBuddyInherited"])) << "Buddy should be ok!"; + ASSERT_FALSE(std::get(res.mOutputs["g_bDniweInherited"])) << "Dniwe should be bad!"; +} \ No newline at end of file From f93a30ff8a706e8d8f3f29b0c5412cce4cc70154 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 30 Nov 2024 20:10:32 +0300 Subject: [PATCH 2/8] [0.0.23] Added more tests. Added macro definition __RG3_CODE_EVAL__ in code eval mode. Fixed string lvalue capture. --- LLVM/source/CodeEvaluator.cpp | 5 +- .../CollectConstexprVariableEvalResult.cpp | 7 +- Tests/Unit/source/Tests_CodeEvaluator.cpp | 89 ++++++++++++++++++- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/LLVM/source/CodeEvaluator.cpp b/LLVM/source/CodeEvaluator.cpp index 0d04f44..181e940 100644 --- a/LLVM/source/CodeEvaluator.cpp +++ b/LLVM/source/CodeEvaluator.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -70,6 +70,9 @@ namespace rg3::llvm clang::CompilerInstance compilerInstance {}; CompilerInstanceFactory::makeInstance(&compilerInstance, m_sSourceCode, m_compilerConfig, pCompilerEnv); + // Add extra definition in our case + compilerInstance.getPreprocessorOpts().addMacroDef("__RG3_CODE_EVAL__=1"); + // Add diagnostics consumer { auto errorCollector = std::make_unique(sTempResult); diff --git a/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp b/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp index 8334b88..55cd2a7 100644 --- a/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp +++ b/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp @@ -49,9 +49,12 @@ namespace rg3::llvm::consumers } else if (pVarDecl->getType()->isPointerType() && pVarDecl->getType()->getPointeeType()->isCharType()) { - if (auto pStrValue = ::llvm::dyn_cast(pEvaluated->getLValueBase().get())) + if (pEvaluated->isLValue()) { - (*m_pEvaluatedVariables)[sName] = pStrValue->getString().str(); + if (auto pStrValue = ::llvm::dyn_cast(pEvaluated->getLValueBase().get())) + { + (*m_pEvaluatedVariables)[sName] = pStrValue->getString().str(); + } } } } diff --git a/Tests/Unit/source/Tests_CodeEvaluator.cpp b/Tests/Unit/source/Tests_CodeEvaluator.cpp index a273e46..908de92 100644 --- a/Tests/Unit/source/Tests_CodeEvaluator.cpp +++ b/Tests/Unit/source/Tests_CodeEvaluator.cpp @@ -10,7 +10,7 @@ class Tests_CodeEvaluator : public ::testing::Test void SetUp() override { g_Eval = std::make_unique(); - g_Eval->getCompilerConfig().cppStandard = rg3::llvm::CxxStandard::CC_20; + g_Eval->getCompilerConfig().cppStandard = rg3::llvm::CxxStandard::CC_20; //23 std raise exceptions on macOS! g_Eval->getCompilerConfig().bSkipFunctionBodies = true; } @@ -54,4 +54,91 @@ constexpr bool g_bBuddyInherited = std::is_base_of_v(res.mOutputs["g_bBuddyInherited"])) << "Buddy should be ok!"; ASSERT_FALSE(std::get(res.mOutputs["g_bDniweInherited"])) << "Dniwe should be bad!"; +} + +constexpr int fibonacci(int n) +{ + return n < 1 ? -1 : (n == 1 || n == 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2)); +} + +TEST_F(Tests_CodeEvaluator, FibonacciConstexprEvalTest) +{ + auto res = g_Eval->evaluateCode(R"( +constexpr int fibonacci(int n) +{ + return n < 1 ? -1 : (n == 1 || n == 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2)); +} + +constexpr auto g_iFib9 = fibonacci(9); +constexpr auto g_iFib10 = fibonacci(10); +constexpr auto g_iFib16 = fibonacci(16); +)", { "g_iFib9", "g_iFib10", "g_iFib16" }); + + ASSERT_TRUE(res.vIssues.empty()) << "No issues expected to be here"; + ASSERT_EQ(res.mOutputs.size(), 3) << "Expected to have 3 outputs"; + ASSERT_TRUE(res.mOutputs.contains("g_iFib9")) << "g_iFib9 should be here"; + ASSERT_TRUE(res.mOutputs.contains("g_iFib10")) << "g_iFib10 should be here"; + ASSERT_TRUE(res.mOutputs.contains("g_iFib16")) << "g_iFib16 should be here"; + ASSERT_EQ(std::get(res.mOutputs["g_iFib9"]), fibonacci(9)) << "FIB(9) FAILED"; + ASSERT_EQ(std::get(res.mOutputs["g_iFib10"]), fibonacci(10)) << "FIB(10) FAILED"; + ASSERT_EQ(std::get(res.mOutputs["g_iFib16"]), fibonacci(16)) << "FIB(16) FAILED"; +} + +TEST_F(Tests_CodeEvaluator, SampleString) +{ + auto res = g_Eval->evaluateCode(R"( +#ifdef __RG3_CODE_EVAL__ +constexpr const char* psFileID = __FILE__; +#else +# error "What the hell is going on here???" +#endif +)", { "psFileID" }); + + ASSERT_TRUE(res.vIssues.empty()) << "No issues expected to be here"; + ASSERT_TRUE(res.mOutputs.contains("psFileID")); + ASSERT_EQ(std::get(res.mOutputs["psFileID"]), "id0.hpp"); +} + +constexpr std::uint32_t FNV1aPrime = 16777619u; +constexpr std::uint32_t FNV1aOffsetBasis = 2166136261u; + +constexpr std::uint32_t fnv1aHash(const char* str, std::size_t length, std::uint32_t hash = FNV1aOffsetBasis) { + return (length == 0) + ? hash + : fnv1aHash(str + 1, length - 1, (hash ^ static_cast(*str)) * FNV1aPrime); +} + +constexpr std::uint32_t fnv1aHash(const char* str) { + std::size_t length = 0; + while (str[length] != '\0') ++length; + return fnv1aHash(str, length); +} + +TEST_F(Tests_CodeEvaluator, HashComputeExample) +{ + auto res = g_Eval->evaluateCode(R"( +#include +#include + +constexpr std::uint32_t FNV1aPrime = 16777619u; +constexpr std::uint32_t FNV1aOffsetBasis = 2166136261u; + +constexpr std::uint32_t fnv1aHash(const char* str, std::size_t length, std::uint32_t hash = FNV1aOffsetBasis) { + return (length == 0) + ? hash + : fnv1aHash(str + 1, length - 1, (hash ^ static_cast(*str)) * FNV1aPrime); +} + +constexpr std::uint32_t fnv1aHash(const char* str) { + std::size_t length = 0; + while (str[length] != '\0') ++length; + return fnv1aHash(str, length); +} + +constexpr const char* testString = "HelloWorldThisIsSamplePr0gr7mmForTe$tHashing"; +constexpr std::uint32_t testHash = fnv1aHash(testString); +)", { "testHash" }); + + ASSERT_TRUE(res.vIssues.empty()) << "No issues expected to be here"; + ASSERT_EQ(std::get(res.mOutputs["testHash"]), fnv1aHash("HelloWorldThisIsSamplePr0gr7mmForTe$tHashing")); } \ No newline at end of file From 1faba2861651f95c1f57d51d4a862fc444b22730 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 30 Nov 2024 20:46:41 +0300 Subject: [PATCH 3/8] [0.0.23] Added python binding and small test --- LLVM/include/RG3/LLVM/CodeEvaluator.h | 5 +- PyBind/include/RG3/PyBind/PyAnalyzerContext.h | 1 + PyBind/rg3py.pyi | 10 +++- PyBind/source/PyBind.cpp | 51 ++++++++++++++++++- Tests/PyIntegration/tests.py | 27 +++++++++- 5 files changed, 90 insertions(+), 4 deletions(-) diff --git a/LLVM/include/RG3/LLVM/CodeEvaluator.h b/LLVM/include/RG3/LLVM/CodeEvaluator.h index b7a5277..1244c98 100644 --- a/LLVM/include/RG3/LLVM/CodeEvaluator.h +++ b/LLVM/include/RG3/LLVM/CodeEvaluator.h @@ -3,6 +3,9 @@ #include #include #include + +#include + #include #include #include @@ -23,7 +26,7 @@ namespace rg3::llvm explicit operator bool() const noexcept; }; - class CodeEvaluator + class CodeEvaluator : public boost::noncopyable { public: CodeEvaluator(); diff --git a/PyBind/include/RG3/PyBind/PyAnalyzerContext.h b/PyBind/include/RG3/PyBind/PyAnalyzerContext.h index e081d97..7cde5ce 100644 --- a/PyBind/include/RG3/PyBind/PyAnalyzerContext.h +++ b/PyBind/include/RG3/PyBind/PyAnalyzerContext.h @@ -76,6 +76,7 @@ namespace rg3::pybind [[nodiscard]] const boost::python::list& getFoundIssues() const; [[nodiscard]] const boost::python::list& getFoundTypes() const; + [[nodiscard]] const rg3::llvm::CompilerConfig& getCompilerConfig() const { return m_compilerConfig; } public: /** diff --git a/PyBind/rg3py.pyi b/PyBind/rg3py.pyi index 18daa57..7268d85 100644 --- a/PyBind/rg3py.pyi +++ b/PyBind/rg3py.pyi @@ -2,7 +2,7 @@ This file contains all public available symbols & definitions for PyBind (rg3py.pyd) Follow PyBind/source/PyBind.cpp for details """ -from typing import List, Union, Optional +from typing import List, Union, Optional, Dict class CppStandard: @@ -401,6 +401,14 @@ class AnalyzerContext: def analyze(self) -> bool: ... + def make_evaluator(self) -> CodeEvaluator: ... + + +class CodeEvaluator: + def __init__(self): ... + + def eval(self, code: str, capture: List[str]) -> Union[List[CppCompilerIssue], Dict[str, any]]: ... + class CppCompilerIssueKind: IK_NONE = 0 diff --git a/PyBind/source/PyBind.cpp b/PyBind/source/PyBind.cpp index 51683a8..ac36cbb 100644 --- a/PyBind/source/PyBind.cpp +++ b/PyBind/source/PyBind.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include #include #include @@ -20,7 +22,6 @@ #include - using namespace boost::python; @@ -163,6 +164,49 @@ namespace rg3::pybind::wrappers { return boost::python::str(sInfo.sPrettyName); } + + static boost::python::object CodeEvaluator_eval(rg3::llvm::CodeEvaluator& sEval, const std::string& sCode, const boost::python::list& aCapture) + { + std::vector aCaptureList {}; + aCaptureList.reserve(len(aCapture)); + + for (int i = 0; i < len(aCapture); ++i) + { + aCaptureList.push_back(boost::python::extract(aCapture[i])); + } + + // Invoke originals + auto sEvalResult = sEval.evaluateCode(sCode, aCaptureList); + if (!sEvalResult) + { + // Error! + boost::python::list aIssues {}; + + for (const auto& sIssue : sEvalResult.vIssues) + { + aIssues.append(sIssue); + } + + return aIssues; + } + + // Make dict + boost::python::dict result {}; + + for (const auto& [sKey, sValue] : sEvalResult.mOutputs) + { + std::visit([&result, &sKey](auto&& v) { + result[sKey] = v; + }, sValue); + } + + return result; + } + + static boost::shared_ptr PyAnalyzerContext_makeEvaluator(const rg3::pybind::PyAnalyzerContext& sContext) + { + return boost::shared_ptr(new rg3::llvm::CodeEvaluator(sContext.getCompilerConfig())); + } } @@ -436,6 +480,7 @@ BOOST_PYTHON_MODULE(rg3py) .def("set_compiler_args", &rg3::pybind::PyAnalyzerContext::setCompilerArgs) .def("set_compiler_defs", &rg3::pybind::PyAnalyzerContext::setCompilerDefs) .def("analyze", &rg3::pybind::PyAnalyzerContext::analyze) + .def("make_evaluator", &rg3::pybind::wrappers::PyAnalyzerContext_makeEvaluator) // Resolvers .def("get_type_by_reference", &rg3::pybind::PyAnalyzerContext::pyGetTypeOfTypeReference) @@ -448,4 +493,8 @@ BOOST_PYTHON_MODULE(rg3py) .def("detect_system_include_sources", &rg3::pybind::PyClangRuntime::detectSystemIncludeSources) .staticmethod("detect_system_include_sources") ; + + class_>("CodeEvaluator", "Eval constexpr C++ code and provide access to result values", boost::python::init<>()) + .def("eval", &rg3::pybind::wrappers::CodeEvaluator_eval) + ; } \ No newline at end of file diff --git a/Tests/PyIntegration/tests.py b/Tests/PyIntegration/tests.py index b356d0e..94aa95b 100644 --- a/Tests/PyIntegration/tests.py +++ b/Tests/PyIntegration/tests.py @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import Optional, List, Union, Dict import pytest import rg3py import os @@ -745,3 +745,28 @@ def test_check_move_and_copy_ctors_and_assign_operators(): assert analyzer.types[0].functions[1].owner == "Entity" assert analyzer.types[0].functions[1].is_noexcept is True assert analyzer.types[0].functions[1].is_static is False + +def test_code_eval_simple(): + evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator() + result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" + constexpr bool bIsOk = true; + constexpr float fIsOk = 1.337f; + constexpr const char* psOK = "OkayDude123"; + """, ["bIsOk", "fIsOk", "psOK"]) + + assert isinstance(result, dict) + assert "bIsOk" in result + assert "fIsOk" in result + assert "psOK" in result + + # With error + result = evaluator.eval("#error IDK", []) + assert isinstance(result, list) + assert len(result) == 1 + + issue: rg3py.CppCompilerIssue = result[0] + assert issue.message == 'IDK' + assert issue.kind == rg3py.CppCompilerIssueKind.IK_ERROR + assert issue.source_file == 'id0.hpp' + + From 6e548767234dd5a6b57c5decf16a8935013c4749 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 30 Nov 2024 21:12:01 +0300 Subject: [PATCH 4/8] [0.0.23] Added more python tests --- Tests/PyIntegration/tests.py | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Tests/PyIntegration/tests.py b/Tests/PyIntegration/tests.py index 94aa95b..55bf12e 100644 --- a/Tests/PyIntegration/tests.py +++ b/Tests/PyIntegration/tests.py @@ -770,3 +770,45 @@ def test_code_eval_simple(): assert issue.source_file == 'id0.hpp' +def test_code_eval_check_class_inheritance(): + evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator() + result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" + #include + + class Simple {}; + class Base {}; + class Inherited : public Base {}; + + constexpr bool r0 = std::is_base_of_v; + constexpr bool r1 = std::is_base_of_v; + """, ["r0", "r1"]) + + assert result['r0'] is True + assert result['r1'] is False + + +def fib(x): + if x == 0: + return 0 + + if x == 1: + return 1 + + return fib(x - 1) + fib(x - 2) + + +def test_code_eval_check_fibonacci_in_constexpr(): + evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator() + result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" + constexpr int fibonacci(int n) + { + return n < 1 ? -1 : + (n == 1 || n == 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2)); + } + + constexpr auto f5 = fibonacci(5); + constexpr auto f10 = fibonacci(10); + """, ["f5", "f10"]) + + assert result['f5'] == fib(5) + assert result['f10'] == fib(10) From f77920aa51a35cc0b44f7bfc2f21023a658310e3 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 30 Nov 2024 21:24:40 +0300 Subject: [PATCH 5/8] [0.0.23] Added ability to make evaluator from analyzer context and from code analyzer instance. --- PyBind/include/RG3/PyBind/PyCodeAnalyzerBuilder.h | 2 ++ PyBind/rg3py.pyi | 2 ++ PyBind/source/PyBind.cpp | 6 ++++++ PyBind/source/PyCodeAnalyzerBuilder.cpp | 5 +++++ Tests/PyIntegration/tests.py | 8 +++++++- 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/PyBind/include/RG3/PyBind/PyCodeAnalyzerBuilder.h b/PyBind/include/RG3/PyBind/PyCodeAnalyzerBuilder.h index 8ee2079..2b61e35 100644 --- a/PyBind/include/RG3/PyBind/PyCodeAnalyzerBuilder.h +++ b/PyBind/include/RG3/PyBind/PyCodeAnalyzerBuilder.h @@ -38,6 +38,8 @@ namespace rg3::pybind const boost::python::list& getFoundTypes() const; const boost::python::list& getFoundIssues() const; + [[nodiscard]] const rg3::llvm::CompilerConfig& getCompilerConfig() const; + private: std::unique_ptr m_pAnalyzerInstance { nullptr }; boost::python::list m_foundTypes {}; diff --git a/PyBind/rg3py.pyi b/PyBind/rg3py.pyi index 7268d85..fe06d08 100644 --- a/PyBind/rg3py.pyi +++ b/PyBind/rg3py.pyi @@ -358,6 +358,8 @@ class CodeAnalyzer: def analyze(self): ... + def make_evaluator(self) -> CodeEvaluator: ... + class AnalyzerContext: @staticmethod diff --git a/PyBind/source/PyBind.cpp b/PyBind/source/PyBind.cpp index ac36cbb..571c462 100644 --- a/PyBind/source/PyBind.cpp +++ b/PyBind/source/PyBind.cpp @@ -207,6 +207,11 @@ namespace rg3::pybind::wrappers { return boost::shared_ptr(new rg3::llvm::CodeEvaluator(sContext.getCompilerConfig())); } + + static boost::shared_ptr PyCodeAnalyzerBuilder_makeEvaluator(const rg3::pybind::PyCodeAnalyzerBuilder& sContext) + { + return boost::shared_ptr(new rg3::llvm::CodeEvaluator(sContext.getCompilerConfig())); + } } @@ -456,6 +461,7 @@ BOOST_PYTHON_MODULE(rg3py) .def("get_definitions", &rg3::pybind::PyCodeAnalyzerBuilder::getCompilerDefinitions) .def("set_definitions", &rg3::pybind::PyCodeAnalyzerBuilder::setCompilerDefinitions) .def("analyze", &rg3::pybind::PyCodeAnalyzerBuilder::analyze) + .def("make_evaluator", &rg3::pybind::wrappers::PyCodeAnalyzerBuilder_makeEvaluator) ; class_>("AnalyzerContext", "A multithreaded analyzer and scheduled which made to analyze a bunch of files at once. If you have more than few files you should use this class.", no_init) diff --git a/PyBind/source/PyCodeAnalyzerBuilder.cpp b/PyBind/source/PyCodeAnalyzerBuilder.cpp index 437aeb1..75e9cf4 100644 --- a/PyBind/source/PyCodeAnalyzerBuilder.cpp +++ b/PyBind/source/PyCodeAnalyzerBuilder.cpp @@ -145,4 +145,9 @@ namespace rg3::pybind { return m_foundIssues; } + + const rg3::llvm::CompilerConfig& PyCodeAnalyzerBuilder::getCompilerConfig() const + { + return m_pAnalyzerInstance->getCompilerConfig(); + } } \ No newline at end of file diff --git a/Tests/PyIntegration/tests.py b/Tests/PyIntegration/tests.py index 55bf12e..67f93f4 100644 --- a/Tests/PyIntegration/tests.py +++ b/Tests/PyIntegration/tests.py @@ -771,7 +771,12 @@ def test_code_eval_simple(): def test_code_eval_check_class_inheritance(): - evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator() + analyzer: rg3py.CodeAnalyzer = rg3py.CodeAnalyzer.make() + analyzer.set_code("void do_foo() {}") + analyzer.set_cpp_standard(rg3py.CppStandard.CXX_20) + analyzer.analyze() + + evaluator: rg3py.CodeEvaluator = analyzer.make_evaluator() result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" #include @@ -783,6 +788,7 @@ class Inherited : public Base {}; constexpr bool r1 = std::is_base_of_v; """, ["r0", "r1"]) + assert isinstance(result, dict) assert result['r0'] is True assert result['r1'] is False From 9df01303490ee25f389ab90d4417bad0f0596425 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 1 Dec 2024 10:14:55 +0300 Subject: [PATCH 6/8] [0.0.23] Added ability to make fully configured instance of CodeEvaluator and added few tests (Code batching) --- PyBind/rg3py.pyi | 3 +++ PyBind/source/PyBind.cpp | 27 ++++++++++++++++++++++++ Tests/PyIntegration/tests.py | 40 ++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/PyBind/rg3py.pyi b/PyBind/rg3py.pyi index fe06d08..2e9417e 100644 --- a/PyBind/rg3py.pyi +++ b/PyBind/rg3py.pyi @@ -411,6 +411,9 @@ class CodeEvaluator: def eval(self, code: str, capture: List[str]) -> Union[List[CppCompilerIssue], Dict[str, any]]: ... + @staticmethod + def make_from_system_env() -> CodeEvaluator|None: ... + class CppCompilerIssueKind: IK_NONE = 0 diff --git a/PyBind/source/PyBind.cpp b/PyBind/source/PyBind.cpp index 571c462..7aae88f 100644 --- a/PyBind/source/PyBind.cpp +++ b/PyBind/source/PyBind.cpp @@ -203,6 +203,30 @@ namespace rg3::pybind::wrappers return result; } + static boost::shared_ptr CodeEvaluator_makeFromSystemEnv() + { + auto environmentExtractResult = rg3::llvm::CompilerConfigDetector::detectSystemCompilerEnvironment(); + if (rg3::llvm::CompilerEnvError* pError = std::get_if(&environmentExtractResult)) + { + std::string sErrorMessage = fmt::format("Failed to detect compiler environment: {}", pError->message); + + PyErr_SetString(PyExc_RuntimeError, sErrorMessage.c_str()); + boost::python::throw_error_already_set(); + return nullptr; + } + + auto pInstance = boost::shared_ptr(new rg3::llvm::CodeEvaluator()); + if (!pInstance) + { + PyErr_SetString(PyExc_MemoryError, "Out of memory (unable to allocate CodeEvaluator)"); + boost::python::throw_error_already_set(); + return nullptr; + } + + pInstance->setCompilerEnvironment(std::get(environmentExtractResult)); + return pInstance; + } + static boost::shared_ptr PyAnalyzerContext_makeEvaluator(const rg3::pybind::PyAnalyzerContext& sContext) { return boost::shared_ptr(new rg3::llvm::CodeEvaluator(sContext.getCompilerConfig())); @@ -502,5 +526,8 @@ BOOST_PYTHON_MODULE(rg3py) class_>("CodeEvaluator", "Eval constexpr C++ code and provide access to result values", boost::python::init<>()) .def("eval", &rg3::pybind::wrappers::CodeEvaluator_eval) + + .def("make_from_system_env", &rg3::pybind::wrappers::CodeEvaluator_makeFromSystemEnv) + .staticmethod("make_from_system_env") ; } \ No newline at end of file diff --git a/Tests/PyIntegration/tests.py b/Tests/PyIntegration/tests.py index 67f93f4..16e5eb8 100644 --- a/Tests/PyIntegration/tests.py +++ b/Tests/PyIntegration/tests.py @@ -818,3 +818,43 @@ def test_code_eval_check_fibonacci_in_constexpr(): assert result['f5'] == fib(5) assert result['f10'] == fib(10) + +def test_code_eval_check_build_instance_from_sys_env(): + evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator.make_from_system_env() + assert evaluator is not None + + result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" + #include + + class Simple {}; + class Base {}; + class Inherited : public Base {}; + + constexpr bool r0 = std::is_base_of_v; + constexpr bool r1 = std::is_base_of_v; + """, ["r0", "r1"]) + + assert isinstance(result, dict) + assert result['r0'] is True + assert result['r1'] is False + + +def test_code_check_batching_hardcore(): + evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator.make_from_system_env() + assert evaluator is not None + + code: str = """ + #include + + class Base {}; + """ + + for i in range(0, 100): + code = f"{code}\nclass Inherited{i} : Base {{}};\n" + + for i in range(0, 100): + code = f"{code}\nconstexpr bool bIsInherited{i} = std::is_base_of_v;\n" + + result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(code, [f"bIsInherited{x}" for x in range(0, 100)]) + assert isinstance(result, dict) + assert all([result[f"bIsInherited{x}"] for x in range(0, 100)]) From 7cfb4e3964fbde71f938fa6e81c48cd25e60fc94 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 1 Dec 2024 10:48:42 +0300 Subject: [PATCH 7/8] [0.0.23] Added compiler configuration methods & added more tests. --- LLVM/include/RG3/LLVM/CodeEvaluator.h | 1 + LLVM/source/CodeEvaluator.cpp | 5 ++ PyBind/rg3py.pyi | 6 ++ PyBind/source/PyBind.cpp | 91 +++++++++++++++++++++++++++ Tests/PyIntegration/tests.py | 46 ++++++++++++++ 5 files changed, 149 insertions(+) diff --git a/LLVM/include/RG3/LLVM/CodeEvaluator.h b/LLVM/include/RG3/LLVM/CodeEvaluator.h index 1244c98..bc84426 100644 --- a/LLVM/include/RG3/LLVM/CodeEvaluator.h +++ b/LLVM/include/RG3/LLVM/CodeEvaluator.h @@ -34,6 +34,7 @@ namespace rg3::llvm void setCompilerEnvironment(const CompilerEnvironment& env); CompilerConfig& getCompilerConfig(); + const CompilerConfig& getCompilerConfig() const; CodeEvaluateResult evaluateCode(const std::string& sCode, const std::vector& aCaptureOutputVariables); diff --git a/LLVM/source/CodeEvaluator.cpp b/LLVM/source/CodeEvaluator.cpp index 181e940..b0f8b5a 100644 --- a/LLVM/source/CodeEvaluator.cpp +++ b/LLVM/source/CodeEvaluator.cpp @@ -43,6 +43,11 @@ namespace rg3::llvm return m_compilerConfig; } + const CompilerConfig& CodeEvaluator::getCompilerConfig() const + { + return m_compilerConfig; + } + CodeEvaluateResult CodeEvaluator::evaluateCode(const std::string& sCode, const std::vector& aCaptureOutputVariables) { CodeEvaluateResult sResult {}; diff --git a/PyBind/rg3py.pyi b/PyBind/rg3py.pyi index 2e9417e..a1ebb17 100644 --- a/PyBind/rg3py.pyi +++ b/PyBind/rg3py.pyi @@ -411,6 +411,12 @@ class CodeEvaluator: def eval(self, code: str, capture: List[str]) -> Union[List[CppCompilerIssue], Dict[str, any]]: ... + def set_cpp_standard(self, standard: CppStandard): ... + + def get_cpp_standard(self) -> CppStandard: ... + + def set_compiler_config(self, config: dict): ... + @staticmethod def make_from_system_env() -> CodeEvaluator|None: ... diff --git a/PyBind/source/PyBind.cpp b/PyBind/source/PyBind.cpp index 7aae88f..a7a735a 100644 --- a/PyBind/source/PyBind.cpp +++ b/PyBind/source/PyBind.cpp @@ -203,6 +203,94 @@ namespace rg3::pybind::wrappers return result; } + static void CodeEvaluator_setCppStandard(rg3::llvm::CodeEvaluator& sEval, rg3::llvm::CxxStandard eStandard) + { + sEval.getCompilerConfig().cppStandard = eStandard; + } + + static void CodeEvaluator_setCompilerConfigFromDict(rg3::llvm::CodeEvaluator& sEval, const boost::python::object& sDescription) + { + /** + * Allowed fields: + * + * cpp_standard + * definitions + * allow_collect_non_runtime + * skip_function_bodies + * + * If description presented as dict - use directly, otherwise try cast to dict via __dict__ + */ + if (sDescription.is_none()) + { + PyErr_SetString(PyExc_MemoryError, "Expected to have a valid description instead of None"); + boost::python::throw_error_already_set(); + return; + } + + if (PyDict_Check(sDescription.ptr())) + { + // Use directly as dict + boost::python::dict pyDict = boost::python::extract(sDescription); + boost::python::list aKeys = pyDict.keys(); + + for (int i = 0; i < boost::python::len(aKeys); ++i) { + const std::string sKey = boost::python::extract(aKeys[i]); + + if (sKey == "cpp_standard") + { + sEval.getCompilerConfig().cppStandard = boost::python::extract(pyDict[sKey]); + } + + if (sKey == "definitions") + { + boost::python::list pyList = boost::python::extract(pyDict[sKey]); + + auto& definitions = sEval.getCompilerConfig().vCompilerDefs; + definitions.clear(); + definitions.reserve(boost::python::len(pyList)); + + for (auto j = 0; j < boost::python::len(pyList); ++j) { + definitions.push_back(boost::python::extract(pyList[j])); + } + } + + if (sKey == "allow_collect_non_runtime") + { + sEval.getCompilerConfig().bAllowCollectNonRuntimeTypes = boost::python::extract(pyDict[sKey]); + } + + if (sKey == "skip_function_bodies") + { + sEval.getCompilerConfig().bSkipFunctionBodies = boost::python::extract(pyDict[sKey]); + } + } + } + else + { + // Maybe subject able to cast with __dict__ ? + boost::python::object dictAttr = sDescription.attr("__dict__"); + if (PyDict_Check(dictAttr.ptr())) + { + // Use as dict + boost::python::dict pyDict = boost::python::extract(dictAttr); + + // Run self. NOTE: Maybe we've should check this for recursion? Idk) + CodeEvaluator_setCompilerConfigFromDict(sEval, pyDict); + } + else + { + // Omg, dict is not a dict. Raise our own error + PyErr_SetString(PyExc_TypeError, "Meta method __dict__ returned not a dict!"); + boost::python::throw_error_already_set(); + } + } + } + + static rg3::llvm::CxxStandard CodeEvaluator_getCppStandard(const rg3::llvm::CodeEvaluator& sEval) + { + return sEval.getCompilerConfig().cppStandard; + } + static boost::shared_ptr CodeEvaluator_makeFromSystemEnv() { auto environmentExtractResult = rg3::llvm::CompilerConfigDetector::detectSystemCompilerEnvironment(); @@ -526,6 +614,9 @@ BOOST_PYTHON_MODULE(rg3py) class_>("CodeEvaluator", "Eval constexpr C++ code and provide access to result values", boost::python::init<>()) .def("eval", &rg3::pybind::wrappers::CodeEvaluator_eval) + .def("set_cpp_standard", &rg3::pybind::wrappers::CodeEvaluator_setCppStandard) + .def("set_compiler_config", &rg3::pybind::wrappers::CodeEvaluator_setCompilerConfigFromDict) + .def("get_cpp_standard", &rg3::pybind::wrappers::CodeEvaluator_getCppStandard) .def("make_from_system_env", &rg3::pybind::wrappers::CodeEvaluator_makeFromSystemEnv) .staticmethod("make_from_system_env") diff --git a/Tests/PyIntegration/tests.py b/Tests/PyIntegration/tests.py index 16e5eb8..bce7566 100644 --- a/Tests/PyIntegration/tests.py +++ b/Tests/PyIntegration/tests.py @@ -1,9 +1,21 @@ from typing import Optional, List, Union, Dict +from dataclasses import dataclass import pytest import rg3py import os +@dataclass +class CompilerConfigDescription: + """ + Simple example of config + """ + cpp_standard: rg3py.CppStandard + definitions: List[str] + allow_collect_non_runtime: bool + skip_function_bodies: bool + + def test_code_analyzer_base(): analyzer: rg3py.CodeAnalyzer = rg3py.CodeAnalyzer.make() @@ -823,6 +835,10 @@ def test_code_eval_check_build_instance_from_sys_env(): evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator.make_from_system_env() assert evaluator is not None + evaluator.set_compiler_config({ + "cpp_standard" : rg3py.CppStandard.CXX_20 + }) + result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" #include @@ -843,6 +859,8 @@ def test_code_check_batching_hardcore(): evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator.make_from_system_env() assert evaluator is not None + evaluator.set_cpp_standard(rg3py.CppStandard.CXX_20) + code: str = """ #include @@ -858,3 +876,31 @@ class Base {}; result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(code, [f"bIsInherited{x}" for x in range(0, 100)]) assert isinstance(result, dict) assert all([result[f"bIsInherited{x}"] for x in range(0, 100)]) + +def test_code_eval_check_init_from_cfg(): + cfg: CompilerConfigDescription = CompilerConfigDescription(cpp_standard=rg3py.CppStandard.CXX_20, + definitions=[], + allow_collect_non_runtime=False, + skip_function_bodies=True) + + evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator.make_from_system_env() + assert evaluator is not None + + assert evaluator.get_cpp_standard() != rg3py.CppStandard.CXX_20 + evaluator.set_compiler_config(cfg) + assert evaluator.get_cpp_standard() == rg3py.CppStandard.CXX_20 + + result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" + #include + + class Simple {}; + class Base {}; + class Inherited : public Base {}; + + constexpr bool r0 = std::is_base_of_v; + constexpr bool r1 = std::is_base_of_v; + """, ["r0", "r1"]) + + assert isinstance(result, dict) + assert result['r0'] is True + assert result['r1'] is False From 4ca77877cf0bff4fc015a3ed187a4f2f78536176 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 1 Dec 2024 17:43:02 +0300 Subject: [PATCH 8/8] [0.0.23] Added docs & fixed old notes in docs --- Extension/README.md | 47 ++++++++++++++++++++++++++++++++++++++++----- README.md | 38 +++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/Extension/README.md b/Extension/README.md index fe99db4..bc72a1f 100644 --- a/Extension/README.md +++ b/Extension/README.md @@ -12,7 +12,7 @@ Install Make sure that your system has clang (any version): * **macOS**: you need to install XCode (tested on 15.x but should work everywhere) * **Window**: you need to install clang 17.x or later and add it into PATH - * **Linux**: gcc & g++ at least 13 version (temporary limitation, planned to fix at 0.0.4) + * **Linux**: gcc & g++ at least 13 version * **Other platforms & archs**: Contact us in [our GitHub](https://github.com/DronCode/RG3). It's a better way to use RG3 inside virtualenv: @@ -99,16 +99,53 @@ We have a type my::cool::name_space::SomeThirdPartyStruct (TK_STRUCT_OR_CLASS) Function static bool IsGeniusDesc(bool bCanReplace) ``` +Another good use case is `constexpr` evaluation feature: +```python +import rg3py +from typing import Union, List, Dict + +evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator.make_from_system_env() + +evaluator.set_compiler_config({ + "cpp_standard" : rg3py.CppStandard.CXX_20 +}) + +result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" + #include + + class Simple {}; + class Base {}; + class Inherited : public Base {}; + + constexpr bool r0 = std::is_base_of_v; + constexpr bool r1 = std::is_base_of_v; + """, ["r0", "r1"]) + +print(result) +``` + +output will be + +```text +{'r1': False, 'r0': True} +``` + +and that's great feature to make some checks like `type should be inherited from A, have some methods and etc...` + +And this is independent of your current environment, only C++ STL library should be found! + + Features --------- * Supported Windows (x86_64), Linux (x86_64) and macOS (x86_64 and ARM64) - * Supported C++03, 11, 14, 17, 20, 23 (26 in theory, need to migrate to next LLVM) + * Supported C++03, 11, 14, 17, 20, 23, 26 * Supported threads in analysis on native side (see Tests/PyIntegration/test.py test: **test_analyzer_context_sample** for example) * Statically linked, no external dependencies (except Clang instance on machine) * Special macro definitions to hide unnecessary code * Template specializations reporting * Anonymous registration without changes in third party code + * Runtime constexpr C++ code evaluation with results extraction Current limitations ------------------- @@ -118,8 +155,8 @@ Project focused on work around C/C++ headers (C++ especially). Feel free to fork Third Party libraries ---------------------- - * LLVM 16.0.4 - our main backend of C++ analysis - * Boost 1.81.0 - python support & process launcher - * FMT - string formatter + * LLVM - our main backend of C++ analysis + * Boost.Python - python support & process launcher + * fmt - string formatter * googletest - for internal unit testing * pytest - for python side unit testing \ No newline at end of file diff --git a/README.md b/README.md index 08ec3ef..45f1e00 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Requirements Windows ------- - * Compiled LLVM (>= 18.1.8) on local machine and environment variables `CLANG_DIR` and `LLVM_DIR` + * Compiled LLVM (>= 19.1.4) on local machine and environment variables `CLANG_DIR` and `LLVM_DIR` * My `Clang_DIR` is `B:/Projects/llvm/build/lib/cmake/clang` * My `LLVM_DIR` is `B:/Projects/llvm/build/lib/cmake/llvm` * Statically compiled Boost (>=1.81.0) with boost python and environment variable `BOOST_ROOT` @@ -98,6 +98,42 @@ and output will be We have a type my::cool::name_space::ECoolEnum (TK_ENUM) ``` + +Another good use case is `constexpr` evaluation feature: +```python +import rg3py +from typing import Union, List, Dict + +evaluator: rg3py.CodeEvaluator = rg3py.CodeEvaluator.make_from_system_env() + +evaluator.set_compiler_config({ + "cpp_standard" : rg3py.CppStandard.CXX_20 +}) + +result: Union[Dict[str, any], List[rg3py.CppCompilerIssue]] = evaluator.eval(""" + #include + + class Simple {}; + class Base {}; + class Inherited : public Base {}; + + constexpr bool r0 = std::is_base_of_v; + constexpr bool r1 = std::is_base_of_v; + """, ["r0", "r1"]) # 1'st argument: code, 2'nd argument: constexpr captures (list of variables which will be exported from AST) + +print(result) # Result maybe a `dict` or `list`. When dict - result is good, when list - list of errors/issues presented +``` + +output will be + +```text +{'r1': False, 'r0': True} +``` + +and that's great feature to make some checks like `type should be inherited from A, have some methods and etc...` + +And this is independent of your current environment, only C++ STL library should be found! + Project state -------------