//===-- lib/Semantics/check-data.cpp --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // DATA statement semantic analysis. // - Applies static semantic checks to the variables in each data-stmt-set with // class DataVarChecker; // - Applies specific checks to each scalar element initialization with a // constant value or pointer tareg with class DataInitializationCompiler; // - Collects the elemental initializations for each symbol and converts them // into a single init() expression with member function // DataChecker::ConstructInitializer(). #include "check-data.h" #include "pointer-assignment.h" #include "flang/Evaluate/fold-designator.h" #include "flang/Evaluate/traverse.h" #include "flang/Parser/parse-tree.h" #include "flang/Parser/tools.h" #include "flang/Semantics/tools.h" namespace Fortran::semantics { // Ensures that references to an implied DO loop control variable are // represented as such in the "body" of the implied DO loop. void DataChecker::Enter(const parser::DataImpliedDo &x) { auto name{std::get(x.t).name.thing.thing}; int kind{evaluate::ResultType::kind}; if (const auto dynamicType{evaluate::DynamicType::From(*name.symbol)}) { if (dynamicType->category() == TypeCategory::Integer) { kind = dynamicType->kind(); } } exprAnalyzer_.AddImpliedDo(name.source, kind); } void DataChecker::Leave(const parser::DataImpliedDo &x) { auto name{std::get(x.t).name.thing.thing}; exprAnalyzer_.RemoveImpliedDo(name.source); } // DataVarChecker applies static checks once to each variable that appears // in a data-stmt-set. These checks are independent of the values that // correspond to the variables. class DataVarChecker : public evaluate::AllTraverse { public: using Base = evaluate::AllTraverse; DataVarChecker(SemanticsContext &c, parser::CharBlock src) : Base{*this}, context_{c}, source_{src} {} using Base::operator(); bool HasComponentWithoutSubscripts() const { return hasComponent_ && !hasSubscript_; } bool operator()(const Symbol &symbol) { // C876 // 8.6.7p(2) - precludes non-pointers of derived types with // default component values const Scope &scope{context_.FindScope(source_)}; bool isFirstSymbol{isFirstSymbol_}; isFirstSymbol_ = false; if (const char *whyNot{IsAutomatic(symbol) ? "Automatic variable" : IsDummy(symbol) ? "Dummy argument" : IsFunctionResult(symbol) ? "Function result" : IsAllocatable(symbol) ? "Allocatable" : IsInitialized(symbol, true) ? "Default-initialized" : IsInBlankCommon(symbol) ? "Blank COMMON object" : IsProcedure(symbol) && !IsPointer(symbol) ? "Procedure" // remaining checks don't apply to components : !isFirstSymbol ? nullptr : IsHostAssociated(symbol, scope) ? "Host-associated object" : IsUseAssociated(symbol, scope) ? "USE-associated object" : nullptr}) { context_.Say(source_, "%s '%s' must not be initialized in a DATA statement"_err_en_US, whyNot, symbol.name()); return false; } else if (IsProcedurePointer(symbol)) { context_.Say(source_, "Procedure pointer '%s' in a DATA statement is not standard"_en_US, symbol.name()); } return true; } bool operator()(const evaluate::Component &component) { hasComponent_ = true; const Symbol &lastSymbol{component.GetLastSymbol()}; if (isPointerAllowed_) { if (IsPointer(lastSymbol) && hasSubscript_) { // C877 context_.Say(source_, "Rightmost data object pointer '%s' must not be subscripted"_err_en_US, lastSymbol.name().ToString()); return false; } RestrictPointer(); } else { if (IsPointer(lastSymbol)) { // C877 context_.Say(source_, "Data object must not contain pointer '%s' as a non-rightmost part"_err_en_US, lastSymbol.name().ToString()); return false; } } return (*this)(component.base()) && (*this)(lastSymbol); } bool operator()(const evaluate::ArrayRef &arrayRef) { hasSubscript_ = true; return (*this)(arrayRef.base()) && (*this)(arrayRef.subscript()); } bool operator()(const evaluate::Substring &substring) { hasSubscript_ = true; return (*this)(substring.parent()) && (*this)(substring.lower()) && (*this)(substring.upper()); } bool operator()(const evaluate::CoarrayRef &) { // C874 context_.Say( source_, "Data object must not be a coindexed variable"_err_en_US); return false; } bool operator()(const evaluate::Subscript &subs) { DataVarChecker subscriptChecker{context_, source_}; subscriptChecker.RestrictPointer(); return std::visit( common::visitors{ [&](const evaluate::IndirectSubscriptIntegerExpr &expr) { return CheckSubscriptExpr(expr); }, [&](const evaluate::Triplet &triplet) { return CheckSubscriptExpr(triplet.lower()) && CheckSubscriptExpr(triplet.upper()) && CheckSubscriptExpr(triplet.stride()); }, }, subs.u) && subscriptChecker(subs.u); } template bool operator()(const evaluate::FunctionRef &) const { // C875 context_.Say(source_, "Data object variable must not be a function reference"_err_en_US); return false; } void RestrictPointer() { isPointerAllowed_ = false; } private: bool CheckSubscriptExpr( const std::optional &x) const { return !x || CheckSubscriptExpr(*x); } bool CheckSubscriptExpr( const evaluate::IndirectSubscriptIntegerExpr &expr) const { return CheckSubscriptExpr(expr.value()); } bool CheckSubscriptExpr( const evaluate::Expr &expr) const { if (!evaluate::IsConstantExpr(expr)) { // C875,C881 context_.Say( source_, "Data object must have constant subscripts"_err_en_US); return false; } else { return true; } } SemanticsContext &context_; parser::CharBlock source_; bool hasComponent_{false}; bool hasSubscript_{false}; bool isPointerAllowed_{true}; bool isFirstSymbol_{true}; }; void DataChecker::Leave(const parser::DataIDoObject &object) { if (const auto *designator{ std::get_if>>( &object.u)}) { if (MaybeExpr expr{exprAnalyzer_.Analyze(*designator)}) { auto source{designator->thing.value().source}; if (evaluate::IsConstantExpr(*expr)) { // C878,C879 exprAnalyzer_.context().Say( source, "Data implied do object must be a variable"_err_en_US); } else { DataVarChecker checker{exprAnalyzer_.context(), source}; if (checker(*expr)) { if (checker.HasComponentWithoutSubscripts()) { // C880 exprAnalyzer_.context().Say(source, "Data implied do structure component must be subscripted"_err_en_US); } else { return; } } } } } currentSetHasFatalErrors_ = true; } void DataChecker::Leave(const parser::DataStmtObject &dataObject) { std::visit(common::visitors{ [](const parser::DataImpliedDo &) { // has own Enter()/Leave() }, [&](const auto &var) { auto expr{exprAnalyzer_.Analyze(var)}; if (!expr || !DataVarChecker{exprAnalyzer_.context(), parser::FindSourceLocation(dataObject)}(*expr)) { currentSetHasFatalErrors_ = true; } }, }, dataObject.u); } // Steps through a list of values in a DATA statement set; implements // repetition. class ValueListIterator { public: explicit ValueListIterator(const parser::DataStmtSet &set) : end_{std::get>(set.t).end()}, at_{std::get>(set.t).begin()} { SetRepetitionCount(); } bool hasFatalError() const { return hasFatalError_; } bool IsAtEnd() const { return at_ == end_; } const SomeExpr *operator*() const { return GetExpr(GetConstant()); } parser::CharBlock LocateSource() const { return GetConstant().source; } ValueListIterator &operator++() { if (repetitionsRemaining_ > 0) { --repetitionsRemaining_; } else if (at_ != end_) { ++at_; SetRepetitionCount(); } return *this; } private: using listIterator = std::list::const_iterator; void SetRepetitionCount(); const parser::DataStmtConstant &GetConstant() const { return std::get(at_->t); } listIterator end_; listIterator at_; ConstantSubscript repetitionsRemaining_{0}; bool hasFatalError_{false}; }; void ValueListIterator::SetRepetitionCount() { for (repetitionsRemaining_ = 1; at_ != end_; ++at_) { if (at_->repetitions < 0) { hasFatalError_ = true; } if (at_->repetitions > 0) { repetitionsRemaining_ = at_->repetitions - 1; return; } } repetitionsRemaining_ = 0; } // Collects all of the elemental initializations from DATA statements // into a single image for each symbol that appears in any DATA. // Expands the implied DO loops and array references. // Applies checks that validate each distinct elemental initialization // of the variables in a data-stmt-set, as well as those that apply // to the corresponding values being use to initialize each element. class DataInitializationCompiler { public: DataInitializationCompiler(DataInitializations &inits, evaluate::ExpressionAnalyzer &a, const parser::DataStmtSet &set) : inits_{inits}, exprAnalyzer_{a}, values_{set} {} const DataInitializations &inits() const { return inits_; } bool HasSurplusValues() const { return !values_.IsAtEnd(); } bool Scan(const parser::DataStmtObject &); private: bool Scan(const parser::Variable &); bool Scan(const parser::Designator &); bool Scan(const parser::DataImpliedDo &); bool Scan(const parser::DataIDoObject &); // Initializes all elements of a designator, which can be an array or section. bool InitDesignator(const SomeExpr &); // Initializes a single object. bool InitElement(const evaluate::OffsetSymbol &, const SomeExpr &designator); DataInitializations &inits_; evaluate::ExpressionAnalyzer &exprAnalyzer_; ValueListIterator values_; }; bool DataInitializationCompiler::Scan(const parser::DataStmtObject &object) { return std::visit( common::visitors{ [&](const common::Indirection &var) { return Scan(var.value()); }, [&](const parser::DataImpliedDo &ido) { return Scan(ido); }, }, object.u); } bool DataInitializationCompiler::Scan(const parser::Variable &var) { if (const auto *expr{GetExpr(var)}) { exprAnalyzer_.GetFoldingContext().messages().SetLocation(var.GetSource()); if (InitDesignator(*expr)) { return true; } } return false; } bool DataInitializationCompiler::Scan(const parser::Designator &designator) { if (auto expr{exprAnalyzer_.Analyze(designator)}) { exprAnalyzer_.GetFoldingContext().messages().SetLocation( parser::FindSourceLocation(designator)); if (InitDesignator(*expr)) { return true; } } return false; } bool DataInitializationCompiler::Scan(const parser::DataImpliedDo &ido) { const auto &bounds{std::get(ido.t)}; auto name{bounds.name.thing.thing}; const auto *lowerExpr{GetExpr(bounds.lower.thing.thing)}; const auto *upperExpr{GetExpr(bounds.upper.thing.thing)}; const auto *stepExpr{ bounds.step ? GetExpr(bounds.step->thing.thing) : nullptr}; if (lowerExpr && upperExpr) { auto lower{ToInt64(*lowerExpr)}; auto upper{ToInt64(*upperExpr)}; auto step{stepExpr ? ToInt64(*stepExpr) : std::nullopt}; auto stepVal{step.value_or(1)}; if (stepVal == 0) { exprAnalyzer_.Say(name.source, "DATA statement implied DO loop has a step value of zero"_err_en_US); } else if (lower && upper) { int kind{evaluate::ResultType::kind}; if (const auto dynamicType{evaluate::DynamicType::From(*name.symbol)}) { if (dynamicType->category() == TypeCategory::Integer) { kind = dynamicType->kind(); } } if (exprAnalyzer_.AddImpliedDo(name.source, kind)) { auto &value{exprAnalyzer_.GetFoldingContext().StartImpliedDo( name.source, *lower)}; bool result{true}; for (auto n{(*upper - value + stepVal) / stepVal}; n > 0; --n, value += stepVal) { for (const auto &object : std::get>(ido.t)) { if (!Scan(object)) { result = false; break; } } } exprAnalyzer_.GetFoldingContext().EndImpliedDo(name.source); exprAnalyzer_.RemoveImpliedDo(name.source); return result; } } } return false; } bool DataInitializationCompiler::Scan(const parser::DataIDoObject &object) { return std::visit( common::visitors{ [&](const parser::Scalar> &var) { return Scan(var.thing.value()); }, [&](const common::Indirection &ido) { return Scan(ido.value()); }, }, object.u); } bool DataInitializationCompiler::InitDesignator(const SomeExpr &designator) { evaluate::FoldingContext &context{exprAnalyzer_.GetFoldingContext()}; evaluate::DesignatorFolder folder{context}; while (auto offsetSymbol{folder.FoldDesignator(designator)}) { if (folder.isOutOfRange()) { if (auto bad{evaluate::OffsetToDesignator(context, *offsetSymbol)}) { exprAnalyzer_.context().Say( "DATA statement designator '%s' is out of range"_err_en_US, bad->AsFortran()); } else { exprAnalyzer_.context().Say( "DATA statement designator '%s' is out of range"_err_en_US, designator.AsFortran()); } return false; } else if (!InitElement(*offsetSymbol, designator)) { return false; } else { ++values_; } } return folder.isEmpty(); } bool DataInitializationCompiler::InitElement( const evaluate::OffsetSymbol &offsetSymbol, const SomeExpr &designator) { const Symbol &symbol{offsetSymbol.symbol()}; const Symbol *lastSymbol{GetLastSymbol(designator)}; bool isPointer{lastSymbol && IsPointer(*lastSymbol)}; bool isProcPointer{lastSymbol && IsProcedurePointer(*lastSymbol)}; evaluate::FoldingContext &context{exprAnalyzer_.GetFoldingContext()}; const auto DescribeElement{[&]() { if (auto badDesignator{ evaluate::OffsetToDesignator(context, offsetSymbol)}) { return badDesignator->AsFortran(); } else { // Error recovery std::string buf; llvm::raw_string_ostream ss{buf}; ss << offsetSymbol.symbol().name() << " offset " << offsetSymbol.offset() << " bytes for " << offsetSymbol.size() << " bytes"; return ss.str(); } }}; const auto GetImage{[&]() -> evaluate::InitialImage & { auto &symbolInit{inits_.emplace(symbol, symbol.size()).first->second}; symbolInit.inits.emplace_back(offsetSymbol.offset(), offsetSymbol.size()); return symbolInit.image; }}; const auto OutOfRangeError{[&]() { evaluate::AttachDeclaration( exprAnalyzer_.context().Say( "DATA statement designator '%s' is out of range for its variable '%s'"_err_en_US, DescribeElement(), symbol.name()), symbol); }}; if (values_.hasFatalError()) { return false; } else if (values_.IsAtEnd()) { exprAnalyzer_.context().Say( "DATA statement set has no value for '%s'"_err_en_US, DescribeElement()); return false; } else if (static_cast( offsetSymbol.offset() + offsetSymbol.size()) > symbol.size()) { OutOfRangeError(); return false; } const SomeExpr *expr{*values_}; if (!expr) { CHECK(exprAnalyzer_.context().AnyFatalError()); } else if (isPointer) { if (static_cast(offsetSymbol.offset() + offsetSymbol.size()) > symbol.size()) { OutOfRangeError(); } else if (evaluate::IsNullPointer(*expr)) { // nothing to do; rely on zero initialization return true; } else if (evaluate::IsProcedure(*expr)) { if (isProcPointer) { if (CheckPointerAssignment(context, designator, *expr)) { GetImage().AddPointer(offsetSymbol.offset(), *expr); return true; } } else { exprAnalyzer_.Say(values_.LocateSource(), "Procedure '%s' may not be used to initialize '%s', which is not a procedure pointer"_err_en_US, expr->AsFortran(), DescribeElement()); } } else if (isProcPointer) { exprAnalyzer_.Say(values_.LocateSource(), "Data object '%s' may not be used to initialize '%s', which is a procedure pointer"_err_en_US, expr->AsFortran(), DescribeElement()); } else if (CheckInitialTarget(context, designator, *expr)) { GetImage().AddPointer(offsetSymbol.offset(), *expr); return true; } } else if (evaluate::IsNullPointer(*expr)) { exprAnalyzer_.Say(values_.LocateSource(), "Initializer for '%s' must not be a pointer"_err_en_US, DescribeElement()); } else if (evaluate::IsProcedure(*expr)) { exprAnalyzer_.Say(values_.LocateSource(), "Initializer for '%s' must not be a procedure"_err_en_US, DescribeElement()); } else if (auto designatorType{designator.GetType()}) { if (auto converted{ evaluate::ConvertToType(*designatorType, SomeExpr{*expr})}) { // value non-pointer initialization if (std::holds_alternative(expr->u) && designatorType->category() != TypeCategory::Integer) { // 8.6.7(11) exprAnalyzer_.Say(values_.LocateSource(), "BOZ literal should appear in a DATA statement only as a value for an integer object, but '%s' is '%s'"_en_US, DescribeElement(), designatorType->AsFortran()); } auto folded{evaluate::Fold(context, std::move(*converted))}; switch ( GetImage().Add(offsetSymbol.offset(), offsetSymbol.size(), folded)) { case evaluate::InitialImage::Ok: return true; case evaluate::InitialImage::NotAConstant: exprAnalyzer_.Say(values_.LocateSource(), "DATA statement value '%s' for '%s' is not a constant"_err_en_US, folded.AsFortran(), DescribeElement()); break; case evaluate::InitialImage::OutOfRange: OutOfRangeError(); break; default: CHECK(exprAnalyzer_.context().AnyFatalError()); break; } } else { exprAnalyzer_.context().Say( "DATA statement value could not be converted to the type '%s' of the object '%s'"_err_en_US, designatorType->AsFortran(), DescribeElement()); } } else { CHECK(exprAnalyzer_.context().AnyFatalError()); } return false; } void DataChecker::Leave(const parser::DataStmtSet &set) { if (!currentSetHasFatalErrors_) { DataInitializationCompiler scanner{inits_, exprAnalyzer_, set}; for (const auto &object : std::get>(set.t)) { if (!scanner.Scan(object)) { return; } } if (scanner.HasSurplusValues()) { exprAnalyzer_.context().Say( "DATA statement set has more values than objects"_err_en_US); } } currentSetHasFatalErrors_ = false; } // Converts the initialization image for all the DATA statement appearances of // a single symbol into an init() expression in the symbol table entry. void DataChecker::ConstructInitializer( const Symbol &symbol, SymbolDataInitialization &initialization) { auto &context{exprAnalyzer_.GetFoldingContext()}; initialization.inits.sort(); ConstantSubscript next{0}; for (const auto &init : initialization.inits) { if (init.start() < next) { auto badDesignator{evaluate::OffsetToDesignator( context, symbol, init.start(), init.size())}; CHECK(badDesignator); exprAnalyzer_.Say(symbol.name(), "DATA statement initializations affect '%s' more than once"_err_en_US, badDesignator->AsFortran()); } next = init.start() + init.size(); CHECK(next <= static_cast(initialization.image.size())); } if (const auto *proc{symbol.detailsIf()}) { CHECK(IsProcedurePointer(symbol)); const auto &procDesignator{initialization.image.AsConstantProcPointer()}; CHECK(!procDesignator.GetComponent()); auto &mutableProc{const_cast(*proc)}; mutableProc.set_init(DEREF(procDesignator.GetSymbol())); } else if (const auto *object{symbol.detailsIf()}) { if (auto symbolType{evaluate::DynamicType::From(symbol)}) { auto &mutableObject{const_cast(*object)}; if (IsPointer(symbol)) { mutableObject.set_init( initialization.image.AsConstantDataPointer(*symbolType)); mutableObject.set_initWasValidated(); } else { if (auto extents{evaluate::GetConstantExtents(context, symbol)}) { mutableObject.set_init( initialization.image.AsConstant(context, *symbolType, *extents)); mutableObject.set_initWasValidated(); } else { exprAnalyzer_.Say(symbol.name(), "internal: unknown shape for '%s' while constructing initializer from DATA"_err_en_US, symbol.name()); return; } } } else { exprAnalyzer_.Say(symbol.name(), "internal: no type for '%s' while constructing initializer from DATA"_err_en_US, symbol.name()); return; } if (!object->init()) { exprAnalyzer_.Say(symbol.name(), "internal: could not construct an initializer from DATA statements for '%s'"_err_en_US, symbol.name()); } } else { CHECK(exprAnalyzer_.context().AnyFatalError()); } } void DataChecker::CompileDataInitializationsIntoInitializers() { for (auto &[symbolRef, initialization] : inits_) { ConstructInitializer(*symbolRef, initialization); } } } // namespace Fortran::semantics