| // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| // Exceptions. See /LICENSE for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| #include "lifetime_analysis/test/lifetime_analysis_test.h" |
| |
| #include <cerrno> |
| #include <cstdlib> |
| #include <cstring> |
| #include <fstream> |
| #include <iostream> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "lifetime_analysis/analyze.h" |
| #include "lifetime_annotations/function_lifetimes.h" |
| #include "lifetime_annotations/lifetime_annotations.h" |
| #include "lifetime_annotations/test/named_func_lifetimes.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/Basic/LLVM.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include "lifetime_annotations/test/run_on_code.h" |
| |
| namespace clang { |
| namespace tidy { |
| namespace lifetimes { |
| namespace { |
| |
| void SaveDotFile(absl::string_view dot, absl::string_view filename_base, |
| absl::string_view test_name, absl::string_view description) { |
| std::string base_path = |
| absl::StrCat(testing::TempDir(), "/", test_name, ".", filename_base); |
| std::ofstream out(absl::StrCat(base_path, ".dot")); |
| if (!out) { |
| llvm::errs() << "Error opening dot file: " << strerror(errno) << "\n"; |
| return; |
| } |
| out << dot; |
| if (!out) { |
| llvm::errs() << "Error writing dot file: " << strerror(errno) << "\n"; |
| return; |
| } |
| out.close(); |
| if (system( |
| absl::StrCat("dot ", base_path, ".dot -T svg -o ", base_path, ".svg") |
| .c_str()) != 0) { |
| llvm::errs() << "Error invoking graphviz. dot file can be found at: " |
| << base_path << ".dot\n"; |
| return; |
| } |
| } |
| |
| } // namespace |
| |
| void LifetimeAnalysisTest::TearDown() { |
| if (HasFailure()) { |
| for (const auto& [func, debug_info] : debug_info_map_) { |
| std::cerr << debug_info.ast << "\n"; |
| |
| std::cerr << debug_info.object_repository << "\n"; |
| |
| const char* test_name = |
| testing::UnitTest::GetInstance()->current_test_info()->name(); |
| |
| SaveDotFile(debug_info.points_to_map_dot, |
| absl::StrCat(func, "_points_to"), test_name, |
| "Points-to map of exit block"); |
| SaveDotFile(debug_info.constraints_dot, |
| absl::StrCat(func, "_constraints"), test_name, |
| "Constraint set at exit block"); |
| SaveDotFile(debug_info.cfg_dot, absl::StrCat(func, "_cfg"), test_name, |
| "Control-flow graph"); |
| } |
| std::cerr << "Debug graphs can be found in " << testing::TempDir() |
| << std::endl; |
| } |
| } |
| |
| std::string LifetimeAnalysisTest::QualifiedName( |
| const clang::FunctionDecl* func) { |
| // TODO(veluca): figure out how to name overloaded functions. |
| std::string str; |
| llvm::raw_string_ostream ostream(str); |
| func->printQualifiedName(ostream); |
| ostream.flush(); |
| return str; |
| } |
| |
| NamedFuncLifetimes LifetimeAnalysisTest::GetLifetimes( |
| llvm::StringRef source_code, const GetLifetimesOptions& options) { |
| NamedFuncLifetimes tu_lifetimes; |
| |
| auto test = [&tu_lifetimes, &options, this]( |
| clang::ASTContext& ast_context, |
| const LifetimeAnnotationContext& lifetime_context) { |
| // This will get called even if the code contains compilation errors. |
| // So we need to check to avoid performing an analysis on code that |
| // doesn't compile. |
| if (ast_context.getDiagnostics().hasUncompilableErrorOccurred() && |
| !analyze_broken_code_) { |
| tu_lifetimes.Add("", "Compilation error -- see log for details"); |
| return; |
| } |
| |
| auto result_callback = [&tu_lifetimes, &options]( |
| const clang::FunctionDecl* func, |
| const FunctionLifetimesOrError& |
| lifetimes_or_error) { |
| if (std::holds_alternative<FunctionAnalysisError>(lifetimes_or_error)) { |
| tu_lifetimes.Add( |
| QualifiedName(func), |
| absl::StrCat( |
| "ERROR: ", |
| std::get<FunctionAnalysisError>(lifetimes_or_error).message)); |
| return; |
| } |
| const auto& func_lifetimes = |
| std::get<FunctionLifetimes>(lifetimes_or_error); |
| |
| // Do not insert in the result set implicitly-defined constructors or |
| // assignment operators. |
| if (auto* constructor = |
| clang::dyn_cast<clang::CXXConstructorDecl>(func)) { |
| if (constructor->isImplicit() && !options.include_implicit_methods) { |
| return; |
| } |
| } |
| if (auto* method = clang::dyn_cast<clang::CXXMethodDecl>(func)) { |
| if (method->isImplicit() && !options.include_implicit_methods) { |
| return; |
| } |
| } |
| |
| tu_lifetimes.Add(QualifiedName(func), NameLifetimes(func_lifetimes)); |
| }; |
| |
| FunctionDebugInfoMap func_ptr_debug_info_map; |
| llvm::DenseMap<const clang::FunctionDecl*, FunctionLifetimesOrError> |
| analysis_result; |
| if (options.with_template_placeholder) { |
| AnalyzeTranslationUnitWithTemplatePlaceholder( |
| ast_context.getTranslationUnitDecl(), lifetime_context, |
| result_callback, |
| /*diag_reporter=*/{}, &func_ptr_debug_info_map); |
| } else { |
| analysis_result = AnalyzeTranslationUnit( |
| ast_context.getTranslationUnitDecl(), lifetime_context, |
| /*diag_reporter=*/{}, &func_ptr_debug_info_map); |
| |
| for (const auto& [func, lifetimes_or_error] : analysis_result) { |
| result_callback(func, lifetimes_or_error); |
| } |
| } |
| |
| for (auto& [func, debug_info] : func_ptr_debug_info_map) { |
| debug_info_map_.try_emplace(func->getDeclName().getAsString(), |
| std::move(debug_info)); |
| } |
| }; |
| |
| if (!runOnCodeWithLifetimeHandlers(source_code, test, |
| {"-fsyntax-only", "-std=c++17"})) { |
| // We need to disambiguate between two cases: |
| // - We were unable to run the analysis at all (because there was some |
| // internal error) |
| // In this case, `tu_lifetimes` will be empty, so add a corresponding |
| // note here. |
| // - The analysis emitted an error diagnostic, which will also cause us to |
| // end up here. |
| // In this case, `tu_lifetimes` already contains an error empty, so we |
| // don't need to do anything. |
| if (tu_lifetimes.Entries().empty()) { |
| tu_lifetimes.Add("", "Error running dataflow analysis"); |
| } |
| } |
| |
| return tu_lifetimes; |
| } |
| |
| NamedFuncLifetimes LifetimeAnalysisTest::GetLifetimesWithPlaceholder( |
| llvm::StringRef source_code) { |
| GetLifetimesOptions options; |
| options.with_template_placeholder = true; |
| return GetLifetimes(source_code, options); |
| } |
| |
| } // namespace lifetimes |
| } // namespace tidy |
| } // namespace clang |