blob: d918d3534014f0f773276e8830a8f49fcf507495 [file] [log] [blame]
Luca Versari99fddff2022-05-25 10:22:32 -07001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5#include "lifetime_analysis/test/lifetime_analysis_test.h"
6
7#include <fstream>
8#include <ostream>
9#include <string>
10#include <utility>
11#include <variant>
12
13#include "lifetime_annotations/test/run_on_code.h"
14
15namespace clang {
16namespace tidy {
17namespace lifetimes {
18namespace {
19
20void SaveDotFile(absl::string_view dot, absl::string_view filename_base,
21 absl::string_view test_name, absl::string_view description) {
22 std::string base_path =
23 absl::StrCat(testing::TempDir(), "/", test_name, ".", filename_base);
24 std::ofstream out(absl::StrCat(base_path, ".dot"));
25 if (!out) {
26 llvm::errs() << "Error opening dot file: " << strerror(errno) << "\n";
27 return;
28 }
29 out << dot;
30 if (!out) {
31 llvm::errs() << "Error writing dot file: " << strerror(errno) << "\n";
32 return;
33 }
34 out.close();
35 if (system(
36 absl::StrCat("dot ", base_path, ".dot -T svg -o ", base_path, ".svg")
37 .c_str()) != 0) {
38 llvm::errs() << "Error invoking graphviz. dot file can be found at: "
39 << base_path << ".dot\n";
40 return;
41 }
42}
43
44} // namespace
45
46void LifetimeAnalysisTest::TearDown() {
47 if (HasFailure()) {
48 for (const auto& [func, debug_info] : debug_info_map_) {
49 std::cerr << debug_info.ast << "\n";
50
51 std::cerr << debug_info.object_repository << "\n";
52
53 const char* test_name =
54 testing::UnitTest::GetInstance()->current_test_info()->name();
55
56 SaveDotFile(debug_info.points_to_map_dot,
57 absl::StrCat(func, "_points_to"), test_name,
58 "Points-to map of exit block");
Luca Versarie06f94b2022-09-02 02:44:06 -070059 SaveDotFile(debug_info.constraints_dot,
60 absl::StrCat(func, "_constraints"), test_name,
61 "Constraint set at exit block");
Luca Versari99fddff2022-05-25 10:22:32 -070062 SaveDotFile(debug_info.cfg_dot, absl::StrCat(func, "_cfg"), test_name,
63 "Control-flow graph");
64 }
65 std::cerr << "Debug graphs can be found in " << testing::TempDir()
66 << std::endl;
67 }
68}
69
70std::string LifetimeAnalysisTest::QualifiedName(
71 const clang::FunctionDecl* func) {
72 // TODO(veluca): figure out how to name overloaded functions.
73 std::string str;
74 llvm::raw_string_ostream ostream(str);
75 func->printQualifiedName(ostream);
76 ostream.flush();
77 return str;
78}
79
80NamedFuncLifetimes LifetimeAnalysisTest::GetLifetimes(
81 llvm::StringRef source_code, const GetLifetimesOptions& options) {
82 NamedFuncLifetimes tu_lifetimes;
83
84 auto test = [&tu_lifetimes, &options, this](
85 clang::ASTContext& ast_context,
86 const LifetimeAnnotationContext& lifetime_context) {
87 // This will get called even if the code contains compilation errors.
88 // So we need to check to avoid performing an analysis on code that
89 // doesn't compile.
90 if (ast_context.getDiagnostics().hasUncompilableErrorOccurred() &&
91 !analyze_broken_code_) {
92 tu_lifetimes.Add("", "Compilation error -- see log for details");
93 return;
94 }
95
96 auto result_callback = [&tu_lifetimes, &options](
97 const clang::FunctionDecl* func,
98 const FunctionLifetimesOrError&
99 lifetimes_or_error) {
100 if (std::holds_alternative<FunctionAnalysisError>(lifetimes_or_error)) {
101 tu_lifetimes.Add(
102 QualifiedName(func),
103 absl::StrCat(
104 "ERROR: ",
105 std::get<FunctionAnalysisError>(lifetimes_or_error).message));
106 return;
107 }
108 const auto& func_lifetimes =
109 std::get<FunctionLifetimes>(lifetimes_or_error);
110
111 // Do not insert in the result set implicitly-defined constructors or
112 // assignment operators.
113 if (auto* constructor =
114 clang::dyn_cast<clang::CXXConstructorDecl>(func)) {
115 if (constructor->isImplicit() && !options.include_implicit_methods) {
116 return;
117 }
118 }
119 if (auto* method = clang::dyn_cast<clang::CXXMethodDecl>(func)) {
120 if (method->isImplicit() && !options.include_implicit_methods) {
121 return;
122 }
123 }
124
125 tu_lifetimes.Add(QualifiedName(func), NameLifetimes(func_lifetimes));
126 };
127
128 FunctionDebugInfoMap func_ptr_debug_info_map;
129 llvm::DenseMap<const clang::FunctionDecl*, FunctionLifetimesOrError>
130 analysis_result;
131 if (options.with_template_placeholder) {
132 AnalyzeTranslationUnitWithTemplatePlaceholder(
133 ast_context.getTranslationUnitDecl(), lifetime_context,
134 result_callback,
135 /*diag_reporter=*/{}, &func_ptr_debug_info_map);
136 } else {
137 analysis_result = AnalyzeTranslationUnit(
138 ast_context.getTranslationUnitDecl(), lifetime_context,
139 /*diag_reporter=*/{}, &func_ptr_debug_info_map);
140
141 for (const auto& [func, lifetimes_or_error] : analysis_result) {
142 result_callback(func, lifetimes_or_error);
143 }
144 }
145
146 for (auto& [func, debug_info] : func_ptr_debug_info_map) {
147 debug_info_map_.try_emplace(func->getDeclName().getAsString(),
148 std::move(debug_info));
149 }
150 };
151
152 if (!runOnCodeWithLifetimeHandlers(source_code, test,
153 {"-fsyntax-only", "-std=c++17"})) {
154 // We need to disambiguate between two cases:
155 // - We were unable to run the analysis at all (because there was some
156 // internal error)
157 // In this case, `tu_lifetimes` will be empty, so add a corresponding
158 // note here.
159 // - The analysis emitted an error diagnostic, which will also cause us to
160 // end up here.
161 // In this case, `tu_lifetimes` already contains an error empty, so we
162 // don't need to do anything.
163 if (tu_lifetimes.Entries().empty()) {
164 tu_lifetimes.Add("", "Error running dataflow analysis");
165 }
166 }
167
168 return tu_lifetimes;
169}
170
171NamedFuncLifetimes LifetimeAnalysisTest::GetLifetimesWithPlaceholder(
172 llvm::StringRef source_code) {
173 GetLifetimesOptions options;
174 options.with_template_placeholder = true;
175 return GetLifetimes(source_code, options);
176}
177
178} // namespace lifetimes
179} // namespace tidy
180} // namespace clang