//===--- ParseIfConfig.cpp - Swift Language Parser for #if directives -----===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Conditional Compilation Block Parsing and AST Building // //===----------------------------------------------------------------------===// #include "swift/Parse/Parser.h" #include "swift/AST/ASTVisitor.h" #include "swift/Basic/Defer.h" #include "swift/Basic/LangOptions.h" #include "swift/Basic/Version.h" #include "swift/Parse/Lexer.h" #include "swift/Parse/SyntaxParsingContext.h" #include "swift/Syntax/SyntaxFactory.h" #include "swift/Syntax/TokenSyntax.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/SaveAndRestore.h" using namespace swift; using namespace swift::syntax; namespace { /// Get PlatformConditionKind from platform condition name. static Optional getPlatformConditionKind(StringRef Name) { return llvm::StringSwitch>(Name) .Case("os", PlatformConditionKind::OS) .Case("arch", PlatformConditionKind::Arch) .Case("_endian", PlatformConditionKind::Endianness) .Case("_runtime", PlatformConditionKind::Runtime) .Case("canImport", PlatformConditionKind::CanImport) .Case("targetEnvironment", PlatformConditionKind::TargetEnvironment) .Default(None); } /// Extract source text of the expression. static StringRef extractExprSource(SourceManager &SM, Expr *E) { CharSourceRange Range = Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange()); return SM.extractText(Range); } /// The condition validator. class ValidateIfConfigCondition : public ExprVisitor { ASTContext &Ctx; DiagnosticEngine &D; bool HasError; /// Get the identifier string of the UnresolvedDeclRefExpr. llvm::Optional getDeclRefStr(Expr *E, DeclRefKind Kind) { auto UDRE = dyn_cast(E); if (!UDRE || !UDRE->hasName() || UDRE->getRefKind() != Kind) return None; if (UDRE->getName().isCompoundName()) { if (!Ctx.isSwiftVersion3()) return None; // Swift3 used to accept compound names; warn and return the basename. D.diagnose(UDRE->getNameLoc().getLParenLoc(), diag::swift3_conditional_compilation_expression_compound) .fixItRemove({ UDRE->getNameLoc().getLParenLoc(), UDRE->getNameLoc().getRParenLoc() }); } return UDRE->getName().getBaseIdentifier().str(); } Expr *diagnoseUnsupportedExpr(Expr *E) { D.diagnose(E->getLoc(), diag::unsupported_conditional_compilation_expression_type); return nullptr; } // Support '||' and '&&' operator. The procedence of '&&' is higher than '||'. // Invalid operator and the next operand are diagnosed and removed from AST. Expr *foldSequence(Expr *LHS, ArrayRef &S, bool isRecurse = false) { assert(!S.empty() && ((S.size() & 1) == 0)); auto getNextOperator = [&]() -> llvm::Optional { assert((S.size() & 1) == 0); while (!S.empty()) { auto Name = getDeclRefStr(S[0], DeclRefKind::BinaryOperator); if (Name.hasValue() && (*Name == "||" || *Name == "&&")) return Name; auto DiagID = isa(S[0]) ? diag::unsupported_conditional_compilation_binary_expression : diag::unsupported_conditional_compilation_expression_type; D.diagnose(S[0]->getLoc(), DiagID); HasError |= true; // Consume invalid operator and the immediate RHS. S = S.slice(2); } return None; }; // Extract out the first operator name. auto OpName = getNextOperator(); if (!OpName.hasValue()) // If failed, it's not a sequence anymore. return LHS; Expr *Op = S[0]; // We will definitely be consuming at least one operator. // Pull out the prospective RHS and slice off the first two elements. Expr *RHS = validate(S[1]); S = S.slice(2); while (true) { // Pull out the next binary operator. auto NextOpName = getNextOperator(); bool IsEnd = !NextOpName.hasValue(); if (!IsEnd && *OpName == "||" && *NextOpName == "&&") { RHS = foldSequence(RHS, S, /*isRecurse*/true); continue; } // Apply the operator with left-associativity by folding the first two // operands. TupleExpr *Arg = TupleExpr::create(Ctx, SourceLoc(), { LHS, RHS }, { }, { }, SourceLoc(), /*HasTrailingClosure=*/false, /*Implicit=*/true); LHS = new (Ctx) BinaryExpr(Op, Arg, /*implicit*/false); // If we don't have the next operator, we're done. if (IsEnd) break; if (isRecurse && *OpName == "&&" && *NextOpName == "||") break; OpName = NextOpName; Op = S[0]; RHS = validate(S[1]); S = S.slice(2); } return LHS; } // In Swift3 mode, leave sequence as a sequence because it has strange // evaluation rule. See 'EvaluateIfConfigCondition::visitSequenceExpr'. Expr *validateSequence(ArrayRef &S) { assert(Ctx.isSwiftVersion3()); SmallVector Filtered; SmallVector AndIdxs; Filtered.push_back(validate(S[0])); S = S.slice(1); while (!S.empty()) { auto OpName = getDeclRefStr(S[0], DeclRefKind::BinaryOperator); if (!OpName.hasValue() || (*OpName != "||" && *OpName != "&&")) { // Warning and ignore in Swift3 mode. D.diagnose( S[0]->getLoc(), diag::swift3_unsupported_conditional_compilation_expression_type) .highlight({ S[0]->getLoc(), S[1]->getEndLoc() }); } else { // Remember the start and end of '&&' sequence. bool InAnd = (AndIdxs.size() & 1) == 1; if ((*OpName == "&&" && !InAnd) || (*OpName == "||" && InAnd)) AndIdxs.push_back(Filtered.size() - 1); Filtered.push_back(S[0]); Filtered.push_back(validate(S[1])); } S = S.slice(2); } assert((Filtered.size() & 1) == 1); // If the last OpName is '&&', close it with a parenthesis, except if the // operators are '&&' only. if ((1 == (AndIdxs.size() & 1)) && AndIdxs.back() > 0) AndIdxs.push_back(Filtered.size() - 1); // Emit fix-its to make this sequence compatilble with Swift >=4 even in // Swift3 mode. if (AndIdxs.size() >= 2) { assert((AndIdxs.size() & 1) == 0); auto diag = D.diagnose( Filtered[AndIdxs[0]]->getStartLoc(), diag::swift3_conditional_compilation_expression_precedence); for (unsigned i = 0, e = AndIdxs.size(); i < e; i += 2) { diag.fixItInsert(Filtered[AndIdxs[i]]->getStartLoc(), "("); diag.fixItInsertAfter(Filtered[AndIdxs[i + 1]]->getEndLoc(), ")"); } } if (Filtered.size() == 1) return Filtered[0]; return SequenceExpr::create(Ctx, Filtered); } public: ValidateIfConfigCondition(ASTContext &Ctx, DiagnosticEngine &D) : Ctx(Ctx), D(D), HasError(false) {} // Explicit configuration flag. Expr *visitUnresolvedDeclRefExpr(UnresolvedDeclRefExpr *E) { if (!getDeclRefStr(E, DeclRefKind::Ordinary).hasValue()) return diagnoseUnsupportedExpr(E); return E; } // 'true' or 'false' constant. Expr *visitBooleanLiteralExpr(BooleanLiteralExpr *E) { return E; } // '0' and '1' are warned, but we accept it. Expr *visitIntegerLiteralExpr(IntegerLiteralExpr *E) { if (E->isNegative() || (E->getDigitsText() != "0" && E->getDigitsText() != "1")) { return diagnoseUnsupportedExpr(E); } // "#if 0" isn't valid, but it is common, so recognize it and handle it // with a fixit. StringRef replacement = E->getDigitsText() == "0" ? "false" :"true"; D.diagnose(E->getLoc(), diag::unsupported_conditional_compilation_integer, E->getDigitsText(), replacement) .fixItReplace(E->getLoc(), replacement); return E; } // Platform conditions. Expr *visitCallExpr(CallExpr *E) { auto KindName = getDeclRefStr(E->getFn(), DeclRefKind::Ordinary); if (!KindName.hasValue()) { D.diagnose(E->getLoc(), diag::unsupported_platform_condition_expression); return nullptr; } auto *ArgP = dyn_cast(E->getArg()); if (!ArgP) { D.diagnose(E->getLoc(), diag::platform_condition_expected_one_argument); return nullptr; } Expr *Arg = ArgP->getSubExpr(); // '_compiler_version' '(' string-literal ')' if (*KindName == "_compiler_version") { auto SLE = dyn_cast(Arg); if (!SLE) { D.diagnose(Arg->getLoc(), diag::unsupported_platform_condition_argument, "string literal"); return nullptr; } auto ValStr = SLE->getValue(); if (ValStr.empty()) { D.diagnose(SLE->getLoc(), diag::empty_version_string); return nullptr; } auto Val = version::Version::parseCompilerVersionString( SLE->getValue(), SLE->getLoc(), &D); if (!Val.hasValue()) return nullptr; return E; } // 'swift' '(' '>=' float-literal ( '.' integer-literal )* ')' // 'compiler' '(' '>=' float-literal ( '.' integer-literal )* ')' if (*KindName == "swift" || *KindName == "compiler") { auto PUE = dyn_cast(Arg); llvm::Optional PrefixName = PUE ? getDeclRefStr(PUE->getFn(), DeclRefKind::PrefixOperator) : None; if (!PrefixName || *PrefixName != ">=") { D.diagnose(Arg->getLoc(), diag::unsupported_platform_condition_argument, "a unary comparison, such as '>=2.2'"); return nullptr; } auto versionString = extractExprSource(Ctx.SourceMgr, PUE->getArg()); auto Val = version::Version::parseVersionString( versionString, PUE->getArg()->getStartLoc(), &D); if (!Val.hasValue()) return nullptr; return E; } // ( 'os' | 'arch' | '_endian' | '_runtime' | 'canImport') '(' identifier ')'' auto Kind = getPlatformConditionKind(*KindName); if (!Kind.hasValue()) { D.diagnose(E->getLoc(), diag::unsupported_platform_condition_expression); return nullptr; } auto ArgStr = getDeclRefStr(Arg, DeclRefKind::Ordinary); if (!ArgStr.hasValue()) { D.diagnose(E->getLoc(), diag::unsupported_platform_condition_argument, "identifier"); return nullptr; } std::vector suggestions; if (!LangOptions::checkPlatformConditionSupported(*Kind, *ArgStr, suggestions)) { if (Kind == PlatformConditionKind::Runtime) { // Error for _runtime() D.diagnose(Arg->getLoc(), diag::unsupported_platform_runtime_condition_argument); return nullptr; } // Just a warning for other unsupported arguments. StringRef DiagName; switch (*Kind) { case PlatformConditionKind::OS: DiagName = "operating system"; break; case PlatformConditionKind::Arch: DiagName = "architecture"; break; case PlatformConditionKind::Endianness: DiagName = "endianness"; break; case PlatformConditionKind::CanImport: DiagName = "import conditional"; break; case PlatformConditionKind::TargetEnvironment: DiagName = "target environment"; break; case PlatformConditionKind::Runtime: llvm_unreachable("handled above"); } auto Loc = Arg->getLoc(); D.diagnose(Loc, diag::unknown_platform_condition_argument, DiagName, *KindName); for (auto suggestion : suggestions) D.diagnose(Loc, diag::note_typo_candidate, suggestion) .fixItReplace(Arg->getSourceRange(), suggestion); } return E; } // Grouped condition. e.g. '(FLAG)' Expr *visitParenExpr(ParenExpr *E) { E->setSubExpr(validate(E->getSubExpr())); return E; } // Prefix '!'. Other prefix operators are rejected. Expr *visitPrefixUnaryExpr(PrefixUnaryExpr *E) { auto OpName = getDeclRefStr(E->getFn(), DeclRefKind::PrefixOperator); if (!OpName.hasValue() || *OpName != "!") { D.diagnose(E->getLoc(), diag::unsupported_conditional_compilation_unary_expression); return nullptr; } E->setArg(validate(E->getArg())); return E; } // Fold sequence expression for non-Swift3 mode. Expr *visitSequenceExpr(SequenceExpr *E) { ArrayRef Elts = E->getElements(); Expr *foldedExpr; if (Ctx.isSwiftVersion3()) { foldedExpr = validateSequence(Elts); } else { auto LHS = validate(Elts[0]); Elts = Elts.slice(1); foldedExpr = foldSequence(LHS, Elts); } assert(Elts.empty()); return foldedExpr; } // Other expression types are unsupported. Expr *visitExpr(Expr *E) { return diagnoseUnsupportedExpr(E); } Expr *validate(Expr *E) { if (auto E2 = visit(E)) return E2; HasError |= true; return E; } bool hasError() const { return HasError; } }; /// Validate and modify the condition expression. /// Returns \c true if the condition contains any error. static bool validateIfConfigCondition(Expr *&condition, ASTContext &Context, DiagnosticEngine &D) { ValidateIfConfigCondition Validator(Context, D); condition = Validator.validate(condition); return Validator.hasError(); } /// The condition evaluator. /// The condition must be validated with validateIfConfigCondition(). class EvaluateIfConfigCondition : public ExprVisitor { ASTContext &Ctx; /// Get the identifier string from an \c Expr assuming it's an /// \c UnresolvedDeclRefExpr. StringRef getDeclRefStr(Expr *E) { return cast(E)->getName().getBaseIdentifier().str(); } public: EvaluateIfConfigCondition(ASTContext &Ctx) : Ctx(Ctx) {} bool visitBooleanLiteralExpr(BooleanLiteralExpr *E) { return E->getValue(); } bool visitIntegerLiteralExpr(IntegerLiteralExpr *E) { return E->getDigitsText() != "0"; } bool visitUnresolvedDeclRefExpr(UnresolvedDeclRefExpr *E) { auto Name = getDeclRefStr(E); return Ctx.LangOpts.isCustomConditionalCompilationFlagSet(Name); } bool visitCallExpr(CallExpr *E) { auto KindName = getDeclRefStr(E->getFn()); auto *Arg = cast(E->getArg())->getSubExpr(); if (KindName == "_compiler_version") { auto Str = cast(Arg)->getValue(); auto Val = version::Version::parseCompilerVersionString( Str, SourceLoc(), nullptr).getValue(); auto thisVersion = version::Version::getCurrentCompilerVersion(); return thisVersion >= Val; } else if (KindName == "swift") { auto PUE = cast(Arg); auto Str = extractExprSource(Ctx.SourceMgr, PUE->getArg()); auto Val = version::Version::parseVersionString( Str, SourceLoc(), nullptr).getValue(); auto thisVersion = Ctx.LangOpts.EffectiveLanguageVersion; return thisVersion >= Val; } else if (KindName == "compiler") { auto PUE = cast(Arg); auto Str = extractExprSource(Ctx.SourceMgr, PUE->getArg()); auto Val = version::Version::parseVersionString( Str, SourceLoc(), nullptr).getValue(); auto thisVersion = version::Version::getCurrentLanguageVersion(); return thisVersion >= Val; } else if (KindName == "canImport") { auto Str = extractExprSource(Ctx.SourceMgr, Arg); return Ctx.canImportModule({ Ctx.getIdentifier(Str) , E->getLoc() }); } auto Val = getDeclRefStr(Arg); auto Kind = getPlatformConditionKind(KindName).getValue(); return Ctx.LangOpts.checkPlatformCondition(Kind, Val); } bool visitPrefixUnaryExpr(PrefixUnaryExpr *E) { return !visit(E->getArg()); } bool visitParenExpr(ParenExpr *E) { return visit(E->getSubExpr()); } bool visitBinaryExpr(BinaryExpr *E) { assert(!Ctx.isSwiftVersion3() && "BinaryExpr in Swift3 mode"); auto OpName = getDeclRefStr(E->getFn()); auto Args = E->getArg()->getElements(); if (OpName == "||") return visit(Args[0]) || visit(Args[1]); if (OpName == "&&") return visit(Args[0]) && visit(Args[1]); llvm_unreachable("unsupported binary operator"); } bool visitSequenceExpr(SequenceExpr *E) { assert(Ctx.isSwiftVersion3() && "SequenceExpr in non-Swift3 mode"); ArrayRef Elems = E->getElements(); auto Result = visit(Elems[0]); Elems = Elems.slice(1); while (!Elems.empty()) { auto OpName = getDeclRefStr(Elems[0]); if (OpName == "||") { Result = Result || visit(Elems[1]); if (Result) // Note that this is the Swift3 behavior. // e.g. 'false || true && false' evaluates to 'true'. return true; } else if (OpName == "&&") { Result = Result && visit(Elems[1]); if (!Result) // Ditto. // e.g. 'false && true || true' evaluates to 'false'. return false; } else { llvm_unreachable("must be removed in validation phase"); } Elems = Elems.slice(2); } return Result; } bool visitExpr(Expr *E) { llvm_unreachable("Unvalidated condition?"); } }; /// Evaluate the condition. /// \c true if success, \c false if failed. static bool evaluateIfConfigCondition(Expr *Condition, ASTContext &Context) { return EvaluateIfConfigCondition(Context).visit(Condition); } /// Version condition checker. class IsVersionIfConfigCondition : public ExprVisitor { /// Get the identifier string from an \c Expr assuming it's an /// \c UnresolvedDeclRefExpr. StringRef getDeclRefStr(Expr *E) { return cast(E)->getName().getBaseIdentifier().str(); } public: IsVersionIfConfigCondition() {} bool visitBinaryExpr(BinaryExpr *E) { auto OpName = getDeclRefStr(E->getFn()); auto Args = E->getArg()->getElements(); if (OpName == "||") return visit(Args[0]) && visit(Args[1]); if (OpName == "&&") return visit(Args[0]) || visit(Args[1]); llvm_unreachable("unsupported binary operator"); } bool visitCallExpr(CallExpr *E) { auto KindName = getDeclRefStr(E->getFn()); return KindName == "_compiler_version" || KindName == "swift" || KindName == "compiler"; } bool visitPrefixUnaryExpr(PrefixUnaryExpr *E) { return visit(E->getArg()); } bool visitParenExpr(ParenExpr *E) { return visit(E->getSubExpr()); } bool visitExpr(Expr *E) { return false; } }; /// Returns \c true if the condition is a version check. static bool isVersionIfConfigCondition(Expr *Condition) { return IsVersionIfConfigCondition().visit(Condition); } /// Get the identifier string from an \c Expr if it's an /// \c UnresolvedDeclRefExpr, otherwise the empty string. static StringRef getDeclRefStr(Expr *E) { if (auto *UDRE = dyn_cast(E)) { return UDRE->getName().getBaseIdentifier().str(); } return ""; } static bool isPlatformConditionDisjunction(Expr *E, PlatformConditionKind Kind, ArrayRef Vals) { if (auto *Or = dyn_cast(E)) { if (getDeclRefStr(Or->getFn()) == "||") { auto Args = Or->getArg()->getElements(); return (isPlatformConditionDisjunction(Args[0], Kind, Vals) && isPlatformConditionDisjunction(Args[1], Kind, Vals)); } } else if (auto *P = dyn_cast(E)) { return isPlatformConditionDisjunction(P->getSubExpr(), Kind, Vals); } else if (auto *C = dyn_cast(E)) { if (getPlatformConditionKind(getDeclRefStr(C->getFn())) != Kind) return false; if (auto *ArgP = dyn_cast(C->getArg())) { if (auto *Arg = ArgP->getSubExpr()) { auto ArgStr = getDeclRefStr(Arg); for (auto V : Vals) { if (ArgStr == V) return true; } } } } return false; } // Search for the first occurrence of a _likely_ (but not definite) implicit // simulator-environment platform condition, or negation thereof. This is // defined as any logical conjunction of one or more os() platform conditions // _strictly_ from the set {iOS, tvOS, watchOS} and one or more arch() platform // conditions _strictly_ from the set {i386, x86_64}. // // These are (at the time of writing) defined as de-facto simulators in // Platform.cpp, and if a user is testing them they're _likely_ looking for // simulator-ness indirectly. If there is anything else in the condition aside // from these conditions (or the negation of such a conjunction), we // conservatively assume the user is testing something other than // simulator-ness. static Expr *findAnyLikelySimulatorEnvironmentTest(Expr *Condition) { if (!Condition) return nullptr; if (auto *N = dyn_cast(Condition)) { return findAnyLikelySimulatorEnvironmentTest(N->getArg()); } else if (auto *P = dyn_cast(Condition)) { return findAnyLikelySimulatorEnvironmentTest(P->getSubExpr()); } // We assume the user is writing the condition in CNF -- say (os(iOS) || // os(tvOS)) && (arch(i386) || arch(x86_64)) -- rather than DNF, as the former // is exponentially more terse, and these conditions are already quite // unwieldy. If field evidence shows people using other variants, possibly add // them here. auto isSimulatorPlatformOSTest = [](Expr *E) -> bool { return isPlatformConditionDisjunction( E, PlatformConditionKind::OS, {"iOS", "tvOS", "watchOS"}); }; auto isSimulatorPlatformArchTest = [](Expr *E) -> bool { return isPlatformConditionDisjunction( E, PlatformConditionKind::Arch, {"i386", "x86_64"}); }; if (auto *And = dyn_cast(Condition)) { if (getDeclRefStr(And->getFn()) == "&&") { auto Args = And->getArg()->getElements(); if ((isSimulatorPlatformOSTest(Args[0]) && isSimulatorPlatformArchTest(Args[1])) || (isSimulatorPlatformOSTest(Args[1]) && isSimulatorPlatformArchTest(Args[0]))) { return And; } } } return nullptr; } } // end anonymous namespace /// Parse and populate a #if ... #endif directive. /// Delegate callback function to parse elements in the blocks. ParserResult Parser::parseIfConfig( llvm::function_ref &, bool)> parseElements) { SyntaxParsingContext IfConfigCtx(SyntaxContext, SyntaxKind::IfConfigDecl); SmallVector Clauses; Parser::StructureMarkerRAII ParsingDecl( *this, Tok.getLoc(), Parser::StructureMarkerKind::IfConfig); bool foundActive = false; bool isVersionCondition = false; while (1) { SyntaxParsingContext ClauseContext(SyntaxContext, SyntaxKind::IfConfigClause); bool isElse = Tok.is(tok::pound_else); SourceLoc ClauseLoc = consumeToken(); Expr *Condition = nullptr; bool isActive = false; // Parse the condition. Evaluate it to determine the active // clause unless we're doing a parse-only pass. if (isElse) { isActive = !foundActive && State->PerformConditionEvaluation; } else { llvm::SaveAndRestore S(InPoundIfEnvironment, true); ParserResult Result = parseExprSequence(diag::expected_expr, /*isBasic*/true, /*isForDirective*/true); if (Result.isNull()) return makeParserError(); Condition = Result.get(); if (validateIfConfigCondition(Condition, Context, Diags)) { // Error in the condition; isActive = false; isVersionCondition = false; } else if (!foundActive && State->PerformConditionEvaluation) { // Evaluate the condition only if we haven't found any active one and // we're not in parse-only mode. isActive = evaluateIfConfigCondition(Condition, Context); isVersionCondition = isVersionIfConfigCondition(Condition); } } foundActive |= isActive; if (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) { diagnose(Tok.getLoc(), diag::extra_tokens_conditional_compilation_directive); } if (Expr *Test = findAnyLikelySimulatorEnvironmentTest(Condition)) { diagnose(Test->getLoc(), diag::likely_simulator_platform_condition) .fixItReplace(Test->getSourceRange(), "targetEnvironment(simulator)"); } // Parse elements SmallVector Elements; if (isActive || !isVersionCondition) { parseElements(Elements, isActive); } else { DiagnosticTransaction DT(Diags); skipUntilConditionalBlockClose(); DT.abort(); } Clauses.emplace_back(ClauseLoc, Condition, Context.AllocateCopy(Elements), isActive); if (Tok.isNot(tok::pound_elseif, tok::pound_else)) break; if (isElse) diagnose(Tok, diag::expected_close_after_else_directive); } SyntaxContext->collectNodesInPlace(SyntaxKind::IfConfigClauseList); SourceLoc EndLoc; bool HadMissingEnd = parseEndIfDirective(EndLoc); auto *ICD = new (Context) IfConfigDecl(CurDeclContext, Context.AllocateCopy(Clauses), EndLoc, HadMissingEnd); return makeParserResult(ICD); }