1 //===-- lib/Semantics/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 //===----------------------------------------------------------------------===//
8
9 #include "assignment.h"
10 #include "pointer-assignment.h"
11 #include "flang/Common/idioms.h"
12 #include "flang/Common/restorer.h"
13 #include "flang/Evaluate/characteristics.h"
14 #include "flang/Evaluate/expression.h"
15 #include "flang/Evaluate/fold.h"
16 #include "flang/Evaluate/tools.h"
17 #include "flang/Parser/message.h"
18 #include "flang/Parser/parse-tree-visitor.h"
19 #include "flang/Parser/parse-tree.h"
20 #include "flang/Semantics/expression.h"
21 #include "flang/Semantics/symbol.h"
22 #include "flang/Semantics/tools.h"
23 #include <optional>
24 #include <set>
25 #include <string>
26 #include <type_traits>
27
28 using namespace Fortran::parser::literals;
29
30 namespace Fortran::semantics {
31
32 class AssignmentContext {
33 public:
AssignmentContext(SemanticsContext & context)34 explicit AssignmentContext(SemanticsContext &context) : context_{context} {}
35 AssignmentContext(AssignmentContext &&) = default;
36 AssignmentContext(const AssignmentContext &) = delete;
operator ==(const AssignmentContext & x) const37 bool operator==(const AssignmentContext &x) const { return this == &x; }
38
39 template <typename A> void PushWhereContext(const A &);
40 void PopWhereContext();
41 void Analyze(const parser::AssignmentStmt &);
42 void Analyze(const parser::PointerAssignmentStmt &);
43 void Analyze(const parser::ConcurrentControl &);
44
45 private:
46 bool CheckForPureContext(const SomeExpr &lhs, const SomeExpr &rhs,
47 parser::CharBlock rhsSource, bool isPointerAssignment);
48 void CheckShape(parser::CharBlock, const SomeExpr *);
49 template <typename... A>
Say(parser::CharBlock at,A &&...args)50 parser::Message *Say(parser::CharBlock at, A &&...args) {
51 return &context_.Say(at, std::forward<A>(args)...);
52 }
foldingContext()53 evaluate::FoldingContext &foldingContext() {
54 return context_.foldingContext();
55 }
56
57 SemanticsContext &context_;
58 int whereDepth_{0}; // number of WHEREs currently nested in
59 // shape of masks in LHS of assignments in current WHERE:
60 std::vector<std::optional<std::int64_t>> whereExtents_;
61 };
62
Analyze(const parser::AssignmentStmt & stmt)63 void AssignmentContext::Analyze(const parser::AssignmentStmt &stmt) {
64 if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) {
65 const SomeExpr &lhs{assignment->lhs};
66 const SomeExpr &rhs{assignment->rhs};
67 auto lhsLoc{std::get<parser::Variable>(stmt.t).GetSource()};
68 auto rhsLoc{std::get<parser::Expr>(stmt.t).source};
69 if (CheckForPureContext(lhs, rhs, rhsLoc, false)) {
70 const Scope &scope{context_.FindScope(lhsLoc)};
71 if (auto whyNot{WhyNotModifiable(lhsLoc, lhs, scope, true)}) {
72 if (auto *msg{Say(lhsLoc,
73 "Left-hand side of assignment is not modifiable"_err_en_US)}) {
74 msg->Attach(*whyNot);
75 }
76 }
77 }
78 if (whereDepth_ > 0) {
79 CheckShape(lhsLoc, &lhs);
80 }
81 }
82 }
83
Analyze(const parser::PointerAssignmentStmt & stmt)84 void AssignmentContext::Analyze(const parser::PointerAssignmentStmt &stmt) {
85 CHECK(whereDepth_ == 0);
86 if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) {
87 const SomeExpr &lhs{assignment->lhs};
88 const SomeExpr &rhs{assignment->rhs};
89 CheckForPureContext(lhs, rhs, std::get<parser::Expr>(stmt.t).source, true);
90 auto restorer{
91 foldingContext().messages().SetLocation(context_.location().value())};
92 CheckPointerAssignment(foldingContext(), *assignment);
93 }
94 }
95
96 // C1594 checks
IsPointerDummyOfPureFunction(const Symbol & x)97 static bool IsPointerDummyOfPureFunction(const Symbol &x) {
98 return IsPointerDummy(x) && FindPureProcedureContaining(x.owner()) &&
99 x.owner().symbol() && IsFunction(*x.owner().symbol());
100 }
101
WhyBaseObjectIsSuspicious(const Symbol & x,const Scope & scope)102 static const char *WhyBaseObjectIsSuspicious(
103 const Symbol &x, const Scope &scope) {
104 // See C1594, first paragraph. These conditions enable checks on both
105 // left-hand and right-hand sides in various circumstances.
106 if (IsHostAssociated(x, scope)) {
107 return "host-associated";
108 } else if (IsUseAssociated(x, scope)) {
109 return "USE-associated";
110 } else if (IsPointerDummyOfPureFunction(x)) {
111 return "a POINTER dummy argument of a pure function";
112 } else if (IsIntentIn(x)) {
113 return "an INTENT(IN) dummy argument";
114 } else if (FindCommonBlockContaining(x)) {
115 return "in a COMMON block";
116 } else {
117 return nullptr;
118 }
119 }
120
121 // Checks C1594(1,2); false if check fails
CheckDefinabilityInPureScope(parser::ContextualMessages & messages,const Symbol & lhs,const Scope & context,const Scope & pure)122 bool CheckDefinabilityInPureScope(parser::ContextualMessages &messages,
123 const Symbol &lhs, const Scope &context, const Scope &pure) {
124 if (pure.symbol()) {
125 if (const char *why{WhyBaseObjectIsSuspicious(lhs, context)}) {
126 evaluate::SayWithDeclaration(messages, lhs,
127 "Pure subprogram '%s' may not define '%s' because it is %s"_err_en_US,
128 pure.symbol()->name(), lhs.name(), why);
129 return false;
130 }
131 }
132 return true;
133 }
134
GetPointerComponentDesignatorName(const SomeExpr & expr)135 static std::optional<std::string> GetPointerComponentDesignatorName(
136 const SomeExpr &expr) {
137 if (const auto *derived{
138 evaluate::GetDerivedTypeSpec(evaluate::DynamicType::From(expr))}) {
139 UltimateComponentIterator ultimates{*derived};
140 if (auto pointer{
141 std::find_if(ultimates.begin(), ultimates.end(), IsPointer)}) {
142 return pointer.BuildResultDesignatorName();
143 }
144 }
145 return std::nullopt;
146 }
147
148 // Checks C1594(5,6); false if check fails
CheckCopyabilityInPureScope(parser::ContextualMessages & messages,const SomeExpr & expr,const Scope & scope)149 bool CheckCopyabilityInPureScope(parser::ContextualMessages &messages,
150 const SomeExpr &expr, const Scope &scope) {
151 if (const Symbol * base{GetFirstSymbol(expr)}) {
152 if (const char *why{WhyBaseObjectIsSuspicious(*base, scope)}) {
153 if (auto pointer{GetPointerComponentDesignatorName(expr)}) {
154 evaluate::SayWithDeclaration(messages, *base,
155 "A pure subprogram may not copy the value of '%s' because it is %s"
156 " and has the POINTER component '%s'"_err_en_US,
157 base->name(), why, *pointer);
158 return false;
159 }
160 }
161 }
162 return true;
163 }
164
CheckForPureContext(const SomeExpr & lhs,const SomeExpr & rhs,parser::CharBlock source,bool isPointerAssignment)165 bool AssignmentContext::CheckForPureContext(const SomeExpr &lhs,
166 const SomeExpr &rhs, parser::CharBlock source, bool isPointerAssignment) {
167 const Scope &scope{context_.FindScope(source)};
168 if (const Scope * pure{FindPureProcedureContaining(scope)}) {
169 parser::ContextualMessages messages{
170 context_.location().value(), &context_.messages()};
171 if (evaluate::ExtractCoarrayRef(lhs)) {
172 messages.Say(
173 "A pure subprogram may not define a coindexed object"_err_en_US);
174 } else if (const Symbol * base{GetFirstSymbol(lhs)}) {
175 if (const auto *assoc{base->detailsIf<AssocEntityDetails>()}) {
176 auto dataRef{ExtractDataRef(assoc->expr(), true)};
177 // ASSOCIATE(a=>x) -- check x, not a, for "a=..."
178 base = dataRef ? &dataRef->GetFirstSymbol() : nullptr;
179 }
180 if (base &&
181 !CheckDefinabilityInPureScope(messages, *base, scope, *pure)) {
182 return false;
183 }
184 }
185 if (isPointerAssignment) {
186 if (const Symbol * base{GetFirstSymbol(rhs)}) {
187 if (const char *why{
188 WhyBaseObjectIsSuspicious(*base, scope)}) { // C1594(3)
189 evaluate::SayWithDeclaration(messages, *base,
190 "A pure subprogram may not use '%s' as the target of pointer assignment because it is %s"_err_en_US,
191 base->name(), why);
192 return false;
193 }
194 }
195 } else if (auto type{evaluate::DynamicType::From(lhs)}) {
196 // C1596 checks for polymorphic deallocation in a pure subprogram
197 // due to automatic reallocation on assignment
198 if (type->IsPolymorphic()) {
199 context_.Say(
200 "Deallocation of polymorphic object is not permitted in a pure subprogram"_err_en_US);
201 return false;
202 }
203 if (const DerivedTypeSpec * derived{GetDerivedTypeSpec(type)}) {
204 if (auto bad{FindPolymorphicAllocatableNonCoarrayUltimateComponent(
205 *derived)}) {
206 evaluate::SayWithDeclaration(messages, *bad,
207 "Deallocation of polymorphic non-coarray component '%s' is not permitted in a pure subprogram"_err_en_US,
208 bad.BuildResultDesignatorName());
209 return false;
210 } else {
211 return CheckCopyabilityInPureScope(messages, rhs, scope);
212 }
213 }
214 }
215 }
216 return true;
217 }
218
219 // 10.2.3.1(2) The masks and LHS of assignments must all have the same shape
CheckShape(parser::CharBlock at,const SomeExpr * expr)220 void AssignmentContext::CheckShape(parser::CharBlock at, const SomeExpr *expr) {
221 if (auto shape{evaluate::GetShape(foldingContext(), expr)}) {
222 std::size_t size{shape->size()};
223 if (whereDepth_ == 0) {
224 whereExtents_.resize(size);
225 } else if (whereExtents_.size() != size) {
226 Say(at,
227 "Must have rank %zd to match prior mask or assignment of"
228 " WHERE construct"_err_en_US,
229 whereExtents_.size());
230 return;
231 }
232 for (std::size_t i{0}; i < size; ++i) {
233 if (std::optional<std::int64_t> extent{evaluate::ToInt64((*shape)[i])}) {
234 if (!whereExtents_[i]) {
235 whereExtents_[i] = *extent;
236 } else if (*whereExtents_[i] != *extent) {
237 Say(at,
238 "Dimension %d must have extent %jd to match prior mask or"
239 " assignment of WHERE construct"_err_en_US,
240 i + 1, *whereExtents_[i]);
241 }
242 }
243 }
244 }
245 }
246
PushWhereContext(const A & x)247 template <typename A> void AssignmentContext::PushWhereContext(const A &x) {
248 const auto &expr{std::get<parser::LogicalExpr>(x.t)};
249 CheckShape(expr.thing.value().source, GetExpr(expr));
250 ++whereDepth_;
251 }
252
PopWhereContext()253 void AssignmentContext::PopWhereContext() {
254 --whereDepth_;
255 if (whereDepth_ == 0) {
256 whereExtents_.clear();
257 }
258 }
259
~AssignmentChecker()260 AssignmentChecker::~AssignmentChecker() {}
261
AssignmentChecker(SemanticsContext & context)262 AssignmentChecker::AssignmentChecker(SemanticsContext &context)
263 : context_{new AssignmentContext{context}} {}
Enter(const parser::AssignmentStmt & x)264 void AssignmentChecker::Enter(const parser::AssignmentStmt &x) {
265 context_.value().Analyze(x);
266 }
Enter(const parser::PointerAssignmentStmt & x)267 void AssignmentChecker::Enter(const parser::PointerAssignmentStmt &x) {
268 context_.value().Analyze(x);
269 }
Enter(const parser::WhereStmt & x)270 void AssignmentChecker::Enter(const parser::WhereStmt &x) {
271 context_.value().PushWhereContext(x);
272 }
Leave(const parser::WhereStmt &)273 void AssignmentChecker::Leave(const parser::WhereStmt &) {
274 context_.value().PopWhereContext();
275 }
Enter(const parser::WhereConstructStmt & x)276 void AssignmentChecker::Enter(const parser::WhereConstructStmt &x) {
277 context_.value().PushWhereContext(x);
278 }
Leave(const parser::EndWhereStmt &)279 void AssignmentChecker::Leave(const parser::EndWhereStmt &) {
280 context_.value().PopWhereContext();
281 }
Enter(const parser::MaskedElsewhereStmt & x)282 void AssignmentChecker::Enter(const parser::MaskedElsewhereStmt &x) {
283 context_.value().PushWhereContext(x);
284 }
Leave(const parser::MaskedElsewhereStmt &)285 void AssignmentChecker::Leave(const parser::MaskedElsewhereStmt &) {
286 context_.value().PopWhereContext();
287 }
288
289 } // namespace Fortran::semantics
290 template class Fortran::common::Indirection<
291 Fortran::semantics::AssignmentContext>;
292