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 ¶m) 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 const auto &ultimate{symbol.GetUltimate()}; 262 if (semantics::IsNamedConstant(ultimate) || ultimate.owner().IsModule() || 263 ultimate.owner().IsSubmodule()) { 264 return std::nullopt; 265 } else if (scope_.IsDerivedType() && 266 IsVariableName(ultimate)) { // C750, C754 267 return "derived type component or type parameter value not allowed to " 268 "reference variable '"s + 269 ultimate.name().ToString() + "'"; 270 } else if (IsDummy(ultimate)) { 271 if (ultimate.attrs().test(semantics::Attr::OPTIONAL)) { 272 return "reference to OPTIONAL dummy argument '"s + 273 ultimate.name().ToString() + "'"; 274 } else if (ultimate.attrs().test(semantics::Attr::INTENT_OUT)) { 275 return "reference to INTENT(OUT) dummy argument '"s + 276 ultimate.name().ToString() + "'"; 277 } else if (ultimate.has<semantics::ObjectEntityDetails>()) { 278 return std::nullopt; 279 } else { 280 return "dummy procedure argument"; 281 } 282 } else if (const auto *object{ 283 ultimate.detailsIf<semantics::ObjectEntityDetails>()}) { 284 // TODO: what about EQUIVALENCE with data in COMMON? 285 // TODO: does this work for blank COMMON? 286 if (object->commonBlock()) { 287 return std::nullopt; 288 } 289 } 290 for (const semantics::Scope *s{&scope_}; !s->IsGlobal();) { 291 s = &s->parent(); 292 if (s == &ultimate.owner()) { 293 return std::nullopt; 294 } 295 } 296 return "reference to local entity '"s + ultimate.name().ToString() + "'"; 297 } 298 299 Result operator()(const Component &x) const { 300 // Don't look at the component symbol. 301 return (*this)(x.base()); 302 } 303 Result operator()(const DescriptorInquiry &) const { 304 // Subtle: Uses of SIZE(), LBOUND(), &c. that are valid in specification 305 // expressions will have been converted to expressions over descriptor 306 // inquiries by Fold(). 307 return std::nullopt; 308 } 309 310 Result operator()(const TypeParamInquiry &inq) const { 311 if (scope_.IsDerivedType() && !IsConstantExpr(inq) && 312 inq.base() /* X%T, not local T */) { // C750, C754 313 return "non-constant reference to a type parameter inquiry not " 314 "allowed for derived type components or type parameter values"; 315 } 316 return std::nullopt; 317 } 318 319 template <typename T> Result operator()(const FunctionRef<T> &x) const { 320 if (const auto *symbol{x.proc().GetSymbol()}) { 321 if (!semantics::IsPureProcedure(*symbol)) { 322 return "reference to impure function '"s + symbol->name().ToString() + 323 "'"; 324 } 325 if (semantics::IsStmtFunction(*symbol)) { 326 return "reference to statement function '"s + 327 symbol->name().ToString() + "'"; 328 } 329 if (scope_.IsDerivedType()) { // C750, C754 330 return "reference to function '"s + symbol->name().ToString() + 331 "' not allowed for derived type components or type parameter" 332 " values"; 333 } 334 // TODO: other checks for standard module procedures 335 } else { 336 const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())}; 337 if (scope_.IsDerivedType()) { // C750, C754 338 if ((table_.IsIntrinsic(intrin.name) && 339 badIntrinsicsForComponents_.find(intrin.name) != 340 badIntrinsicsForComponents_.end()) || 341 IsProhibitedFunction(intrin.name)) { 342 return "reference to intrinsic '"s + intrin.name + 343 "' not allowed for derived type components or type parameter" 344 " values"; 345 } 346 if (table_.GetIntrinsicClass(intrin.name) == 347 IntrinsicClass::inquiryFunction && 348 !IsConstantExpr(x)) { 349 return "non-constant reference to inquiry intrinsic '"s + 350 intrin.name + 351 "' not allowed for derived type components or type" 352 " parameter values"; 353 } 354 } else if (intrin.name == "present") { 355 return std::nullopt; // no need to check argument(s) 356 } 357 if (IsConstantExpr(x)) { 358 // inquiry functions may not need to check argument(s) 359 return std::nullopt; 360 } 361 } 362 return (*this)(x.arguments()); 363 } 364 365 private: 366 const semantics::Scope &scope_; 367 const IntrinsicProcTable &table_; 368 const std::set<std::string> badIntrinsicsForComponents_{ 369 "allocated", "associated", "extends_type_of", "present", "same_type_as"}; 370 static bool IsProhibitedFunction(std::string name) { return false; } 371 }; 372 373 template <typename A> 374 void CheckSpecificationExpr(const A &x, parser::ContextualMessages &messages, 375 const semantics::Scope &scope, const IntrinsicProcTable &table) { 376 if (auto why{CheckSpecificationExprHelper{scope, table}(x)}) { 377 messages.Say("Invalid specification expression: %s"_err_en_US, *why); 378 } 379 } 380 381 template void CheckSpecificationExpr(const Expr<SomeType> &, 382 parser::ContextualMessages &, const semantics::Scope &, 383 const IntrinsicProcTable &); 384 template void CheckSpecificationExpr(const Expr<SomeInteger> &, 385 parser::ContextualMessages &, const semantics::Scope &, 386 const IntrinsicProcTable &); 387 template void CheckSpecificationExpr(const Expr<SubscriptInteger> &, 388 parser::ContextualMessages &, const semantics::Scope &, 389 const IntrinsicProcTable &); 390 template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &, 391 parser::ContextualMessages &, const semantics::Scope &, 392 const IntrinsicProcTable &); 393 template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &, 394 parser::ContextualMessages &, const semantics::Scope &, 395 const IntrinsicProcTable &); 396 template void CheckSpecificationExpr( 397 const std::optional<Expr<SubscriptInteger>> &, parser::ContextualMessages &, 398 const semantics::Scope &, const IntrinsicProcTable &); 399 400 // IsSimplyContiguous() -- 9.5.4 401 class IsSimplyContiguousHelper 402 : public AnyTraverse<IsSimplyContiguousHelper, std::optional<bool>> { 403 public: 404 using Result = std::optional<bool>; // tri-state 405 using Base = AnyTraverse<IsSimplyContiguousHelper, Result>; 406 explicit IsSimplyContiguousHelper(const IntrinsicProcTable &t) 407 : Base{*this}, table_{t} {} 408 using Base::operator(); 409 410 Result operator()(const semantics::Symbol &symbol) const { 411 if (symbol.attrs().test(semantics::Attr::CONTIGUOUS) || 412 symbol.Rank() == 0) { 413 return true; 414 } else if (semantics::IsPointer(symbol)) { 415 return false; 416 } else if (const auto *details{ 417 symbol.detailsIf<semantics::ObjectEntityDetails>()}) { 418 // N.B. ALLOCATABLEs are deferred shape, not assumed, and 419 // are obviously contiguous. 420 return !details->IsAssumedShape() && !details->IsAssumedRank(); 421 } else { 422 return false; 423 } 424 } 425 426 Result operator()(const ArrayRef &x) const { 427 const auto &symbol{x.GetLastSymbol()}; 428 if (!(*this)(symbol)) { 429 return false; 430 } else if (auto rank{CheckSubscripts(x.subscript())}) { 431 // a(:)%b(1,1) is not contiguous; a(1)%b(:,:) is 432 return *rank > 0 || x.Rank() == 0; 433 } else { 434 return false; 435 } 436 } 437 Result operator()(const CoarrayRef &x) const { 438 return CheckSubscripts(x.subscript()).has_value(); 439 } 440 Result operator()(const Component &x) const { 441 return x.base().Rank() == 0 && (*this)(x.GetLastSymbol()); 442 } 443 Result operator()(const ComplexPart &) const { return false; } 444 Result operator()(const Substring &) const { return false; } 445 446 template <typename T> Result operator()(const FunctionRef<T> &x) const { 447 if (auto chars{ 448 characteristics::Procedure::Characterize(x.proc(), table_)}) { 449 if (chars->functionResult) { 450 const auto &result{*chars->functionResult}; 451 return !result.IsProcedurePointer() && 452 result.attrs.test(characteristics::FunctionResult::Attr::Pointer) && 453 result.attrs.test( 454 characteristics::FunctionResult::Attr::Contiguous); 455 } 456 } 457 return false; 458 } 459 460 private: 461 // If the subscripts can possibly be on a simply-contiguous array reference, 462 // return the rank. 463 static std::optional<int> CheckSubscripts( 464 const std::vector<Subscript> &subscript) { 465 bool anyTriplet{false}; 466 int rank{0}; 467 for (auto j{subscript.size()}; j-- > 0;) { 468 if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) { 469 if (!triplet->IsStrideOne()) { 470 return std::nullopt; 471 } else if (anyTriplet) { 472 if (triplet->lower() || triplet->upper()) { 473 // all triplets before the last one must be just ":" 474 return std::nullopt; 475 } 476 } else { 477 anyTriplet = true; 478 } 479 ++rank; 480 } else if (anyTriplet || subscript[j].Rank() > 0) { 481 return std::nullopt; 482 } 483 } 484 return rank; 485 } 486 487 const IntrinsicProcTable &table_; 488 }; 489 490 template <typename A> 491 bool IsSimplyContiguous(const A &x, const IntrinsicProcTable &table) { 492 if (IsVariable(x)) { 493 auto known{IsSimplyContiguousHelper{table}(x)}; 494 return known && *known; 495 } else { 496 return true; // not a variable 497 } 498 } 499 500 template bool IsSimplyContiguous( 501 const Expr<SomeType> &, const IntrinsicProcTable &); 502 503 } // namespace Fortran::evaluate 504