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 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