xref: /llvm-project/flang/lib/Evaluate/check-expression.cpp (revision 4cbfd93a591a2e861bb69dfe896a7cff41931c05)
1 //===-- lib/Evaluate/check-expression.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 "flang/Evaluate/check-expression.h"
10 #include "flang/Evaluate/intrinsics.h"
11 #include "flang/Evaluate/traverse.h"
12 #include "flang/Evaluate/type.h"
13 #include "flang/Semantics/symbol.h"
14 #include "flang/Semantics/tools.h"
15 #include <set>
16 #include <string>
17 
18 namespace Fortran::evaluate {
19 
20 // Constant expression predicate IsConstantExpr().
21 // This code determines whether an expression is a "constant expression"
22 // in the sense of section 10.1.12.  This is not the same thing as being
23 // able to fold it (yet) into a known constant value; specifically,
24 // the expression may reference derived type kind parameters whose values
25 // are not yet known.
26 class IsConstantExprHelper : public AllTraverse<IsConstantExprHelper, true> {
27 public:
28   using Base = AllTraverse<IsConstantExprHelper, true>;
29   IsConstantExprHelper() : Base{*this} {}
30   using Base::operator();
31 
32   bool operator()(const TypeParamInquiry &inq) const {
33     return IsKindTypeParameter(inq.parameter());
34   }
35   bool operator()(const semantics::Symbol &symbol) const {
36     const auto &ultimate{symbol.GetUltimate()};
37     return IsNamedConstant(ultimate) || IsImpliedDoIndex(ultimate) ||
38         IsInitialProcedureTarget(ultimate);
39   }
40   bool operator()(const CoarrayRef &) const { return false; }
41   bool operator()(const semantics::ParamValue &param) const {
42     return param.isExplicit() && (*this)(param.GetExplicit());
43   }
44   template <typename T> bool operator()(const FunctionRef<T> &call) const {
45     if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&call.proc().u)}) {
46       // kind is always a constant, and we avoid cascading errors by calling
47       // invalid calls to intrinsics constant
48       return intrinsic->name == "kind" ||
49           intrinsic->name == IntrinsicProcTable::InvalidName;
50       // TODO: other inquiry intrinsics
51     } else {
52       return false;
53     }
54   }
55   bool operator()(const StructureConstructor &constructor) const {
56     for (const auto &[symRef, expr] : constructor) {
57       if (!IsConstantStructureConstructorComponent(*symRef, expr.value())) {
58         return false;
59       }
60     }
61     return true;
62   }
63   bool operator()(const Component &component) const {
64     return (*this)(component.base());
65   }
66   // Forbid integer division by zero in constants.
67   template <int KIND>
68   bool operator()(
69       const Divide<Type<TypeCategory::Integer, KIND>> &division) const {
70     using T = Type<TypeCategory::Integer, KIND>;
71     if (const auto divisor{GetScalarConstantValue<T>(division.right())}) {
72       return !divisor->IsZero() && (*this)(division.left());
73     } else {
74       return false;
75     }
76   }
77 
78   bool operator()(const Constant<SomeDerived> &) const { return true; }
79 
80 private:
81   bool IsConstantStructureConstructorComponent(
82       const Symbol &component, const Expr<SomeType> &expr) const {
83     if (IsAllocatable(component)) {
84       return IsNullPointer(expr);
85     } else if (IsPointer(component)) {
86       return IsNullPointer(expr) || IsInitialDataTarget(expr) ||
87           IsInitialProcedureTarget(expr);
88     } else {
89       return (*this)(expr);
90     }
91   }
92 };
93 
94 template <typename A> bool IsConstantExpr(const A &x) {
95   return IsConstantExprHelper{}(x);
96 }
97 template bool IsConstantExpr(const Expr<SomeType> &);
98 template bool IsConstantExpr(const Expr<SomeInteger> &);
99 template bool IsConstantExpr(const Expr<SubscriptInteger> &);
100 template bool IsConstantExpr(const StructureConstructor &);
101 
102 // Object pointer initialization checking predicate IsInitialDataTarget().
103 // This code determines whether an expression is allowable as the static
104 // data address used to initialize a pointer with "=> x".  See C765.
105 class IsInitialDataTargetHelper
106     : public AllTraverse<IsInitialDataTargetHelper, true> {
107 public:
108   using Base = AllTraverse<IsInitialDataTargetHelper, true>;
109   using Base::operator();
110   explicit IsInitialDataTargetHelper(parser::ContextualMessages *m)
111       : Base{*this}, messages_{m} {}
112 
113   bool emittedMessage() const { return emittedMessage_; }
114 
115   bool operator()(const BOZLiteralConstant &) const { return false; }
116   bool operator()(const NullPointer &) const { return true; }
117   template <typename T> bool operator()(const Constant<T> &) const {
118     return false;
119   }
120   bool operator()(const semantics::Symbol &symbol) {
121     const Symbol &ultimate{symbol.GetUltimate()};
122     if (IsAllocatable(ultimate)) {
123       if (messages_) {
124         messages_->Say(
125             "An initial data target may not be a reference to an ALLOCATABLE '%s'"_err_en_US,
126             ultimate.name());
127         emittedMessage_ = true;
128       }
129       return false;
130     } else if (ultimate.Corank() > 0) {
131       if (messages_) {
132         messages_->Say(
133             "An initial data target may not be a reference to a coarray '%s'"_err_en_US,
134             ultimate.name());
135         emittedMessage_ = true;
136       }
137       return false;
138     } else if (!ultimate.attrs().test(semantics::Attr::TARGET)) {
139       if (messages_) {
140         messages_->Say(
141             "An initial data target may not be a reference to an object '%s' that lacks the TARGET attribute"_err_en_US,
142             ultimate.name());
143         emittedMessage_ = true;
144       }
145       return false;
146     } else if (!IsSaved(ultimate)) {
147       if (messages_) {
148         messages_->Say(
149             "An initial data target may not be a reference to an object '%s' that lacks the SAVE attribute"_err_en_US,
150             ultimate.name());
151         emittedMessage_ = true;
152       }
153       return false;
154     }
155     return true;
156   }
157   bool operator()(const StaticDataObject &) const { return false; }
158   bool operator()(const TypeParamInquiry &) const { return false; }
159   bool operator()(const Triplet &x) const {
160     return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
161         IsConstantExpr(x.stride());
162   }
163   bool operator()(const Subscript &x) const {
164     return std::visit(common::visitors{
165                           [&](const Triplet &t) { return (*this)(t); },
166                           [&](const auto &y) {
167                             return y.value().Rank() == 0 &&
168                                 IsConstantExpr(y.value());
169                           },
170                       },
171         x.u);
172   }
173   bool operator()(const CoarrayRef &) const { return false; }
174   bool operator()(const Substring &x) const {
175     return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
176         (*this)(x.parent());
177   }
178   bool operator()(const DescriptorInquiry &) const { return false; }
179   template <typename T> bool operator()(const ArrayConstructor<T> &) const {
180     return false;
181   }
182   bool operator()(const StructureConstructor &) const { return false; }
183   template <typename T> bool operator()(const FunctionRef<T> &) {
184     return false;
185   }
186   template <typename D, typename R, typename... O>
187   bool operator()(const Operation<D, R, O...> &) const {
188     return false;
189   }
190   template <typename T> bool operator()(const Parentheses<T> &x) const {
191     return (*this)(x.left());
192   }
193   bool operator()(const Relational<SomeType> &) const { return false; }
194 
195 private:
196   parser::ContextualMessages *messages_;
197   bool emittedMessage_{false};
198 };
199 
200 bool IsInitialDataTarget(
201     const Expr<SomeType> &x, parser::ContextualMessages *messages) {
202   IsInitialDataTargetHelper helper{messages};
203   bool result{helper(x)};
204   if (!result && messages && !helper.emittedMessage()) {
205     messages->Say(
206         "An initial data target must be a designator with constant subscripts"_err_en_US);
207   }
208   return result;
209 }
210 
211 bool IsInitialProcedureTarget(const semantics::Symbol &symbol) {
212   const auto &ultimate{symbol.GetUltimate()};
213   return std::visit(
214       common::visitors{
215           [](const semantics::SubprogramDetails &) { return true; },
216           [](const semantics::SubprogramNameDetails &) { return true; },
217           [&](const semantics::ProcEntityDetails &proc) {
218             return !semantics::IsPointer(ultimate) && !proc.isDummy();
219           },
220           [](const auto &) { return false; },
221       },
222       ultimate.details());
223 }
224 
225 bool IsInitialProcedureTarget(const ProcedureDesignator &proc) {
226   if (const auto *intrin{proc.GetSpecificIntrinsic()}) {
227     return !intrin->isRestrictedSpecific;
228   } else if (proc.GetComponent()) {
229     return false;
230   } else {
231     return IsInitialProcedureTarget(DEREF(proc.GetSymbol()));
232   }
233 }
234 
235 bool IsInitialProcedureTarget(const Expr<SomeType> &expr) {
236   if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) {
237     return IsInitialProcedureTarget(*proc);
238   } else {
239     return IsNullPointer(expr);
240   }
241 }
242 
243 // Specification expression validation (10.1.11(2), C1010)
244 class CheckSpecificationExprHelper
245     : public AnyTraverse<CheckSpecificationExprHelper,
246           std::optional<std::string>> {
247 public:
248   using Result = std::optional<std::string>;
249   using Base = AnyTraverse<CheckSpecificationExprHelper, Result>;
250   explicit CheckSpecificationExprHelper(
251       const semantics::Scope &s, const IntrinsicProcTable &table)
252       : Base{*this}, scope_{s}, table_{table} {}
253   using Base::operator();
254 
255   Result operator()(const ProcedureDesignator &) const {
256     return "dummy procedure argument";
257   }
258   Result operator()(const CoarrayRef &) const { return "coindexed reference"; }
259 
260   Result operator()(const semantics::Symbol &symbol) const {
261     if (semantics::IsNamedConstant(symbol)) {
262       return std::nullopt;
263     } else if (scope_.IsDerivedType() && IsVariableName(symbol)) { // C750, C754
264       return "derived type component or type parameter value not allowed to "
265              "reference variable '"s +
266           symbol.name().ToString() + "'";
267     } else if (IsDummy(symbol)) {
268       if (symbol.attrs().test(semantics::Attr::OPTIONAL)) {
269         return "reference to OPTIONAL dummy argument '"s +
270             symbol.name().ToString() + "'";
271       } else if (symbol.attrs().test(semantics::Attr::INTENT_OUT)) {
272         return "reference to INTENT(OUT) dummy argument '"s +
273             symbol.name().ToString() + "'";
274       } else if (symbol.has<semantics::ObjectEntityDetails>()) {
275         return std::nullopt;
276       } else {
277         return "dummy procedure argument";
278       }
279     } else if (symbol.has<semantics::UseDetails>() ||
280         symbol.has<semantics::HostAssocDetails>() ||
281         symbol.owner().kind() == semantics::Scope::Kind::Module) {
282       return std::nullopt;
283     } else if (const auto *object{
284                    symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
285       // TODO: what about EQUIVALENCE with data in COMMON?
286       // TODO: does this work for blank COMMON?
287       if (object->commonBlock()) {
288         return std::nullopt;
289       }
290     }
291     for (const semantics::Scope *s{&scope_}; !s->IsGlobal();) {
292       s = &s->parent();
293       if (s == &symbol.owner()) {
294         return std::nullopt;
295       }
296     }
297     return "reference to local entity '"s + symbol.name().ToString() + "'";
298   }
299 
300   Result operator()(const Component &x) const {
301     // Don't look at the component symbol.
302     return (*this)(x.base());
303   }
304   Result operator()(const DescriptorInquiry &) const {
305     // Subtle: Uses of SIZE(), LBOUND(), &c. that are valid in specification
306     // expressions will have been converted to expressions over descriptor
307     // inquiries by Fold().
308     return std::nullopt;
309   }
310 
311   Result operator()(const TypeParamInquiry &inq) const {
312     if (scope_.IsDerivedType() && !IsConstantExpr(inq) &&
313         inq.parameter().owner() != scope_) { // C750, C754
314       return "non-constant reference to a type parameter inquiry not "
315              "allowed for derived type components or type parameter values";
316     }
317     return std::nullopt;
318   }
319 
320   template <typename T> Result operator()(const FunctionRef<T> &x) const {
321     if (const auto *symbol{x.proc().GetSymbol()}) {
322       if (!semantics::IsPureProcedure(*symbol)) {
323         return "reference to impure function '"s + symbol->name().ToString() +
324             "'";
325       }
326       if (semantics::IsStmtFunction(*symbol)) {
327         return "reference to statement function '"s +
328             symbol->name().ToString() + "'";
329       }
330       if (scope_.IsDerivedType()) { // C750, C754
331         return "reference to function '"s + symbol->name().ToString() +
332             "' not allowed for derived type components or type parameter"
333             " values";
334       }
335       // TODO: other checks for standard module procedures
336     } else {
337       const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())};
338       if (scope_.IsDerivedType()) { // C750, C754
339         if ((table_.IsIntrinsic(intrin.name) &&
340                 badIntrinsicsForComponents_.find(intrin.name) !=
341                     badIntrinsicsForComponents_.end()) ||
342             IsProhibitedFunction(intrin.name)) {
343           return "reference to intrinsic '"s + intrin.name +
344               "' not allowed for derived type components or type parameter"
345               " values";
346         }
347         if (table_.GetIntrinsicClass(intrin.name) ==
348                 IntrinsicClass::inquiryFunction &&
349             !IsConstantExpr(x)) {
350           return "non-constant reference to inquiry intrinsic '"s +
351               intrin.name +
352               "' not allowed for derived type components or type"
353               " parameter values";
354         }
355       } else if (intrin.name == "present") {
356         return std::nullopt; // no need to check argument(s)
357       }
358       if (IsConstantExpr(x)) {
359         // inquiry functions may not need to check argument(s)
360         return std::nullopt;
361       }
362     }
363     return (*this)(x.arguments());
364   }
365 
366 private:
367   const semantics::Scope &scope_;
368   const IntrinsicProcTable &table_;
369   const std::set<std::string> badIntrinsicsForComponents_{
370       "allocated", "associated", "extends_type_of", "present", "same_type_as"};
371   static bool IsProhibitedFunction(std::string name) { return false; }
372 };
373 
374 template <typename A>
375 void CheckSpecificationExpr(const A &x, parser::ContextualMessages &messages,
376     const semantics::Scope &scope, const IntrinsicProcTable &table) {
377   if (auto why{CheckSpecificationExprHelper{scope, table}(x)}) {
378     messages.Say("Invalid specification expression: %s"_err_en_US, *why);
379   }
380 }
381 
382 template void CheckSpecificationExpr(const Expr<SomeType> &,
383     parser::ContextualMessages &, const semantics::Scope &,
384     const IntrinsicProcTable &);
385 template void CheckSpecificationExpr(const Expr<SomeInteger> &,
386     parser::ContextualMessages &, const semantics::Scope &,
387     const IntrinsicProcTable &);
388 template void CheckSpecificationExpr(const Expr<SubscriptInteger> &,
389     parser::ContextualMessages &, const semantics::Scope &,
390     const IntrinsicProcTable &);
391 template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &,
392     parser::ContextualMessages &, const semantics::Scope &,
393     const IntrinsicProcTable &);
394 template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &,
395     parser::ContextualMessages &, const semantics::Scope &,
396     const IntrinsicProcTable &);
397 template void CheckSpecificationExpr(
398     const std::optional<Expr<SubscriptInteger>> &, parser::ContextualMessages &,
399     const semantics::Scope &, const IntrinsicProcTable &);
400 
401 // IsSimplyContiguous() -- 9.5.4
402 class IsSimplyContiguousHelper
403     : public AnyTraverse<IsSimplyContiguousHelper, std::optional<bool>> {
404 public:
405   using Result = std::optional<bool>; // tri-state
406   using Base = AnyTraverse<IsSimplyContiguousHelper, Result>;
407   explicit IsSimplyContiguousHelper(const IntrinsicProcTable &t)
408       : Base{*this}, table_{t} {}
409   using Base::operator();
410 
411   Result operator()(const semantics::Symbol &symbol) const {
412     if (symbol.attrs().test(semantics::Attr::CONTIGUOUS) ||
413         symbol.Rank() == 0) {
414       return true;
415     } else if (semantics::IsPointer(symbol)) {
416       return false;
417     } else if (const auto *details{
418                    symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
419       // N.B. ALLOCATABLEs are deferred shape, not assumed, and
420       // are obviously contiguous.
421       return !details->IsAssumedShape() && !details->IsAssumedRank();
422     } else {
423       return false;
424     }
425   }
426 
427   Result operator()(const ArrayRef &x) const {
428     const auto &symbol{x.GetLastSymbol()};
429     if (!(*this)(symbol)) {
430       return false;
431     } else if (auto rank{CheckSubscripts(x.subscript())}) {
432       // a(:)%b(1,1) is not contiguous; a(1)%b(:,:) is
433       return *rank > 0 || x.Rank() == 0;
434     } else {
435       return false;
436     }
437   }
438   Result operator()(const CoarrayRef &x) const {
439     return CheckSubscripts(x.subscript()).has_value();
440   }
441   Result operator()(const Component &x) const {
442     return x.base().Rank() == 0 && (*this)(x.GetLastSymbol());
443   }
444   Result operator()(const ComplexPart &) const { return false; }
445   Result operator()(const Substring &) const { return false; }
446 
447   template <typename T> Result operator()(const FunctionRef<T> &x) const {
448     if (auto chars{
449             characteristics::Procedure::Characterize(x.proc(), table_)}) {
450       if (chars->functionResult) {
451         const auto &result{*chars->functionResult};
452         return !result.IsProcedurePointer() &&
453             result.attrs.test(characteristics::FunctionResult::Attr::Pointer) &&
454             result.attrs.test(
455                 characteristics::FunctionResult::Attr::Contiguous);
456       }
457     }
458     return false;
459   }
460 
461 private:
462   // If the subscripts can possibly be on a simply-contiguous array reference,
463   // return the rank.
464   static std::optional<int> CheckSubscripts(
465       const std::vector<Subscript> &subscript) {
466     bool anyTriplet{false};
467     int rank{0};
468     for (auto j{subscript.size()}; j-- > 0;) {
469       if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) {
470         if (!triplet->IsStrideOne()) {
471           return std::nullopt;
472         } else if (anyTriplet) {
473           if (triplet->lower() || triplet->upper()) {
474             // all triplets before the last one must be just ":"
475             return std::nullopt;
476           }
477         } else {
478           anyTriplet = true;
479         }
480         ++rank;
481       } else if (anyTriplet || subscript[j].Rank() > 0) {
482         return std::nullopt;
483       }
484     }
485     return rank;
486   }
487 
488   const IntrinsicProcTable &table_;
489 };
490 
491 template <typename A>
492 bool IsSimplyContiguous(const A &x, const IntrinsicProcTable &table) {
493   if (IsVariable(x)) {
494     auto known{IsSimplyContiguousHelper{table}(x)};
495     return known && *known;
496   } else {
497     return true; // not a variable
498   }
499 }
500 
501 template bool IsSimplyContiguous(
502     const Expr<SomeType> &, const IntrinsicProcTable &);
503 
504 } // namespace Fortran::evaluate
505