[MemProf] Templatize CallStackRadixTreeBuilder (NFC) (#117014)
[llvm-project.git] / flang / lib / Semantics / pointer-assignment.cpp
blob2450ce39215ec9964686486f3a427374cae58e64
1 //===-- lib/Semantics/pointer-assignment.cpp ------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
9 #include "pointer-assignment.h"
10 #include "definable.h"
11 #include "flang/Common/idioms.h"
12 #include "flang/Common/restorer.h"
13 #include "flang/Common/template.h"
14 #include "flang/Evaluate/characteristics.h"
15 #include "flang/Evaluate/expression.h"
16 #include "flang/Evaluate/fold.h"
17 #include "flang/Evaluate/tools.h"
18 #include "flang/Parser/message.h"
19 #include "flang/Parser/parse-tree-visitor.h"
20 #include "flang/Parser/parse-tree.h"
21 #include "flang/Semantics/expression.h"
22 #include "flang/Semantics/symbol.h"
23 #include "flang/Semantics/tools.h"
24 #include "llvm/Support/raw_ostream.h"
25 #include <optional>
26 #include <set>
27 #include <string>
28 #include <type_traits>
30 // Semantic checks for pointer assignment.
32 namespace Fortran::semantics {
34 using namespace parser::literals;
35 using evaluate::characteristics::DummyDataObject;
36 using evaluate::characteristics::FunctionResult;
37 using evaluate::characteristics::Procedure;
38 using evaluate::characteristics::TypeAndShape;
39 using parser::MessageFixedText;
40 using parser::MessageFormattedText;
42 class PointerAssignmentChecker {
43 public:
44 PointerAssignmentChecker(SemanticsContext &context, const Scope &scope,
45 parser::CharBlock source, const std::string &description)
46 : context_{context}, scope_{scope}, source_{source}, description_{
47 description} {}
48 PointerAssignmentChecker(
49 SemanticsContext &context, const Scope &scope, const Symbol &lhs)
50 : context_{context}, scope_{scope}, source_{lhs.name()},
51 description_{"pointer '"s + lhs.name().ToString() + '\''}, lhs_{&lhs} {
52 set_lhsType(TypeAndShape::Characterize(lhs, foldingContext_));
53 set_isContiguous(lhs.attrs().test(Attr::CONTIGUOUS));
54 set_isVolatile(lhs.attrs().test(Attr::VOLATILE));
56 PointerAssignmentChecker &set_lhsType(std::optional<TypeAndShape> &&);
57 PointerAssignmentChecker &set_isContiguous(bool);
58 PointerAssignmentChecker &set_isVolatile(bool);
59 PointerAssignmentChecker &set_isBoundsRemapping(bool);
60 PointerAssignmentChecker &set_isAssumedRank(bool);
61 PointerAssignmentChecker &set_pointerComponentLHS(const Symbol *);
62 bool CheckLeftHandSide(const SomeExpr &);
63 bool Check(const SomeExpr &);
65 private:
66 bool CharacterizeProcedure();
67 template <typename T> bool Check(const T &);
68 template <typename T> bool Check(const evaluate::Expr<T> &);
69 template <typename T> bool Check(const evaluate::FunctionRef<T> &);
70 template <typename T> bool Check(const evaluate::Designator<T> &);
71 bool Check(const evaluate::NullPointer &);
72 bool Check(const evaluate::ProcedureDesignator &);
73 bool Check(const evaluate::ProcedureRef &);
74 // Target is a procedure
75 bool Check(parser::CharBlock rhsName, bool isCall,
76 const Procedure * = nullptr,
77 const evaluate::SpecificIntrinsic *specific = nullptr);
78 bool LhsOkForUnlimitedPoly() const;
79 template <typename... A> parser::Message *Say(A &&...);
80 template <typename FeatureOrUsageWarning, typename... A>
81 parser::Message *Warn(FeatureOrUsageWarning, A &&...);
83 SemanticsContext &context_;
84 evaluate::FoldingContext &foldingContext_{context_.foldingContext()};
85 const Scope &scope_;
86 const parser::CharBlock source_;
87 const std::string description_;
88 const Symbol *lhs_{nullptr};
89 std::optional<TypeAndShape> lhsType_;
90 std::optional<Procedure> procedure_;
91 bool characterizedProcedure_{false};
92 bool isContiguous_{false};
93 bool isVolatile_{false};
94 bool isBoundsRemapping_{false};
95 bool isAssumedRank_{false};
96 const Symbol *pointerComponentLHS_{nullptr};
99 PointerAssignmentChecker &PointerAssignmentChecker::set_lhsType(
100 std::optional<TypeAndShape> &&lhsType) {
101 lhsType_ = std::move(lhsType);
102 return *this;
105 PointerAssignmentChecker &PointerAssignmentChecker::set_isContiguous(
106 bool isContiguous) {
107 isContiguous_ = isContiguous;
108 return *this;
111 PointerAssignmentChecker &PointerAssignmentChecker::set_isVolatile(
112 bool isVolatile) {
113 isVolatile_ = isVolatile;
114 return *this;
117 PointerAssignmentChecker &PointerAssignmentChecker::set_isBoundsRemapping(
118 bool isBoundsRemapping) {
119 isBoundsRemapping_ = isBoundsRemapping;
120 return *this;
123 PointerAssignmentChecker &PointerAssignmentChecker::set_isAssumedRank(
124 bool isAssumedRank) {
125 isAssumedRank_ = isAssumedRank;
126 return *this;
129 PointerAssignmentChecker &PointerAssignmentChecker::set_pointerComponentLHS(
130 const Symbol *symbol) {
131 pointerComponentLHS_ = symbol;
132 return *this;
135 bool PointerAssignmentChecker::CharacterizeProcedure() {
136 if (!characterizedProcedure_) {
137 characterizedProcedure_ = true;
138 if (lhs_ && IsProcedure(*lhs_)) {
139 procedure_ = Procedure::Characterize(*lhs_, foldingContext_);
142 return procedure_.has_value();
145 bool PointerAssignmentChecker::CheckLeftHandSide(const SomeExpr &lhs) {
146 if (auto whyNot{WhyNotDefinable(foldingContext_.messages().at(), scope_,
147 DefinabilityFlags{DefinabilityFlag::PointerDefinition}, lhs)}) {
148 if (auto *msg{Say(
149 "The left-hand side of a pointer assignment is not definable"_err_en_US)}) {
150 msg->Attach(std::move(whyNot->set_severity(parser::Severity::Because)));
152 return false;
153 } else if (evaluate::IsAssumedRank(lhs)) {
154 Say("The left-hand side of a pointer assignment must not be an assumed-rank dummy argument"_err_en_US);
155 return false;
156 } else {
157 return true;
161 template <typename T> bool PointerAssignmentChecker::Check(const T &) {
162 // Catch-all case for really bad target expression
163 Say("Target associated with %s must be a designator or a call to a"
164 " pointer-valued function"_err_en_US,
165 description_);
166 return false;
169 template <typename T>
170 bool PointerAssignmentChecker::Check(const evaluate::Expr<T> &x) {
171 return common::visit([&](const auto &x) { return Check(x); }, x.u);
174 bool PointerAssignmentChecker::Check(const SomeExpr &rhs) {
175 if (HasVectorSubscript(rhs)) { // C1025
176 Say("An array section with a vector subscript may not be a pointer target"_err_en_US);
177 return false;
179 if (ExtractCoarrayRef(rhs)) { // C1026
180 Say("A coindexed object may not be a pointer target"_err_en_US);
181 return false;
183 if (!common::visit([&](const auto &x) { return Check(x); }, rhs.u)) {
184 return false;
186 if (IsNullPointer(rhs)) {
187 return true;
189 if (lhs_ && IsProcedure(*lhs_)) {
190 return true;
192 if (const auto *pureProc{FindPureProcedureContaining(scope_)}) {
193 if (pointerComponentLHS_) { // C1594(4) is a hard error
194 if (const Symbol * object{FindExternallyVisibleObject(rhs, *pureProc)}) {
195 if (auto *msg{Say(
196 "Externally visible object '%s' may not be associated with pointer component '%s' in a pure procedure"_err_en_US,
197 object->name(), pointerComponentLHS_->name())}) {
198 msg->Attach(object->name(), "Object declaration"_en_US)
199 .Attach(
200 pointerComponentLHS_->name(), "Pointer declaration"_en_US);
202 return false;
204 } else if (const Symbol * base{GetFirstSymbol(rhs)}) {
205 if (const char *why{WhyBaseObjectIsSuspicious(
206 base->GetUltimate(), scope_)}) { // C1594(3)
207 evaluate::SayWithDeclaration(foldingContext_.messages(), *base,
208 "A pure subprogram may not use '%s' as the target of pointer assignment because it is %s"_err_en_US,
209 base->name(), why);
210 return false;
214 if (isContiguous_) {
215 if (auto contiguous{evaluate::IsContiguous(rhs, foldingContext_)}) {
216 if (!*contiguous) {
217 Say("CONTIGUOUS pointer may not be associated with a discontiguous target"_err_en_US);
218 return false;
220 } else {
221 Warn(common::UsageWarning::PointerToPossibleNoncontiguous,
222 "Target of CONTIGUOUS pointer association is not known to be contiguous"_warn_en_US);
225 // Warn about undefinable data targets
226 if (auto because{
227 WhyNotDefinable(foldingContext_.messages().at(), scope_, {}, rhs)}) {
228 if (auto *msg{Warn(common::UsageWarning::PointerToUndefinable,
229 "Pointer target is not a definable variable"_warn_en_US)}) {
230 msg->Attach(std::move(because->set_severity(parser::Severity::Because)));
231 return false;
234 return true;
237 bool PointerAssignmentChecker::Check(const evaluate::NullPointer &) {
238 return true; // P => NULL() without MOLD=; always OK
241 template <typename T>
242 bool PointerAssignmentChecker::Check(const evaluate::FunctionRef<T> &f) {
243 std::string funcName;
244 const auto *symbol{f.proc().GetSymbol()};
245 if (symbol) {
246 funcName = symbol->name().ToString();
247 } else if (const auto *intrinsic{f.proc().GetSpecificIntrinsic()}) {
248 funcName = intrinsic->name;
250 auto proc{
251 Procedure::Characterize(f.proc(), foldingContext_, /*emitError=*/true)};
252 if (!proc) {
253 return false;
255 std::optional<MessageFixedText> msg;
256 const auto &funcResult{proc->functionResult}; // C1025
257 if (!funcResult) {
258 msg = "%s is associated with the non-existent result of reference to"
259 " procedure"_err_en_US;
260 } else if (CharacterizeProcedure()) {
261 // Shouldn't be here in this function unless lhs is an object pointer.
262 msg = "Procedure %s is associated with the result of a reference to"
263 " function '%s' that does not return a procedure pointer"_err_en_US;
264 } else if (funcResult->IsProcedurePointer()) {
265 msg = "Object %s is associated with the result of a reference to"
266 " function '%s' that is a procedure pointer"_err_en_US;
267 } else if (!funcResult->attrs.test(FunctionResult::Attr::Pointer)) {
268 msg = "%s is associated with the result of a reference to function '%s'"
269 " that is a not a pointer"_err_en_US;
270 } else if (isContiguous_ &&
271 !funcResult->attrs.test(FunctionResult::Attr::Contiguous)) {
272 auto restorer{common::ScopedSet(lhs_, symbol)};
273 if (Warn(common::UsageWarning::PointerToPossibleNoncontiguous,
274 "CONTIGUOUS %s is associated with the result of reference to function '%s' that is not known to be contiguous"_warn_en_US,
275 description_, funcName)) {
276 return false;
278 } else if (lhsType_) {
279 const auto *frTypeAndShape{funcResult->GetTypeAndShape()};
280 CHECK(frTypeAndShape);
281 if (!lhsType_->IsCompatibleWith(foldingContext_.messages(), *frTypeAndShape,
282 "pointer", "function result",
283 /*omitShapeConformanceCheck=*/isBoundsRemapping_ || isAssumedRank_,
284 evaluate::CheckConformanceFlags::BothDeferredShape)) {
285 return false; // IsCompatibleWith() emitted message
288 if (msg) {
289 auto restorer{common::ScopedSet(lhs_, symbol)};
290 Say(*msg, description_, funcName);
291 return false;
293 return true;
296 template <typename T>
297 bool PointerAssignmentChecker::Check(const evaluate::Designator<T> &d) {
298 const Symbol *last{d.GetLastSymbol()};
299 const Symbol *base{d.GetBaseObject().symbol()};
300 if (!last || !base) {
301 // P => "character literal"(1:3)
302 Say("Pointer target is not a named entity"_err_en_US);
303 return false;
305 std::optional<std::variant<MessageFixedText, MessageFormattedText>> msg;
306 if (CharacterizeProcedure()) {
307 // Shouldn't be here in this function unless lhs is an object pointer.
308 msg = "In assignment to procedure %s, the target is not a procedure or"
309 " procedure pointer"_err_en_US;
310 } else if (!evaluate::GetLastTarget(GetSymbolVector(d))) { // C1025
311 msg = "In assignment to object %s, the target '%s' is not an object with"
312 " POINTER or TARGET attributes"_err_en_US;
313 } else if (auto rhsType{TypeAndShape::Characterize(d, foldingContext_)}) {
314 if (!lhsType_) {
315 msg = "%s associated with object '%s' with incompatible type or"
316 " shape"_err_en_US;
317 } else if (rhsType->corank() > 0 &&
318 (isVolatile_ != last->attrs().test(Attr::VOLATILE))) { // C1020
319 // TODO: what if A is VOLATILE in A%B%C? need a better test here
320 if (isVolatile_) {
321 msg = "Pointer may not be VOLATILE when target is a"
322 " non-VOLATILE coarray"_err_en_US;
323 } else {
324 msg = "Pointer must be VOLATILE when target is a"
325 " VOLATILE coarray"_err_en_US;
327 } else if (rhsType->type().IsUnlimitedPolymorphic()) {
328 if (!LhsOkForUnlimitedPoly()) {
329 msg = "Pointer type must be unlimited polymorphic or non-extensible"
330 " derived type when target is unlimited polymorphic"_err_en_US;
332 } else {
333 if (!lhsType_->type().IsTkLenCompatibleWith(rhsType->type())) {
334 msg = MessageFormattedText{
335 "Target type %s is not compatible with pointer type %s"_err_en_US,
336 rhsType->type().AsFortran(), lhsType_->type().AsFortran()};
338 } else if (!isBoundsRemapping_ &&
339 !lhsType_->attrs().test(TypeAndShape::Attr::AssumedRank)) {
340 int lhsRank{lhsType_->Rank()};
341 int rhsRank{rhsType->Rank()};
342 if (lhsRank != rhsRank) {
343 msg = MessageFormattedText{
344 "Pointer has rank %d but target has rank %d"_err_en_US, lhsRank,
345 rhsRank};
350 if (msg) {
351 auto restorer{common::ScopedSet(lhs_, last)};
352 if (auto *m{std::get_if<MessageFixedText>(&*msg)}) {
353 std::string buf;
354 llvm::raw_string_ostream ss{buf};
355 d.AsFortran(ss);
356 Say(*m, description_, buf);
357 } else {
358 Say(std::get<MessageFormattedText>(*msg));
360 return false;
361 } else {
362 context_.NoteDefinedSymbol(*base);
363 return true;
367 // Common handling for procedure pointer right-hand sides
368 bool PointerAssignmentChecker::Check(parser::CharBlock rhsName, bool isCall,
369 const Procedure *rhsProcedure,
370 const evaluate::SpecificIntrinsic *specific) {
371 std::string whyNot;
372 std::optional<std::string> warning;
373 CharacterizeProcedure();
374 if (std::optional<MessageFixedText> msg{evaluate::CheckProcCompatibility(
375 isCall, procedure_, rhsProcedure, specific, whyNot, warning,
376 /*ignoreImplicitVsExplicit=*/isCall)}) {
377 Say(std::move(*msg), description_, rhsName, whyNot);
378 return false;
380 if (warning) {
381 Warn(common::UsageWarning::ProcDummyArgShapes,
382 "%s and %s may not be completely compatible procedures: %s"_warn_en_US,
383 description_, rhsName, std::move(*warning));
385 return true;
388 bool PointerAssignmentChecker::Check(const evaluate::ProcedureDesignator &d) {
389 const Symbol *symbol{d.GetSymbol()};
390 if (symbol) {
391 if (const auto *subp{
392 symbol->GetUltimate().detailsIf<SubprogramDetails>()}) {
393 if (subp->stmtFunction()) {
394 evaluate::SayWithDeclaration(foldingContext_.messages(), *symbol,
395 "Statement function '%s' may not be the target of a pointer assignment"_err_en_US,
396 symbol->name());
397 return false;
399 } else if (symbol->has<ProcBindingDetails>()) {
400 evaluate::AttachDeclaration(
401 Warn(common::LanguageFeature::BindingAsProcedure,
402 "Procedure binding '%s' used as target of a pointer assignment"_port_en_US,
403 symbol->name()),
404 *symbol);
407 if (auto chars{
408 Procedure::Characterize(d, foldingContext_, /*emitError=*/true)}) {
409 // Disregard the elemental attribute of RHS intrinsics.
410 if (symbol && symbol->GetUltimate().attrs().test(Attr::INTRINSIC)) {
411 chars->attrs.reset(Procedure::Attr::Elemental);
413 return Check(d.GetName(), false, &*chars, d.GetSpecificIntrinsic());
414 } else {
415 return Check(d.GetName(), false);
419 bool PointerAssignmentChecker::Check(const evaluate::ProcedureRef &ref) {
420 auto chars{Procedure::Characterize(ref, foldingContext_)};
421 return Check(ref.proc().GetName(), true, common::GetPtrFromOptional(chars));
424 // The target can be unlimited polymorphic if the pointer is, or if it is
425 // a non-extensible derived type.
426 bool PointerAssignmentChecker::LhsOkForUnlimitedPoly() const {
427 const auto &type{lhsType_->type()};
428 if (type.category() != TypeCategory::Derived || type.IsAssumedType()) {
429 return false;
430 } else if (type.IsUnlimitedPolymorphic()) {
431 return true;
432 } else {
433 return !IsExtensibleType(&type.GetDerivedTypeSpec());
437 template <typename... A>
438 parser::Message *PointerAssignmentChecker::Say(A &&...x) {
439 auto *msg{foldingContext_.messages().Say(std::forward<A>(x)...)};
440 if (msg) {
441 if (lhs_) {
442 return evaluate::AttachDeclaration(msg, *lhs_);
444 if (!source_.empty()) {
445 msg->Attach(source_, "Declaration of %s"_en_US, description_);
448 return msg;
451 template <typename FeatureOrUsageWarning, typename... A>
452 parser::Message *PointerAssignmentChecker::Warn(
453 FeatureOrUsageWarning warning, A &&...x) {
454 auto *msg{context_.Warn(
455 warning, foldingContext_.messages().at(), std::forward<A>(x)...)};
456 if (msg) {
457 if (lhs_) {
458 return evaluate::AttachDeclaration(msg, *lhs_);
460 if (!source_.empty()) {
461 msg->Attach(source_, "Declaration of %s"_en_US, description_);
464 return msg;
467 // Verify that any bounds on the LHS of a pointer assignment are valid.
468 // Return true if it is a bound-remapping so we can perform further checks.
469 static bool CheckPointerBounds(
470 evaluate::FoldingContext &context, const evaluate::Assignment &assignment) {
471 auto &messages{context.messages()};
472 const SomeExpr &lhs{assignment.lhs};
473 const SomeExpr &rhs{assignment.rhs};
474 bool isBoundsRemapping{false};
475 std::size_t numBounds{common::visit(
476 common::visitors{
477 [&](const evaluate::Assignment::BoundsSpec &bounds) {
478 return bounds.size();
480 [&](const evaluate::Assignment::BoundsRemapping &bounds) {
481 isBoundsRemapping = true;
482 evaluate::ExtentExpr lhsSizeExpr{1};
483 for (const auto &bound : bounds) {
484 lhsSizeExpr = std::move(lhsSizeExpr) *
485 (common::Clone(bound.second) - common::Clone(bound.first) +
486 evaluate::ExtentExpr{1});
488 if (std::optional<std::int64_t> lhsSize{evaluate::ToInt64(
489 evaluate::Fold(context, std::move(lhsSizeExpr)))}) {
490 if (auto shape{evaluate::GetShape(context, rhs)}) {
491 if (std::optional<std::int64_t> rhsSize{
492 evaluate::ToInt64(evaluate::Fold(
493 context, evaluate::GetSize(std::move(*shape))))}) {
494 if (*lhsSize > *rhsSize) {
495 messages.Say(
496 "Pointer bounds require %d elements but target has"
497 " only %d"_err_en_US,
498 *lhsSize, *rhsSize); // 10.2.2.3(9)
503 return bounds.size();
505 [](const auto &) -> std::size_t {
506 DIE("not valid for pointer assignment");
509 assignment.u)};
510 if (numBounds > 0) {
511 if (lhs.Rank() != static_cast<int>(numBounds)) {
512 messages.Say("Pointer '%s' has rank %d but the number of bounds specified"
513 " is %d"_err_en_US,
514 lhs.AsFortran(), lhs.Rank(), numBounds); // C1018
517 if (isBoundsRemapping && rhs.Rank() != 1 &&
518 !evaluate::IsSimplyContiguous(rhs, context)) {
519 messages.Say("Pointer bounds remapping target must have rank 1 or be"
520 " simply contiguous"_err_en_US); // 10.2.2.3(9)
522 return isBoundsRemapping;
525 bool CheckPointerAssignment(SemanticsContext &context,
526 const evaluate::Assignment &assignment, const Scope &scope) {
527 return CheckPointerAssignment(context, assignment.lhs, assignment.rhs, scope,
528 CheckPointerBounds(context.foldingContext(), assignment),
529 /*isAssumedRank=*/false);
532 bool CheckPointerAssignment(SemanticsContext &context, const SomeExpr &lhs,
533 const SomeExpr &rhs, const Scope &scope, bool isBoundsRemapping,
534 bool isAssumedRank) {
535 const Symbol *pointer{GetLastSymbol(lhs)};
536 if (!pointer) {
537 return false; // error was reported
539 PointerAssignmentChecker checker{context, scope, *pointer};
540 checker.set_isBoundsRemapping(isBoundsRemapping);
541 checker.set_isAssumedRank(isAssumedRank);
542 bool lhsOk{checker.CheckLeftHandSide(lhs)};
543 bool rhsOk{checker.Check(rhs)};
544 return lhsOk && rhsOk; // don't short-circuit
547 bool CheckStructConstructorPointerComponent(SemanticsContext &context,
548 const Symbol &lhs, const SomeExpr &rhs, const Scope &scope) {
549 return PointerAssignmentChecker{context, scope, lhs}
550 .set_pointerComponentLHS(&lhs)
551 .Check(rhs);
554 bool CheckPointerAssignment(SemanticsContext &context, parser::CharBlock source,
555 const std::string &description, const DummyDataObject &lhs,
556 const SomeExpr &rhs, const Scope &scope, bool isAssumedRank) {
557 return PointerAssignmentChecker{context, scope, source, description}
558 .set_lhsType(common::Clone(lhs.type))
559 .set_isContiguous(lhs.attrs.test(DummyDataObject::Attr::Contiguous))
560 .set_isVolatile(lhs.attrs.test(DummyDataObject::Attr::Volatile))
561 .set_isAssumedRank(isAssumedRank)
562 .Check(rhs);
565 bool CheckInitialDataPointerTarget(SemanticsContext &context,
566 const SomeExpr &pointer, const SomeExpr &init, const Scope &scope) {
567 return evaluate::IsInitialDataTarget(
568 init, &context.foldingContext().messages()) &&
569 CheckPointerAssignment(context, pointer, init, scope,
570 /*isBoundsRemapping=*/false,
571 /*isAssumedRank=*/false);
574 } // namespace Fortran::semantics