Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 1 | // 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/lifetime_analysis.h" |
| 6 | |
| 7 | #include <iostream> |
| 8 | #include <memory> |
| 9 | #include <optional> |
| 10 | #include <string> |
| 11 | #include <utility> |
| 12 | #include <variant> |
| 13 | #include <vector> |
| 14 | |
| 15 | #include "lifetime_analysis/builtin_lifetimes.h" |
| 16 | #include "lifetime_analysis/object.h" |
| 17 | #include "lifetime_analysis/object_repository.h" |
| 18 | #include "lifetime_analysis/object_set.h" |
| 19 | #include "lifetime_analysis/pointer_compatibility.h" |
| 20 | #include "lifetime_analysis/points_to_map.h" |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 21 | #include "lifetime_annotations/function_lifetimes.h" |
| 22 | #include "lifetime_annotations/lifetime.h" |
| 23 | #include "lifetime_annotations/pointee_type.h" |
| 24 | #include "lifetime_annotations/type_lifetimes.h" |
| 25 | #include "clang/AST/Decl.h" |
| 26 | #include "clang/AST/DeclCXX.h" |
| 27 | #include "clang/AST/Expr.h" |
| 28 | #include "clang/AST/ExprCXX.h" |
| 29 | #include "clang/AST/OperationKinds.h" |
| 30 | #include "clang/AST/Stmt.h" |
| 31 | #include "clang/AST/StmtVisitor.h" |
| 32 | #include "clang/AST/TemplateBase.h" |
| 33 | #include "clang/AST/Type.h" |
Wei Yi Tee | dae0e82 | 2022-09-20 15:03:43 -0700 | [diff] [blame] | 34 | #include "clang/Analysis/CFG.h" |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 35 | #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" |
| 36 | #include "llvm/ADT/ArrayRef.h" |
| 37 | #include "llvm/ADT/DenseMap.h" |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 38 | #include "llvm/Support/Error.h" |
| 39 | #include "llvm/Support/ErrorHandling.h" |
| 40 | |
| 41 | namespace clang { |
| 42 | namespace tidy { |
| 43 | namespace lifetimes { |
| 44 | |
| 45 | namespace { |
| 46 | |
| 47 | class TransferStmtVisitor |
| 48 | : public clang::StmtVisitor<TransferStmtVisitor, |
| 49 | std::optional<std::string>> { |
| 50 | public: |
| 51 | TransferStmtVisitor( |
| 52 | ObjectRepository& object_repository, PointsToMap& points_to_map, |
Luca Versari | 53a2f58 | 2023-01-16 10:09:29 -0800 | [diff] [blame] | 53 | LifetimeConstraints& constraints, ObjectSet& single_valued_objects, |
| 54 | const clang::FunctionDecl* func, |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 55 | const llvm::DenseMap<const clang::FunctionDecl*, |
| 56 | FunctionLifetimesOrError>& callee_lifetimes, |
| 57 | const DiagnosticReporter& diag_reporter) |
| 58 | : object_repository_(object_repository), |
| 59 | points_to_map_(points_to_map), |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 60 | constraints_(constraints), |
Luca Versari | 53a2f58 | 2023-01-16 10:09:29 -0800 | [diff] [blame] | 61 | single_valued_objects_(single_valued_objects), |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 62 | func_(func), |
| 63 | callee_lifetimes_(callee_lifetimes), |
| 64 | diag_reporter_(diag_reporter) {} |
| 65 | |
| 66 | std::optional<std::string> VisitExpr(const clang::Expr* expr); |
| 67 | std::optional<std::string> VisitDeclRefExpr( |
| 68 | const clang::DeclRefExpr* decl_ref); |
| 69 | std::optional<std::string> VisitStringLiteral( |
| 70 | const clang::StringLiteral* strlit); |
| 71 | std::optional<std::string> VisitCastExpr(const clang::CastExpr* cast); |
| 72 | std::optional<std::string> VisitReturnStmt( |
| 73 | const clang::ReturnStmt* return_stmt); |
| 74 | std::optional<std::string> VisitDeclStmt(const clang::DeclStmt* decl_stmt); |
| 75 | std::optional<std::string> VisitUnaryOperator(const clang::UnaryOperator* op); |
| 76 | std::optional<std::string> VisitArraySubscriptExpr( |
| 77 | const clang::ArraySubscriptExpr* subscript); |
| 78 | std::optional<std::string> VisitBinaryOperator( |
| 79 | const clang::BinaryOperator* op); |
| 80 | std::optional<std::string> VisitConditionalOperator( |
| 81 | const clang::ConditionalOperator* op); |
| 82 | std::optional<std::string> VisitInitListExpr( |
| 83 | const clang::InitListExpr* init_list); |
| 84 | std::optional<std::string> VisitMaterializeTemporaryExpr( |
| 85 | const clang::MaterializeTemporaryExpr* temporary_expr); |
| 86 | std::optional<std::string> VisitMemberExpr(const clang::MemberExpr* member); |
| 87 | std::optional<std::string> VisitCXXThisExpr( |
| 88 | const clang::CXXThisExpr* this_expr); |
| 89 | std::optional<std::string> VisitCallExpr(const clang::CallExpr* call); |
| 90 | std::optional<std::string> VisitCXXConstructExpr( |
| 91 | const clang::CXXConstructExpr* construct_expr); |
| 92 | |
| 93 | private: |
| 94 | ObjectRepository& object_repository_; |
| 95 | PointsToMap& points_to_map_; |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 96 | LifetimeConstraints& constraints_; |
Luca Versari | 53a2f58 | 2023-01-16 10:09:29 -0800 | [diff] [blame] | 97 | ObjectSet& single_valued_objects_; |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 98 | const clang::FunctionDecl* func_; |
| 99 | const llvm::DenseMap<const clang::FunctionDecl*, FunctionLifetimesOrError>& |
| 100 | callee_lifetimes_; |
| 101 | const DiagnosticReporter& diag_reporter_; |
| 102 | }; |
| 103 | |
Luca Versari | a12c9d5 | 2023-02-23 08:40:48 -0800 | [diff] [blame] | 104 | void GenerateConstraintsForSingleAssignment(const Object* oldp, |
| 105 | const Object* newp, |
| 106 | LifetimeConstraints& constraints) { |
| 107 | if (oldp->GetFuncLifetimes().has_value() && |
| 108 | newp->GetFuncLifetimes().has_value()) { |
| 109 | // The order of `newp` and `oldp` here may seem surprising. However, this |
| 110 | // can be thought of as: "I am assigning `newp` where before I had `oldp`, |
| 111 | // therefore `oldp` needs to be able to represent a call to whatever it is |
| 112 | // that `newp` represents, hence I need to generate constraints for |
| 113 | // replacing `newp` with `oldp`". At least this is why veluca@ thinks this |
| 114 | // is the correct order (the opposite order generates incorrect results). |
| 115 | constraints.join(LifetimeConstraints::ForCallableSubstitutionFull( |
| 116 | *newp->GetFuncLifetimes(), *oldp->GetFuncLifetimes())); |
| 117 | } |
| 118 | constraints.AddOutlivesConstraint(oldp->GetLifetime(), newp->GetLifetime()); |
| 119 | } |
| 120 | |
Luca Versari | 5a414a2 | 2022-10-05 03:35:56 -0700 | [diff] [blame] | 121 | void GenerateConstraintsForAssignmentNonRecursive( |
| 122 | const ObjectSet& old_pointees, const ObjectSet& new_pointees, |
| 123 | bool is_in_invariant_context, LifetimeConstraints& constraints) { |
| 124 | // The new pointees must always outlive the old pointees. |
| 125 | for (const Object* old : old_pointees) { |
| 126 | for (const Object* newp : new_pointees) { |
Luca Versari | a12c9d5 | 2023-02-23 08:40:48 -0800 | [diff] [blame] | 127 | GenerateConstraintsForSingleAssignment(old, newp, constraints); |
Luca Versari | 5a414a2 | 2022-10-05 03:35:56 -0700 | [diff] [blame] | 128 | } |
| 129 | } |
| 130 | |
| 131 | // If we are in an invariant context, we need to insert constraints in the |
| 132 | // opposite direction too (i.e. we need equality). |
| 133 | if (is_in_invariant_context) { |
| 134 | for (const Object* old : old_pointees) { |
| 135 | for (const Object* newp : new_pointees) { |
Luca Versari | a12c9d5 | 2023-02-23 08:40:48 -0800 | [diff] [blame] | 136 | GenerateConstraintsForSingleAssignment(newp, old, constraints); |
Luca Versari | 5a414a2 | 2022-10-05 03:35:56 -0700 | [diff] [blame] | 137 | } |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 142 | // TODO(veluca): this is quadratic. |
Luca Versari | 5a414a2 | 2022-10-05 03:35:56 -0700 | [diff] [blame] | 143 | void GenerateConstraintsForAssignmentRecursive( |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 144 | const ObjectSet& pointers, const ObjectSet& new_pointees, |
| 145 | clang::QualType pointer_type, const ObjectRepository& object_repository, |
Luca Versari | c6965ec | 2022-09-02 02:51:33 -0700 | [diff] [blame] | 146 | const PointsToMap& points_to_map, bool is_in_invariant_context, |
| 147 | LifetimeConstraints& constraints, |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 148 | llvm::DenseSet<std::pair<const Object*, const Object*>>& seen_pairs) { |
| 149 | // Check for cycles. |
| 150 | { |
| 151 | size_t num_seen_pairs = seen_pairs.size(); |
| 152 | for (auto pointer : pointers) { |
| 153 | for (auto pointee : new_pointees) { |
| 154 | seen_pairs.insert({pointer, pointee}); |
| 155 | } |
| 156 | } |
| 157 | // All done: all the pairs we have were already seen. |
| 158 | if (num_seen_pairs == seen_pairs.size()) return; |
| 159 | } |
| 160 | |
Luca Versari | cd257d3 | 2022-08-22 02:09:25 -0700 | [diff] [blame] | 161 | if (pointer_type->isIncompleteType()) { |
| 162 | // Nothing we *can* do. |
| 163 | return; |
| 164 | } |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 165 | assert(!pointer_type->isRecordType()); |
| 166 | if (!pointer_type->isPointerType() && !pointer_type->isReferenceType()) { |
| 167 | // Nothing to do. |
| 168 | return; |
| 169 | } |
| 170 | |
| 171 | ObjectSet old_pointees = points_to_map.GetPointerPointsToSet(pointers); |
| 172 | |
Luca Versari | 5a414a2 | 2022-10-05 03:35:56 -0700 | [diff] [blame] | 173 | GenerateConstraintsForAssignmentNonRecursive( |
| 174 | old_pointees, new_pointees, is_in_invariant_context, constraints); |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 175 | |
Luca Versari | c6965ec | 2022-09-02 02:51:33 -0700 | [diff] [blame] | 176 | // See https://doc.rust-lang.org/nomicon/subtyping.html for an explanation of |
| 177 | // variance; here in particular, we use the fact that the pointee of a pointer |
Luca Versari | c084315 | 2022-09-16 02:10:25 -0700 | [diff] [blame] | 178 | // is covariant if the pointer points to a const-qualified type, and invariant |
| 179 | // otherwise. |
| 180 | is_in_invariant_context = !pointer_type->getPointeeType().isConstQualified(); |
Luca Versari | c6965ec | 2022-09-02 02:51:33 -0700 | [diff] [blame] | 181 | |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 182 | // Recurse in pointees. As the pointee might be of struct type, we need first |
| 183 | // to extract all field pointers from it. |
| 184 | struct RecursiveVisitInfo { |
| 185 | clang::QualType type; |
| 186 | ObjectSet old_pointees; |
| 187 | ObjectSet new_pointees; |
| 188 | }; |
| 189 | |
| 190 | std::vector<RecursiveVisitInfo> calls_to_make; |
| 191 | calls_to_make.push_back( |
| 192 | {pointer_type->getPointeeType(), old_pointees, new_pointees}); |
| 193 | while (!calls_to_make.empty()) { |
| 194 | RecursiveVisitInfo call = std::move(calls_to_make.back()); |
| 195 | calls_to_make.pop_back(); |
| 196 | |
Luca Versari | cd257d3 | 2022-08-22 02:09:25 -0700 | [diff] [blame] | 197 | if (call.type->isIncompleteType()) { |
| 198 | // Nothing we *can* do. |
| 199 | continue; |
| 200 | } |
| 201 | |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 202 | if (const auto* record_type = call.type->getAs<clang::RecordType>()) { |
| 203 | for (auto field : record_type->getDecl()->fields()) { |
| 204 | calls_to_make.push_back( |
| 205 | {field->getType(), |
Luca Versari | 187176a | 2022-09-02 02:42:23 -0700 | [diff] [blame] | 206 | object_repository.GetFieldObject(call.old_pointees, field), |
| 207 | object_repository.GetFieldObject(call.new_pointees, field)}); |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 208 | } |
| 209 | if (auto* cxxrecord = |
| 210 | clang::dyn_cast<clang::CXXRecordDecl>(record_type->getDecl())) { |
| 211 | for (const clang::CXXBaseSpecifier& base : cxxrecord->bases()) { |
| 212 | calls_to_make.push_back({base.getType(), |
| 213 | object_repository.GetBaseClassObject( |
Luca Versari | 187176a | 2022-09-02 02:42:23 -0700 | [diff] [blame] | 214 | call.old_pointees, base.getType()), |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 215 | object_repository.GetBaseClassObject( |
Luca Versari | 187176a | 2022-09-02 02:42:23 -0700 | [diff] [blame] | 216 | call.new_pointees, base.getType())}); |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 217 | } |
| 218 | } |
| 219 | } else { |
Luca Versari | 5a414a2 | 2022-10-05 03:35:56 -0700 | [diff] [blame] | 220 | GenerateConstraintsForAssignmentRecursive( |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 221 | call.old_pointees, |
| 222 | points_to_map.GetPointerPointsToSet(call.new_pointees), call.type, |
Luca Versari | c6965ec | 2022-09-02 02:51:33 -0700 | [diff] [blame] | 223 | object_repository, points_to_map, is_in_invariant_context, |
| 224 | constraints, seen_pairs); |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 225 | } |
| 226 | } |
| 227 | } |
| 228 | |
Luca Versari | 187176a | 2022-09-02 02:42:23 -0700 | [diff] [blame] | 229 | void GenerateConstraintsForAssignment(const ObjectSet& pointers, |
| 230 | const ObjectSet& new_pointees, |
| 231 | clang::QualType pointer_type, |
| 232 | const ObjectRepository& object_repository, |
| 233 | PointsToMap& points_to_map, |
| 234 | LifetimeConstraints& constraints) { |
| 235 | llvm::DenseSet<std::pair<const Object*, const Object*>> seen_pairs; |
Luca Versari | c6965ec | 2022-09-02 02:51:33 -0700 | [diff] [blame] | 236 | // Outer-most pointers are never invariant. |
Luca Versari | 5a414a2 | 2022-10-05 03:35:56 -0700 | [diff] [blame] | 237 | GenerateConstraintsForAssignmentRecursive( |
Luca Versari | c6965ec | 2022-09-02 02:51:33 -0700 | [diff] [blame] | 238 | pointers, new_pointees, pointer_type, object_repository, points_to_map, |
| 239 | /*is_in_invariant_context=*/false, constraints, seen_pairs); |
Luca Versari | 187176a | 2022-09-02 02:42:23 -0700 | [diff] [blame] | 240 | } |
| 241 | |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 242 | void GenerateConstraintsForObjectLifetimeEquality( |
| 243 | const ObjectSet& a, const ObjectSet& b, const clang::QualType& type, |
| 244 | const PointsToMap& points_to_map, const ObjectRepository& object_repository, |
| 245 | LifetimeConstraints& constraints) { |
| 246 | GenerateConstraintsForAssignmentNonRecursive( |
| 247 | a, b, /*is_in_invariant_context=*/true, constraints); |
| 248 | if (const auto* record_type = type->getAs<clang::RecordType>()) { |
| 249 | for (auto field : record_type->getDecl()->fields()) { |
| 250 | GenerateConstraintsForObjectLifetimeEquality( |
| 251 | object_repository.GetFieldObject(a, field), |
| 252 | object_repository.GetFieldObject(b, field), field->getType(), |
| 253 | points_to_map, object_repository, constraints); |
| 254 | } |
| 255 | if (auto* cxxrecord = |
| 256 | clang::dyn_cast<clang::CXXRecordDecl>(record_type->getDecl())) { |
| 257 | for (const clang::CXXBaseSpecifier& base : cxxrecord->bases()) { |
| 258 | GenerateConstraintsForObjectLifetimeEquality( |
| 259 | object_repository.GetBaseClassObject(a, base.getType()), |
| 260 | object_repository.GetBaseClassObject(b, base.getType()), |
| 261 | base.getType(), points_to_map, object_repository, constraints); |
| 262 | } |
| 263 | } |
| 264 | } else if (!PointeeType(type).isNull()) { |
| 265 | GenerateConstraintsForObjectLifetimeEquality( |
| 266 | points_to_map.GetPointerPointsToSet(a), |
| 267 | points_to_map.GetPointerPointsToSet(b), PointeeType(type), |
| 268 | points_to_map, object_repository, constraints); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | } // namespace |
| 273 | |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 274 | void HandlePointsToSetExtension(const ObjectSet& pointers, |
| 275 | const ObjectSet& new_pointees, |
| 276 | clang::QualType pointer_type, |
| 277 | const ObjectRepository& object_repository, |
| 278 | PointsToMap& points_to_map, |
| 279 | LifetimeConstraints& constraints) { |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 280 | // Record types should not get to this point at all, as |
| 281 | // their initialization is done by constructor calls. |
| 282 | assert(!pointer_type->isRecordType()); |
| 283 | GenerateConstraintsForAssignment(pointers, new_pointees, pointer_type, |
| 284 | object_repository, points_to_map, |
| 285 | constraints); |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 286 | for (const Object* pointer : pointers) { |
| 287 | points_to_map.ExtendPointerPointsToSet(pointer, new_pointees); |
| 288 | } |
| 289 | } |
| 290 | |
Martin Brænne | 2a1b27a | 2022-07-01 06:17:32 -0700 | [diff] [blame] | 291 | void TransferInitializer(const Object* dest, clang::QualType type, |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 292 | const ObjectRepository& object_repository, |
| 293 | const clang::Expr* init_expr, |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 294 | TargetPointeeBehavior pointee_behavior, |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 295 | PointsToMap& points_to_map, |
| 296 | LifetimeConstraints& constraints) { |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 297 | type = type.getCanonicalType(); |
| 298 | if (type->isArrayType()) { |
| 299 | type = type->castAsArrayTypeUnsafe()->getElementType(); |
| 300 | } |
| 301 | |
| 302 | // Initializer lists are handled one member/field at a time. |
| 303 | if (type->isRecordType()) { |
| 304 | if (auto init_list_expr = clang::dyn_cast<clang::InitListExpr>(init_expr)) { |
| 305 | // We assume that initializers are always the semantic form of |
| 306 | // InitListExpr. |
| 307 | assert(init_list_expr->isSemanticForm()); |
| 308 | size_t init = 0; |
| 309 | for (auto f : type->getAs<clang::RecordType>()->getDecl()->fields()) { |
| 310 | assert(init < init_list_expr->getNumInits()); |
| 311 | auto field_init = init_list_expr->getInit(init); |
| 312 | ++init; |
Martin Brænne | 2a1b27a | 2022-07-01 06:17:32 -0700 | [diff] [blame] | 313 | TransferInitializer(object_repository.GetFieldObject(dest, f), |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 314 | f->getType(), object_repository, field_init, |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 315 | pointee_behavior, points_to_map, constraints); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 316 | } |
| 317 | return; |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | if (type->isPointerType() || type->isReferenceType() || |
| 322 | type->isStructureOrClassType()) { |
| 323 | ObjectSet init_points_to = points_to_map.GetExprObjectSet(init_expr); |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 324 | if (pointee_behavior == TargetPointeeBehavior::kKeep) { |
| 325 | // It's important to use "Extend" (not "Set") here because we process |
| 326 | // initializers for member variables only _after_ the dataflow analysis |
| 327 | // has run. |
| 328 | HandlePointsToSetExtension({dest}, init_points_to, type, |
| 329 | object_repository, points_to_map, constraints); |
Martin Brænne | 5d1a524 | 2022-06-30 06:26:28 -0700 | [diff] [blame] | 330 | } else { |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 331 | points_to_map.SetPointerPointsToSet(dest, init_points_to); |
Martin Brænne | 5d1a524 | 2022-06-30 06:26:28 -0700 | [diff] [blame] | 332 | } |
| 333 | } |
Martin Brænne | 5d1a524 | 2022-06-30 06:26:28 -0700 | [diff] [blame] | 334 | } |
| 335 | |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 336 | LifetimeLattice LifetimeAnalysis::initialElement() { |
Luca Versari | 53a2f58 | 2023-01-16 10:09:29 -0800 | [diff] [blame] | 337 | return LifetimeLattice(object_repository_.InitialPointsToMap(), |
| 338 | object_repository_.InitialSingleValuedObjects()); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 339 | } |
| 340 | |
| 341 | std::string LifetimeAnalysis::ToString(const LifetimeLattice& state) { |
| 342 | return state.ToString(); |
| 343 | } |
| 344 | |
| 345 | bool LifetimeAnalysis::IsEqual(const LifetimeLattice& state1, |
| 346 | const LifetimeLattice& state2) { |
| 347 | return state1 == state2; |
| 348 | } |
| 349 | |
Googler | 9949b46 | 2023-02-15 05:09:07 -0800 | [diff] [blame] | 350 | void LifetimeAnalysis::transfer(const clang::CFGElement& elt, |
Wei Yi Tee | dae0e82 | 2022-09-20 15:03:43 -0700 | [diff] [blame] | 351 | LifetimeLattice& state, |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 352 | clang::dataflow::Environment& /*environment*/) { |
| 353 | if (state.IsError()) return; |
| 354 | |
Googler | 9949b46 | 2023-02-15 05:09:07 -0800 | [diff] [blame] | 355 | auto cfg_stmt = elt.getAs<clang::CFGStmt>(); |
Wei Yi Tee | dae0e82 | 2022-09-20 15:03:43 -0700 | [diff] [blame] | 356 | if (!cfg_stmt) return; |
| 357 | auto stmt = cfg_stmt->getStmt(); |
| 358 | |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 359 | TransferStmtVisitor visitor(object_repository_, state.PointsTo(), |
Luca Versari | 53a2f58 | 2023-01-16 10:09:29 -0800 | [diff] [blame] | 360 | state.Constraints(), state.SingleValuedObjects(), |
| 361 | func_, callee_lifetimes_, diag_reporter_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 362 | if (std::optional<std::string> err = |
| 363 | visitor.Visit(const_cast<clang::Stmt*>(stmt))) { |
| 364 | state = LifetimeLattice(*err); |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | namespace { |
| 369 | |
| 370 | std::optional<std::string> TransferStmtVisitor::VisitExpr( |
| 371 | const clang::Expr* expr) { |
| 372 | // Ensure that we don't attempt to analyze code that contains errors. |
| 373 | // This is triggered by TypoExpr and RecoveryExpr, but rather than handling |
| 374 | // these particular expression types individually, we just check |
| 375 | // Expr::containsErrors(). |
| 376 | if (expr->containsErrors()) { |
| 377 | return "encountered an expression containing errors"; |
| 378 | } |
| 379 | return std::nullopt; |
| 380 | } |
| 381 | |
| 382 | std::optional<std::string> TransferStmtVisitor::VisitDeclRefExpr( |
| 383 | const clang::DeclRefExpr* decl_ref) { |
| 384 | auto* decl = decl_ref->getDecl(); |
| 385 | if (!clang::isa<clang::VarDecl>(decl) && |
| 386 | !clang::isa<clang::FunctionDecl>(decl)) { |
| 387 | return std::nullopt; |
| 388 | } |
| 389 | |
Martin Brænne | 46b5f07 | 2022-07-01 02:29:16 -0700 | [diff] [blame] | 390 | const Object* object = object_repository_.GetDeclObject(decl); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 391 | |
| 392 | assert(decl_ref->isGLValue() || decl_ref->getType()->isBuiltinType()); |
| 393 | |
| 394 | clang::QualType type = decl->getType().getCanonicalType(); |
| 395 | |
| 396 | if (type->isReferenceType()) { |
| 397 | points_to_map_.SetExprObjectSet( |
| 398 | decl_ref, points_to_map_.GetPointerPointsToSet(object)); |
| 399 | } else { |
| 400 | points_to_map_.SetExprObjectSet(decl_ref, {object}); |
| 401 | } |
| 402 | |
| 403 | return std::nullopt; |
| 404 | } |
| 405 | |
| 406 | std::optional<std::string> TransferStmtVisitor::VisitStringLiteral( |
| 407 | const clang::StringLiteral* strlit) { |
Luca Versari | a12c9d5 | 2023-02-23 08:40:48 -0800 | [diff] [blame] | 408 | points_to_map_.SetExprObjectSet( |
| 409 | strlit, {object_repository_.GetStringLiteralObject()}); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 410 | return std::nullopt; |
| 411 | } |
| 412 | |
| 413 | std::optional<std::string> TransferStmtVisitor::VisitCastExpr( |
| 414 | const clang::CastExpr* cast) { |
| 415 | switch (cast->getCastKind()) { |
| 416 | case clang::CK_LValueToRValue: { |
| 417 | if (cast->getType()->isPointerType()) { |
| 418 | // Converting from a glvalue to a prvalue means that we need to perform |
| 419 | // a dereferencing operation because the objects associated with |
| 420 | // glvalues and prvalues have different meanings: |
| 421 | // - A glvalue is associated with the object identified by the glvalue. |
| 422 | // - A prvalue is only associated with an object if the prvalue is of |
| 423 | // pointer type; the object it is associated with is the object the |
| 424 | // pointer points to. |
| 425 | // See also documentation for PointsToMap. |
| 426 | ObjectSet points_to = points_to_map_.GetPointerPointsToSet( |
| 427 | points_to_map_.GetExprObjectSet(cast->getSubExpr())); |
| 428 | points_to_map_.SetExprObjectSet(cast, points_to); |
| 429 | } |
| 430 | break; |
| 431 | } |
| 432 | case clang::CK_NullToPointer: { |
| 433 | points_to_map_.SetExprObjectSet(cast, {}); |
| 434 | break; |
| 435 | } |
| 436 | // These casts are just no-ops from a Object point of view. |
| 437 | case clang::CK_FunctionToPointerDecay: |
| 438 | case clang::CK_BuiltinFnToFnPtr: |
| 439 | case clang::CK_ArrayToPointerDecay: |
| 440 | case clang::CK_UserDefinedConversion: |
| 441 | // Note on CK_UserDefinedConversion: The actual conversion happens in a |
| 442 | // CXXMemberCallExpr that is a subexpression of this CastExpr. The |
| 443 | // CK_UserDefinedConversion is just used to mark the fact that this is a |
| 444 | // user-defined conversion; it's therefore a no-op for our purposes. |
| 445 | case clang::CK_NoOp: { |
| 446 | clang::QualType type = cast->getType().getCanonicalType(); |
| 447 | if (type->isPointerType() || cast->isGLValue()) { |
| 448 | points_to_map_.SetExprObjectSet( |
| 449 | cast, points_to_map_.GetExprObjectSet(cast->getSubExpr())); |
| 450 | } |
| 451 | break; |
| 452 | } |
| 453 | case clang::CK_DerivedToBase: |
| 454 | case clang::CK_UncheckedDerivedToBase: |
| 455 | case clang::CK_BaseToDerived: |
| 456 | case clang::CK_Dynamic: { |
| 457 | // These need to be mapped to what the subexpr points to. |
| 458 | // (Simple cases just work okay with this; may need to be revisited when |
| 459 | // we add more inheritance support.) |
| 460 | ObjectSet points_to = points_to_map_.GetExprObjectSet(cast->getSubExpr()); |
| 461 | points_to_map_.SetExprObjectSet(cast, points_to); |
| 462 | break; |
| 463 | } |
| 464 | case clang::CK_BitCast: |
| 465 | case clang::CK_LValueBitCast: |
| 466 | case clang::CK_IntegralToPointer: { |
| 467 | // We don't support analyzing functions that perform a reinterpret_cast. |
| 468 | diag_reporter_( |
| 469 | func_->getBeginLoc(), |
| 470 | "cannot infer lifetimes because function uses a type-unsafe cast", |
| 471 | clang::DiagnosticIDs::Warning); |
| 472 | diag_reporter_(cast->getBeginLoc(), "type-unsafe cast occurs here", |
| 473 | clang::DiagnosticIDs::Note); |
| 474 | return "type-unsafe cast prevents analysis"; |
| 475 | } |
| 476 | default: { |
| 477 | if (cast->isGLValue() || |
| 478 | cast->getType().getCanonicalType()->isPointerType()) { |
| 479 | llvm::errs() << "Unknown cast type:\n"; |
| 480 | cast->dump(); |
| 481 | // No-noop casts of pointer types are not handled yet. |
| 482 | llvm::report_fatal_error("unknown cast type encountered"); |
| 483 | } |
| 484 | } |
| 485 | } |
| 486 | return std::nullopt; |
| 487 | } |
| 488 | |
| 489 | std::optional<std::string> TransferStmtVisitor::VisitReturnStmt( |
| 490 | const clang::ReturnStmt* return_stmt) { |
| 491 | clang::QualType return_type = func_->getReturnType(); |
| 492 | // We only need to handle pointers and references. |
| 493 | // For record types, initialization of the return value has already been |
| 494 | // handled in VisitCXXConstructExpr() or VisitInitListExpr(), so nothing |
| 495 | // to do here. |
| 496 | if (!return_type->isPointerType() && !return_type->isReferenceType()) { |
| 497 | return std::nullopt; |
| 498 | } |
| 499 | |
| 500 | const clang::Expr* ret_expr = return_stmt->getRetValue(); |
| 501 | // This occurs when computing `ret_expr`s result includes creating temporary |
| 502 | // objects with destructors. We want to find the value to be returned inside |
| 503 | // the ExprWithCleanups. |
| 504 | // |
| 505 | // The PointsToMap::GetExprObjectSet() function could do this but it doesn't |
| 506 | // understand the context from which it is being called. This operation needs |
| 507 | // to be done only in cases where we are leaving scope - that is, the return |
| 508 | // statement. And the return statement also needs to look for initializers in |
| 509 | // its sub expressions, after looking inside ExprWithCleanups. |
| 510 | // |
| 511 | // That means GetExprObjectSet() would need to also look for initializers but |
| 512 | // we don't want to do this on every call to GetExprObjectSet(). |
| 513 | if (auto cleanups = clang::dyn_cast<clang::ExprWithCleanups>(ret_expr)) { |
| 514 | ret_expr = cleanups->getSubExpr(); |
| 515 | } |
| 516 | |
| 517 | ObjectSet expr_points_to = points_to_map_.GetExprObjectSet(ret_expr); |
Luca Versari | d1b2e69 | 2023-01-18 13:07:25 -0800 | [diff] [blame] | 518 | GenerateConstraintsForAssignment( |
| 519 | {object_repository_.GetReturnObject()}, expr_points_to, return_type, |
| 520 | object_repository_, points_to_map_, constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 521 | return std::nullopt; |
| 522 | } |
| 523 | |
| 524 | std::optional<std::string> TransferStmtVisitor::VisitDeclStmt( |
| 525 | const clang::DeclStmt* decl_stmt) { |
| 526 | for (const clang::Decl* decl : decl_stmt->decls()) { |
| 527 | if (const auto* var_decl = clang::dyn_cast<clang::VarDecl>(decl)) { |
Martin Brænne | 46b5f07 | 2022-07-01 02:29:16 -0700 | [diff] [blame] | 528 | const Object* var_object = object_repository_.GetDeclObject(var_decl); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 529 | |
| 530 | // Don't need to record initializers because initialization has already |
| 531 | // happened in VisitCXXConstructExpr(), VisitInitListExpr(), or |
| 532 | // VisitCallExpr(). |
| 533 | if (var_decl->hasInit() && !var_decl->getType()->isRecordType()) { |
Martin Brænne | 2a1b27a | 2022-07-01 06:17:32 -0700 | [diff] [blame] | 534 | TransferInitializer(var_object, var_decl->getType(), object_repository_, |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 535 | var_decl->getInit(), TargetPointeeBehavior::kIgnore, |
| 536 | points_to_map_, constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 537 | } |
| 538 | } |
| 539 | } |
| 540 | return std::nullopt; |
| 541 | } |
| 542 | |
| 543 | std::optional<std::string> TransferStmtVisitor::VisitUnaryOperator( |
| 544 | const clang::UnaryOperator* op) { |
| 545 | if (!op->isGLValue() && !op->getType()->isPointerType() && |
| 546 | !op->getType()->isArrayType()) { |
| 547 | return std::nullopt; |
| 548 | } |
| 549 | |
| 550 | ObjectSet sub_points_to = points_to_map_.GetExprObjectSet(op->getSubExpr()); |
| 551 | |
| 552 | // Maybe surprisingly, the code here doesn't do any actual address-taking or |
| 553 | // dereferencing. |
| 554 | // This is because AddrOf and Deref really only do a reinterpretation: |
| 555 | // - AddrOf reinterprets a glvalue of type T as a prvalue of type T* |
| 556 | // - Deref reinterprets an prvalue of type T* as a glvalue of type T |
| 557 | // (See also the assertions below.) |
| 558 | // The actual dereferencing happens in the LValueToRValue CastExpr, |
| 559 | // see TransferCastExpr(). |
| 560 | |
| 561 | switch (op->getOpcode()) { |
| 562 | case clang::UO_AddrOf: |
| 563 | assert(!op->isGLValue()); |
| 564 | assert(op->getSubExpr()->isGLValue()); |
| 565 | points_to_map_.SetExprObjectSet(op, sub_points_to); |
| 566 | break; |
| 567 | |
| 568 | case clang::UO_Deref: |
| 569 | assert(op->isGLValue()); |
| 570 | assert(!op->getSubExpr()->isGLValue()); |
| 571 | points_to_map_.SetExprObjectSet(op, sub_points_to); |
| 572 | break; |
| 573 | |
| 574 | case clang::UO_PostInc: |
| 575 | case clang::UO_PostDec: |
| 576 | assert(!op->isGLValue()); |
| 577 | assert(op->getSubExpr()->isGLValue()); |
| 578 | points_to_map_.SetExprObjectSet( |
| 579 | op, points_to_map_.GetPointerPointsToSet(sub_points_to)); |
| 580 | break; |
| 581 | |
| 582 | case clang::UO_PreInc: |
| 583 | case clang::UO_PreDec: |
| 584 | assert(op->isGLValue()); |
| 585 | assert(op->getSubExpr()->isGLValue()); |
| 586 | points_to_map_.SetExprObjectSet(op, sub_points_to); |
| 587 | break; |
| 588 | |
| 589 | default: |
| 590 | break; |
| 591 | } |
| 592 | return std::nullopt; |
| 593 | } |
| 594 | |
| 595 | std::optional<std::string> TransferStmtVisitor::VisitArraySubscriptExpr( |
| 596 | const clang::ArraySubscriptExpr* subscript) { |
| 597 | // For our purposes here, a subscripting operation is equivalent to a |
| 598 | // dereference on its base - we don't make a distinction between different |
| 599 | // lifetimes in an array. This effectively merges the points-to sets of all |
Luca Versari | e5f66ad | 2022-08-02 02:18:15 -0700 | [diff] [blame] | 600 | // elements in the array. See [/docs/lifetimes_static_analysis.md](/docs/lifetimes_static_analysis.md) for why we |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 601 | // don't track individual array elements. |
| 602 | |
| 603 | ObjectSet sub_points_to = |
| 604 | points_to_map_.GetExprObjectSet(subscript->getBase()); |
| 605 | |
| 606 | assert(subscript->isGLValue()); |
| 607 | assert(!subscript->getBase()->isGLValue()); |
| 608 | points_to_map_.SetExprObjectSet(subscript, sub_points_to); |
| 609 | return std::nullopt; |
| 610 | } |
| 611 | |
| 612 | std::optional<std::string> TransferStmtVisitor::VisitBinaryOperator( |
| 613 | const clang::BinaryOperator* op) { |
| 614 | switch (op->getOpcode()) { |
| 615 | case clang::BO_Assign: { |
| 616 | assert(op->getLHS()->isGLValue()); |
| 617 | ObjectSet lhs_points_to = points_to_map_.GetExprObjectSet(op->getLHS()); |
| 618 | points_to_map_.SetExprObjectSet(op, lhs_points_to); |
| 619 | // Because of how we handle reference-like structs, a member access to a |
| 620 | // non-reference-like field in a struct might still produce lifetimes. We |
| 621 | // don't want to change points-to sets in those cases. |
| 622 | if (!op->getLHS()->getType()->isPointerType()) break; |
| 623 | ObjectSet rhs_points_to = points_to_map_.GetExprObjectSet(op->getRHS()); |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 624 | // We can overwrite (instead of extend) the destination points-to-set |
| 625 | // only in very specific circumstances: |
| 626 | // - We need to know unambiguously what the LHS refers to, so that we |
| 627 | // know we're definitely writing to a particular object, and |
Luca Versari | 53a2f58 | 2023-01-16 10:09:29 -0800 | [diff] [blame] | 628 | // - That destination object needs to be "single-valued" (see docstring of |
| 629 | // LifetimeLattice::SingleValuedObjects for the definition of this |
| 630 | // term). |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 631 | if (lhs_points_to.size() == 1 && |
Luca Versari | 53a2f58 | 2023-01-16 10:09:29 -0800 | [diff] [blame] | 632 | single_valued_objects_.Contains(*lhs_points_to.begin())) { |
Luca Versari | 1f9fc2e | 2022-08-17 07:06:00 -0700 | [diff] [blame] | 633 | // Replacing the points-to-set entirely does not generate any |
| 634 | // constraints. |
| 635 | points_to_map_.SetPointerPointsToSet(lhs_points_to, rhs_points_to); |
| 636 | } else { |
| 637 | HandlePointsToSetExtension(lhs_points_to, rhs_points_to, |
| 638 | op->getLHS()->getType(), object_repository_, |
| 639 | points_to_map_, constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 640 | } |
| 641 | break; |
| 642 | } |
| 643 | |
| 644 | case clang::BO_Add: |
| 645 | case clang::BO_Sub: { |
| 646 | // Pointer arithmetic. |
| 647 | // We are only interested in the case in which exactly one of the two |
| 648 | // operands is a pointer (in particular we want to exclude int* - int*). |
| 649 | if (op->getLHS()->getType()->isPointerType() ^ |
| 650 | op->getRHS()->getType()->isPointerType()) { |
| 651 | if (op->getLHS()->getType()->isPointerType()) { |
| 652 | points_to_map_.SetExprObjectSet( |
| 653 | op, points_to_map_.GetExprObjectSet(op->getLHS())); |
| 654 | } else { |
| 655 | points_to_map_.SetExprObjectSet( |
| 656 | op, points_to_map_.GetExprObjectSet(op->getRHS())); |
| 657 | } |
| 658 | } |
| 659 | break; |
| 660 | } |
| 661 | |
| 662 | default: |
| 663 | break; |
| 664 | } |
| 665 | return std::nullopt; |
| 666 | } |
| 667 | |
| 668 | std::optional<std::string> TransferStmtVisitor::VisitConditionalOperator( |
| 669 | const clang::ConditionalOperator* op) { |
| 670 | clang::QualType type = op->getType().getCanonicalType(); |
| 671 | |
| 672 | if (op->isGLValue() || type->isPointerType()) { |
Kinuko Yasuda | 45fd4be | 2023-05-03 02:11:57 -0700 | [diff] [blame] | 673 | // It is possible that either of the expressions may not have an ObjectSet |
| 674 | // if the node is pruned as it is considered unreachable. |
| 675 | assert(points_to_map_.ExprHasObjectSet(op->getTrueExpr()) || |
| 676 | points_to_map_.ExprHasObjectSet(op->getFalseExpr())); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 677 | ObjectSet points_to_true = |
Kinuko Yasuda | 45fd4be | 2023-05-03 02:11:57 -0700 | [diff] [blame] | 678 | points_to_map_.ExprHasObjectSet(op->getTrueExpr()) |
| 679 | ? points_to_map_.GetExprObjectSet(op->getTrueExpr()) |
| 680 | : ObjectSet(); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 681 | ObjectSet points_to_false = |
Kinuko Yasuda | 45fd4be | 2023-05-03 02:11:57 -0700 | [diff] [blame] | 682 | points_to_map_.ExprHasObjectSet(op->getFalseExpr()) |
| 683 | ? points_to_map_.GetExprObjectSet(op->getFalseExpr()) |
| 684 | : ObjectSet(); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 685 | points_to_map_.SetExprObjectSet(op, points_to_true.Union(points_to_false)); |
| 686 | } |
| 687 | return std::nullopt; |
| 688 | } |
| 689 | |
| 690 | std::optional<std::string> TransferStmtVisitor::VisitInitListExpr( |
| 691 | const clang::InitListExpr* init_list) { |
| 692 | if (init_list->isSyntacticForm()) { |
| 693 | // We are only interested in the semantic form, which is fully realized, |
| 694 | // and is the one considered to be the initializer. |
| 695 | return std::nullopt; |
| 696 | } |
| 697 | if (IsInitExprInitializingARecordObject(init_list)) { |
| 698 | if (init_list->isTransparent()) { |
| 699 | // A transparent initializer list does nothing, the actual initializer |
| 700 | // terminating expression is within, and has already transferred lifetimes |
| 701 | // up to the object being initialized. |
| 702 | return std::nullopt; |
| 703 | } |
| 704 | // The object set for each field should be pointing to the initializers. |
Martin Brænne | d7c0d0b | 2022-07-01 05:43:00 -0700 | [diff] [blame] | 705 | const Object* init_object = |
| 706 | object_repository_.GetInitializedObject(init_list); |
Martin Brænne | 2a1b27a | 2022-07-01 06:17:32 -0700 | [diff] [blame] | 707 | TransferInitializer(init_object, init_list->getType(), object_repository_, |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 708 | init_list, TargetPointeeBehavior::kKeep, points_to_map_, |
| 709 | constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 710 | } else { |
| 711 | // If the InitListExpr is not initializing a record object, we assume it's |
| 712 | // initializing an array or a reference and hence associate the InitListExpr |
| 713 | // with the union of the points-to sets of the initializers (as the analysis |
| 714 | // is array-insensitive). |
| 715 | ObjectSet targets; |
| 716 | for (clang::Expr* expr : init_list->inits()) { |
| 717 | // If we are constructing an initializer list of non-pointer types, we |
| 718 | // don't need to do anything here. Note that initializer list elements |
| 719 | // must all have the same type in this case. |
| 720 | if (PointeeType(expr->getType()).isNull() && !expr->isGLValue()) { |
| 721 | return std::nullopt; |
| 722 | } |
| 723 | targets.Add(points_to_map_.GetExprObjectSet(expr)); |
| 724 | } |
| 725 | points_to_map_.SetExprObjectSet(init_list, std::move(targets)); |
| 726 | } |
| 727 | return std::nullopt; |
| 728 | } |
| 729 | |
| 730 | std::optional<std::string> TransferStmtVisitor::VisitMaterializeTemporaryExpr( |
| 731 | const clang::MaterializeTemporaryExpr* temporary_expr) { |
Martin Brænne | d7c0d0b | 2022-07-01 05:43:00 -0700 | [diff] [blame] | 732 | const Object* temp_object = |
| 733 | object_repository_.GetTemporaryObject(temporary_expr); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 734 | points_to_map_.SetExprObjectSet(temporary_expr, {temp_object}); |
| 735 | return std::nullopt; |
| 736 | } |
| 737 | |
| 738 | std::optional<std::string> TransferStmtVisitor::VisitMemberExpr( |
| 739 | const clang::MemberExpr* member) { |
| 740 | ObjectSet struct_points_to = |
| 741 | points_to_map_.GetExprObjectSet(member->getBase()); |
| 742 | |
| 743 | if (const auto* method = |
| 744 | clang::dyn_cast<clang::CXXMethodDecl>(member->getMemberDecl())) { |
| 745 | // It doesn't really make sense to associate an object set with a non-static |
| 746 | // member function. |
| 747 | // If the member function is being called, we're not interested in its |
| 748 | // "value" anyway. If the non-static member function is used outside of a |
| 749 | // function call, then, it's a pointer-to-member, but those aren't |
| 750 | // really pointers anyway, and we'll need special treatment for them. |
| 751 | if (method->isStatic()) { |
| 752 | points_to_map_.SetExprObjectSet( |
| 753 | member, {object_repository_.GetDeclObject(method)}); |
| 754 | } |
| 755 | return std::nullopt; |
| 756 | } |
| 757 | |
| 758 | auto field = clang::dyn_cast<clang::FieldDecl>(member->getMemberDecl()); |
| 759 | if (field == nullptr) { |
| 760 | llvm::report_fatal_error("indirect member access is not supported yet"); |
| 761 | } |
| 762 | ObjectSet expr_points_to = |
| 763 | object_repository_.GetFieldObject(struct_points_to, field); |
| 764 | if (field->getType()->isReferenceType()) { |
| 765 | expr_points_to = points_to_map_.GetPointerPointsToSet(expr_points_to); |
| 766 | } |
| 767 | points_to_map_.SetExprObjectSet(member, expr_points_to); |
| 768 | return std::nullopt; |
| 769 | } |
| 770 | |
| 771 | std::optional<std::string> TransferStmtVisitor::VisitCXXThisExpr( |
| 772 | const clang::CXXThisExpr* this_expr) { |
Martin Brænne | d7c0d0b | 2022-07-01 05:43:00 -0700 | [diff] [blame] | 773 | std::optional<const Object*> this_object = object_repository_.GetThisObject(); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 774 | assert(this_object.has_value()); |
| 775 | points_to_map_.SetExprObjectSet(this_expr, ObjectSet{this_object.value()}); |
| 776 | return std::nullopt; |
| 777 | } |
| 778 | |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 779 | namespace { |
| 780 | void ConstrainFunctionLifetimesForCall( |
| 781 | const FunctionLifetimes& callee_lifetimes, |
| 782 | const FunctionLifetimes& placeholder_lifetimes, |
| 783 | LifetimeConstraints& constraints) { |
| 784 | // We handle function calls as follows: |
| 785 | // - We create a placeholder FunctionLifetimes for each call location, meant |
| 786 | // to indicate the concrete lifetimes that the callee is instantiated with for |
| 787 | // that specific call. |
| 788 | // - When we analyze the call, we constrain the concrete lifetimes so that |
| 789 | // they are compatible with the lifetimes of the arguments. |
| 790 | // - We then constrain the placeholder lifetimes so that the actual callee |
| 791 | // could be used in place of the placeholder callee. |
| 792 | // - As a consequence, the lifetimes of the return object are also constrained |
| 793 | // correctly (ie. in such a way that they are compatible with the callee and |
| 794 | // the call arguments). |
| 795 | // TODO(veluca): this code assumes that the lifetime of variables cannot |
| 796 | // change across function call boundaries. It is not an intrinsic limitation |
| 797 | // (could potentially be resolved by updating the PointsToMap after the fact, |
| 798 | // or - even better - by converting the entire CFG into SSA form), but for |
| 799 | // simplicity we are leaving things as they are for now; if real-world usage |
| 800 | // shows this to be an important limitation, we should revisit this decision. |
| 801 | constraints.join(LifetimeConstraints::ForCallableSubstitutionFull( |
| 802 | callee_lifetimes, placeholder_lifetimes)); |
| 803 | } |
| 804 | } // namespace |
| 805 | |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 806 | std::optional<std::string> TransferStmtVisitor::VisitCallExpr( |
| 807 | const clang::CallExpr* call) { |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 808 | struct CalleeInfo { |
| 809 | bool is_member_operator; |
| 810 | FunctionLifetimes lifetimes; |
| 811 | // Type of the function being called. Note that this might not know anything |
| 812 | // about the `this` argument for non-static methods. |
| 813 | const clang::FunctionProtoType* type; |
| 814 | }; |
| 815 | |
| 816 | llvm::SmallVector<CalleeInfo, 1> callees; |
| 817 | |
| 818 | auto add_callee_from_decl = |
| 819 | [&callees, call, |
| 820 | this](const clang::FunctionDecl* decl) -> std::optional<std::string> { |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 821 | const FunctionLifetimesOrError& callee_lifetimes_or_error = |
Luca Versari | a12c9d5 | 2023-02-23 08:40:48 -0800 | [diff] [blame] | 822 | GetFunctionLifetimes(decl, callee_lifetimes_); |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 823 | |
| 824 | if (!std::holds_alternative<FunctionLifetimes>(callee_lifetimes_or_error)) { |
Kinuko Yasuda | 45fd4be | 2023-05-03 02:11:57 -0700 | [diff] [blame] | 825 | // Note: It is possible that this does not have an entry if the function |
| 826 | // is not analyzed (because its body is not defined in this TU). If this |
| 827 | // happens, we currently bail without analyzing further. |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 828 | return "No lifetimes for callee '" + decl->getNameAsString() + "': " + |
| 829 | std::get<FunctionAnalysisError>(callee_lifetimes_or_error).message; |
| 830 | } |
| 831 | FunctionLifetimes callee_lifetimes = |
| 832 | std::get<FunctionLifetimes>(callee_lifetimes_or_error); |
| 833 | |
| 834 | bool is_member_operator = clang::isa<clang::CXXOperatorCallExpr>(call) && |
| 835 | clang::isa<clang::CXXMethodDecl>(decl); |
| 836 | callees.push_back( |
| 837 | CalleeInfo{is_member_operator, callee_lifetimes, |
| 838 | decl->getType()->getAs<clang::FunctionProtoType>()}); |
| 839 | return std::nullopt; |
| 840 | }; |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 841 | |
| 842 | const clang::FunctionDecl* direct_callee = call->getDirectCallee(); |
| 843 | if (direct_callee) { |
| 844 | // This code path is needed for non-static member functions, as those don't |
| 845 | // have an `Object` for their callees. |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 846 | if (auto err = add_callee_from_decl(direct_callee); err.has_value()) { |
| 847 | return err; |
| 848 | } |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 849 | } else { |
| 850 | const clang::Expr* callee = call->getCallee(); |
| 851 | for (const auto& object : points_to_map_.GetExprObjectSet(callee)) { |
Luca Versari | a12c9d5 | 2023-02-23 08:40:48 -0800 | [diff] [blame] | 852 | if (const std::optional<FunctionLifetimes>& func_lifetimes = |
| 853 | object->GetFuncLifetimes(); |
| 854 | func_lifetimes.has_value()) { |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 855 | callees.push_back( |
| 856 | {.is_member_operator = false, |
| 857 | .lifetimes = *func_lifetimes, |
| 858 | .type = object->Type()->getAs<clang::FunctionProtoType>()}); |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 859 | } |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 860 | } |
| 861 | } |
| 862 | |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 863 | for (const CalleeInfo& callee : callees) { |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 864 | ConstrainFunctionLifetimesForCall( |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 865 | callee.lifetimes, object_repository_.GetCallExprVirtualLifetimes(call), |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 866 | constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 867 | |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 868 | for (size_t i = callee.is_member_operator ? 1 : 0; i < call->getNumArgs(); |
| 869 | i++) { |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 870 | // We can't just use SetPointerPointsToSet here because call->getArg(i) |
| 871 | // might not have an ObjectSet (for example for integer constants); it |
| 872 | // also may be needed for struct initialization. |
| 873 | // Note that we don't need to worry about possibly extending the |
| 874 | // PointsToSet more than needed, as dataflow analysis relies on points-to |
| 875 | // sets never shrinking. |
| 876 | TransferInitializer( |
Martin Brænne | 2a1b27a | 2022-07-01 06:17:32 -0700 | [diff] [blame] | 877 | object_repository_.GetCallExprArgumentObject(call, i), |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 878 | callee.type->getParamType(callee.is_member_operator ? i - 1 : i), |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 879 | object_repository_, call->getArg(i), TargetPointeeBehavior::kKeep, |
| 880 | points_to_map_, constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 881 | } |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 882 | |
| 883 | std::optional<ObjectSet> this_object_set; |
Luca Versari | 5082488 | 2023-01-23 05:51:35 -0800 | [diff] [blame] | 884 | if (callee.is_member_operator) { |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 885 | this_object_set = points_to_map_.GetExprObjectSet(call->getArg(0)); |
| 886 | } else if (const auto* member_call = |
| 887 | clang::dyn_cast<clang::CXXMemberCallExpr>(call)) { |
| 888 | this_object_set = points_to_map_.GetExprObjectSet( |
| 889 | member_call->getImplicitObjectArgument()); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 890 | } |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 891 | if (this_object_set.has_value()) { |
| 892 | const Object* this_ptr = object_repository_.GetCallExprThisPointer(call); |
| 893 | HandlePointsToSetExtension({this_ptr}, *this_object_set, this_ptr->Type(), |
| 894 | object_repository_, points_to_map_, |
| 895 | constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 896 | } |
| 897 | } |
| 898 | |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 899 | if (IsInitExprInitializingARecordObject(call)) { |
| 900 | const Object* init_object = object_repository_.GetInitializedObject(call); |
| 901 | GenerateConstraintsForObjectLifetimeEquality( |
| 902 | {init_object}, {object_repository_.GetCallExprRetObject(call)}, |
| 903 | init_object->Type(), points_to_map_, object_repository_, constraints_); |
| 904 | } else { |
| 905 | ObjectSet ret_pts = points_to_map_.GetPointerPointsToSet( |
| 906 | object_repository_.GetCallExprRetObject(call)); |
| 907 | // SetExprObjectSet will assert-fail if `call` does not have a type that can |
| 908 | // have an object set; this `if` guards against that. |
| 909 | if (!ret_pts.empty()) { |
| 910 | points_to_map_.SetExprObjectSet(call, ret_pts); |
| 911 | } |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 912 | } |
| 913 | return std::nullopt; |
| 914 | } |
| 915 | |
| 916 | std::optional<std::string> TransferStmtVisitor::VisitCXXConstructExpr( |
| 917 | const clang::CXXConstructExpr* construct_expr) { |
| 918 | const clang::CXXConstructorDecl* constructor = |
| 919 | construct_expr->getConstructor(); |
| 920 | |
| 921 | assert(callee_lifetimes_.count(constructor->getCanonicalDecl())); |
| 922 | const FunctionLifetimesOrError& callee_lifetimes_or_error = |
| 923 | callee_lifetimes_.lookup(constructor->getCanonicalDecl()); |
| 924 | if (!std::holds_alternative<FunctionLifetimes>(callee_lifetimes_or_error)) { |
| 925 | return "No lifetimes for constructor " + constructor->getNameAsString(); |
| 926 | } |
| 927 | const FunctionLifetimes& callee_lifetimes = |
| 928 | std::get<FunctionLifetimes>(callee_lifetimes_or_error); |
| 929 | |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 930 | ConstrainFunctionLifetimesForCall( |
| 931 | callee_lifetimes, |
| 932 | object_repository_.GetCallExprVirtualLifetimes(construct_expr), |
| 933 | constraints_); |
| 934 | |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 935 | // We check <= instead of == because of default arguments. |
| 936 | assert(construct_expr->getNumArgs() <= constructor->getNumParams()); |
| 937 | |
| 938 | for (size_t i = 0; i < construct_expr->getNumArgs(); i++) { |
Martin Brænne | 2a1b27a | 2022-07-01 06:17:32 -0700 | [diff] [blame] | 939 | TransferInitializer( |
| 940 | object_repository_.GetCXXConstructExprArgumentObject(construct_expr, i), |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 941 | constructor->getParamDecl(i)->getType(), object_repository_, |
| 942 | construct_expr->getArg(i), TargetPointeeBehavior::kKeep, points_to_map_, |
| 943 | constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 944 | } |
| 945 | |
| 946 | // Handle the `this` parameter, which should point to the object getting |
| 947 | // initialized. |
Luca Versari | efeaf27 | 2023-01-16 10:19:28 -0800 | [diff] [blame] | 948 | HandlePointsToSetExtension( |
| 949 | {object_repository_.GetCXXConstructExprThisPointer(construct_expr)}, |
| 950 | {object_repository_.GetInitializedObject(construct_expr)}, |
| 951 | constructor->getThisType(), object_repository_, points_to_map_, |
| 952 | constraints_); |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 953 | |
Luca Versari | 99fddff | 2022-05-25 10:22:32 -0700 | [diff] [blame] | 954 | return std::nullopt; |
| 955 | } |
| 956 | |
| 957 | } // namespace |
| 958 | |
| 959 | } // namespace lifetimes |
| 960 | } // namespace tidy |
| 961 | } // namespace clang |