//===--- RewriteObjCFoundationAPI.cpp - Foundation API Rewriter -----------===// // // 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 // //===----------------------------------------------------------------------===// // // Rewrites legacy method calls to modern syntax. // //===----------------------------------------------------------------------===// #include "clang/Edit/Rewriters.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/NSAPI.h" #include "clang/AST/ParentMap.h" #include "clang/Edit/Commit.h" #include "clang/Lex/Lexer.h" using namespace clang; using namespace edit; static bool checkForLiteralCreation(const ObjCMessageExpr *Msg, IdentifierInfo *&ClassId, const LangOptions &LangOpts) { if (!Msg || Msg->isImplicit() || !Msg->getMethodDecl()) return false; const ObjCInterfaceDecl *Receiver = Msg->getReceiverInterface(); if (!Receiver) return false; ClassId = Receiver->getIdentifier(); if (Msg->getReceiverKind() == ObjCMessageExpr::Class) return true; // When in ARC mode we also convert "[[.. alloc] init]" messages to literals, // since the change from +1 to +0 will be handled fine by ARC. if (LangOpts.ObjCAutoRefCount) { if (Msg->getReceiverKind() == ObjCMessageExpr::Instance) { if (const ObjCMessageExpr *Rec = dyn_cast( Msg->getInstanceReceiver()->IgnoreParenImpCasts())) { if (Rec->getMethodFamily() == OMF_alloc) return true; } } } return false; } //===----------------------------------------------------------------------===// // rewriteObjCRedundantCallWithLiteral. //===----------------------------------------------------------------------===// bool edit::rewriteObjCRedundantCallWithLiteral(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { IdentifierInfo *II = nullptr; if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts())) return false; if (Msg->getNumArgs() != 1) return false; const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts(); Selector Sel = Msg->getSelector(); if ((isa(Arg) && NS.getNSClassId(NSAPI::ClassId_NSString) == II && (NS.getNSStringSelector(NSAPI::NSStr_stringWithString) == Sel || NS.getNSStringSelector(NSAPI::NSStr_initWithString) == Sel)) || (isa(Arg) && NS.getNSClassId(NSAPI::ClassId_NSArray) == II && (NS.getNSArraySelector(NSAPI::NSArr_arrayWithArray) == Sel || NS.getNSArraySelector(NSAPI::NSArr_initWithArray) == Sel)) || (isa(Arg) && NS.getNSClassId(NSAPI::ClassId_NSDictionary) == II && (NS.getNSDictionarySelector( NSAPI::NSDict_dictionaryWithDictionary) == Sel || NS.getNSDictionarySelector(NSAPI::NSDict_initWithDictionary) == Sel))) { commit.replaceWithInner(Msg->getSourceRange(), Msg->getArg(0)->getSourceRange()); return true; } return false; } //===----------------------------------------------------------------------===// // rewriteToObjCSubscriptSyntax. //===----------------------------------------------------------------------===// /// Check for classes that accept 'objectForKey:' (or the other selectors /// that the migrator handles) but return their instances as 'id', resulting /// in the compiler resolving 'objectForKey:' as the method from NSDictionary. /// /// When checking if we can convert to subscripting syntax, check whether /// the receiver is a result of a class method from a hardcoded list of /// such classes. In such a case return the specific class as the interface /// of the receiver. /// /// FIXME: Remove this when these classes start using 'instancetype'. static const ObjCInterfaceDecl * maybeAdjustInterfaceForSubscriptingCheck(const ObjCInterfaceDecl *IFace, const Expr *Receiver, ASTContext &Ctx) { assert(IFace && Receiver); // If the receiver has type 'id'... if (!Ctx.isObjCIdType(Receiver->getType().getUnqualifiedType())) return IFace; const ObjCMessageExpr * InnerMsg = dyn_cast(Receiver->IgnoreParenCasts()); if (!InnerMsg) return IFace; QualType ClassRec; switch (InnerMsg->getReceiverKind()) { case ObjCMessageExpr::Instance: case ObjCMessageExpr::SuperInstance: return IFace; case ObjCMessageExpr::Class: ClassRec = InnerMsg->getClassReceiver(); break; case ObjCMessageExpr::SuperClass: ClassRec = InnerMsg->getSuperType(); break; } if (ClassRec.isNull()) return IFace; // ...and it is the result of a class message... const ObjCObjectType *ObjTy = ClassRec->getAs(); if (!ObjTy) return IFace; const ObjCInterfaceDecl *OID = ObjTy->getInterface(); // ...and the receiving class is NSMapTable or NSLocale, return that // class as the receiving interface. if (OID->getName() == "NSMapTable" || OID->getName() == "NSLocale") return OID; return IFace; } static bool canRewriteToSubscriptSyntax(const ObjCInterfaceDecl *&IFace, const ObjCMessageExpr *Msg, ASTContext &Ctx, Selector subscriptSel) { const Expr *Rec = Msg->getInstanceReceiver(); if (!Rec) return false; IFace = maybeAdjustInterfaceForSubscriptingCheck(IFace, Rec, Ctx); if (const ObjCMethodDecl *MD = IFace->lookupInstanceMethod(subscriptSel)) { if (!MD->isUnavailable()) return true; } return false; } static bool subscriptOperatorNeedsParens(const Expr *FullExpr); static void maybePutParensOnReceiver(const Expr *Receiver, Commit &commit) { if (subscriptOperatorNeedsParens(Receiver)) { SourceRange RecRange = Receiver->getSourceRange(); commit.insertWrap("(", RecRange, ")"); } } static bool rewriteToSubscriptGetCommon(const ObjCMessageExpr *Msg, Commit &commit) { if (Msg->getNumArgs() != 1) return false; const Expr *Rec = Msg->getInstanceReceiver(); if (!Rec) return false; SourceRange MsgRange = Msg->getSourceRange(); SourceRange RecRange = Rec->getSourceRange(); SourceRange ArgRange = Msg->getArg(0)->getSourceRange(); commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), ArgRange.getBegin()), CharSourceRange::getTokenRange(RecRange)); commit.replaceWithInner(SourceRange(ArgRange.getBegin(), MsgRange.getEnd()), ArgRange); commit.insertWrap("[", ArgRange, "]"); maybePutParensOnReceiver(Rec, commit); return true; } static bool rewriteToArraySubscriptGet(const ObjCInterfaceDecl *IFace, const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(), NS.getObjectAtIndexedSubscriptSelector())) return false; return rewriteToSubscriptGetCommon(Msg, commit); } static bool rewriteToDictionarySubscriptGet(const ObjCInterfaceDecl *IFace, const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(), NS.getObjectForKeyedSubscriptSelector())) return false; return rewriteToSubscriptGetCommon(Msg, commit); } static bool rewriteToArraySubscriptSet(const ObjCInterfaceDecl *IFace, const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(), NS.getSetObjectAtIndexedSubscriptSelector())) return false; if (Msg->getNumArgs() != 2) return false; const Expr *Rec = Msg->getInstanceReceiver(); if (!Rec) return false; SourceRange MsgRange = Msg->getSourceRange(); SourceRange RecRange = Rec->getSourceRange(); SourceRange Arg0Range = Msg->getArg(0)->getSourceRange(); SourceRange Arg1Range = Msg->getArg(1)->getSourceRange(); commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), Arg0Range.getBegin()), CharSourceRange::getTokenRange(RecRange)); commit.replaceWithInner(CharSourceRange::getCharRange(Arg0Range.getBegin(), Arg1Range.getBegin()), CharSourceRange::getTokenRange(Arg0Range)); commit.replaceWithInner(SourceRange(Arg1Range.getBegin(), MsgRange.getEnd()), Arg1Range); commit.insertWrap("[", CharSourceRange::getCharRange(Arg0Range.getBegin(), Arg1Range.getBegin()), "] = "); maybePutParensOnReceiver(Rec, commit); return true; } static bool rewriteToDictionarySubscriptSet(const ObjCInterfaceDecl *IFace, const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(), NS.getSetObjectForKeyedSubscriptSelector())) return false; if (Msg->getNumArgs() != 2) return false; const Expr *Rec = Msg->getInstanceReceiver(); if (!Rec) return false; SourceRange MsgRange = Msg->getSourceRange(); SourceRange RecRange = Rec->getSourceRange(); SourceRange Arg0Range = Msg->getArg(0)->getSourceRange(); SourceRange Arg1Range = Msg->getArg(1)->getSourceRange(); SourceLocation LocBeforeVal = Arg0Range.getBegin(); commit.insertBefore(LocBeforeVal, "] = "); commit.insertFromRange(LocBeforeVal, Arg1Range, /*afterToken=*/false, /*beforePreviousInsertions=*/true); commit.insertBefore(LocBeforeVal, "["); commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), Arg0Range.getBegin()), CharSourceRange::getTokenRange(RecRange)); commit.replaceWithInner(SourceRange(Arg0Range.getBegin(), MsgRange.getEnd()), Arg0Range); maybePutParensOnReceiver(Rec, commit); return true; } bool edit::rewriteToObjCSubscriptSyntax(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { if (!Msg || Msg->isImplicit() || Msg->getReceiverKind() != ObjCMessageExpr::Instance) return false; const ObjCMethodDecl *Method = Msg->getMethodDecl(); if (!Method) return false; const ObjCInterfaceDecl *IFace = NS.getASTContext().getObjContainingInterface(Method); if (!IFace) return false; Selector Sel = Msg->getSelector(); if (Sel == NS.getNSArraySelector(NSAPI::NSArr_objectAtIndex)) return rewriteToArraySubscriptGet(IFace, Msg, NS, commit); if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_objectForKey)) return rewriteToDictionarySubscriptGet(IFace, Msg, NS, commit); if (Msg->getNumArgs() != 2) return false; if (Sel == NS.getNSArraySelector(NSAPI::NSMutableArr_replaceObjectAtIndex)) return rewriteToArraySubscriptSet(IFace, Msg, NS, commit); if (Sel == NS.getNSDictionarySelector(NSAPI::NSMutableDict_setObjectForKey)) return rewriteToDictionarySubscriptSet(IFace, Msg, NS, commit); return false; } //===----------------------------------------------------------------------===// // rewriteToObjCLiteralSyntax. //===----------------------------------------------------------------------===// static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit, const ParentMap *PMap); static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit); static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit); static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit); static bool rewriteToStringBoxedExpression(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit); bool edit::rewriteToObjCLiteralSyntax(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit, const ParentMap *PMap) { IdentifierInfo *II = nullptr; if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts())) return false; if (II == NS.getNSClassId(NSAPI::ClassId_NSArray)) return rewriteToArrayLiteral(Msg, NS, commit, PMap); if (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary)) return rewriteToDictionaryLiteral(Msg, NS, commit); if (II == NS.getNSClassId(NSAPI::ClassId_NSNumber)) return rewriteToNumberLiteral(Msg, NS, commit); if (II == NS.getNSClassId(NSAPI::ClassId_NSString)) return rewriteToStringBoxedExpression(Msg, NS, commit); return false; } /// Returns true if the immediate message arguments of \c Msg should not /// be rewritten because it will interfere with the rewrite of the parent /// message expression. e.g. /// \code /// [NSDictionary dictionaryWithObjects: /// [NSArray arrayWithObjects:@"1", @"2", nil] /// forKeys:[NSArray arrayWithObjects:@"A", @"B", nil]]; /// \endcode /// It will return true for this because we are going to rewrite this directly /// to a dictionary literal without any array literals. static bool shouldNotRewriteImmediateMessageArgs(const ObjCMessageExpr *Msg, const NSAPI &NS); //===----------------------------------------------------------------------===// // rewriteToArrayLiteral. //===----------------------------------------------------------------------===// /// Adds an explicit cast to 'id' if the type is not objc object. static void objectifyExpr(const Expr *E, Commit &commit); static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit, const ParentMap *PMap) { if (PMap) { const ObjCMessageExpr *ParentMsg = dyn_cast_or_null(PMap->getParentIgnoreParenCasts(Msg)); if (shouldNotRewriteImmediateMessageArgs(ParentMsg, NS)) return false; } Selector Sel = Msg->getSelector(); SourceRange MsgRange = Msg->getSourceRange(); if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array)) { if (Msg->getNumArgs() != 0) return false; commit.replace(MsgRange, "@[]"); return true; } if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) { if (Msg->getNumArgs() != 1) return false; objectifyExpr(Msg->getArg(0), commit); SourceRange ArgRange = Msg->getArg(0)->getSourceRange(); commit.replaceWithInner(MsgRange, ArgRange); commit.insertWrap("@[", ArgRange, "]"); return true; } if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects) || Sel == NS.getNSArraySelector(NSAPI::NSArr_initWithObjects)) { if (Msg->getNumArgs() == 0) return false; const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1); if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) return false; for (unsigned i = 0, e = Msg->getNumArgs() - 1; i != e; ++i) objectifyExpr(Msg->getArg(i), commit); if (Msg->getNumArgs() == 1) { commit.replace(MsgRange, "@[]"); return true; } SourceRange ArgRange(Msg->getArg(0)->getBeginLoc(), Msg->getArg(Msg->getNumArgs() - 2)->getEndLoc()); commit.replaceWithInner(MsgRange, ArgRange); commit.insertWrap("@[", ArgRange, "]"); return true; } return false; } //===----------------------------------------------------------------------===// // rewriteToDictionaryLiteral. //===----------------------------------------------------------------------===// /// If \c Msg is an NSArray creation message or literal, this gets the /// objects that were used to create it. /// \returns true if it is an NSArray and we got objects, or false otherwise. static bool getNSArrayObjects(const Expr *E, const NSAPI &NS, SmallVectorImpl &Objs) { if (!E) return false; E = E->IgnoreParenCasts(); if (!E) return false; if (const ObjCMessageExpr *Msg = dyn_cast(E)) { IdentifierInfo *Cls = nullptr; if (!checkForLiteralCreation(Msg, Cls, NS.getASTContext().getLangOpts())) return false; if (Cls != NS.getNSClassId(NSAPI::ClassId_NSArray)) return false; Selector Sel = Msg->getSelector(); if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array)) return true; // empty array. if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) { if (Msg->getNumArgs() != 1) return false; Objs.push_back(Msg->getArg(0)); return true; } if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects) || Sel == NS.getNSArraySelector(NSAPI::NSArr_initWithObjects)) { if (Msg->getNumArgs() == 0) return false; const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1); if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) return false; for (unsigned i = 0, e = Msg->getNumArgs() - 1; i != e; ++i) Objs.push_back(Msg->getArg(i)); return true; } } else if (const ObjCArrayLiteral *ArrLit = dyn_cast(E)) { for (unsigned i = 0, e = ArrLit->getNumElements(); i != e; ++i) Objs.push_back(ArrLit->getElement(i)); return true; } return false; } static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { Selector Sel = Msg->getSelector(); SourceRange MsgRange = Msg->getSourceRange(); if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_dictionary)) { if (Msg->getNumArgs() != 0) return false; commit.replace(MsgRange, "@{}"); return true; } if (Sel == NS.getNSDictionarySelector( NSAPI::NSDict_dictionaryWithObjectForKey)) { if (Msg->getNumArgs() != 2) return false; objectifyExpr(Msg->getArg(0), commit); objectifyExpr(Msg->getArg(1), commit); SourceRange ValRange = Msg->getArg(0)->getSourceRange(); SourceRange KeyRange = Msg->getArg(1)->getSourceRange(); // Insert key before the value. commit.insertBefore(ValRange.getBegin(), ": "); commit.insertFromRange(ValRange.getBegin(), CharSourceRange::getTokenRange(KeyRange), /*afterToken=*/false, /*beforePreviousInsertions=*/true); commit.insertBefore(ValRange.getBegin(), "@{"); commit.insertAfterToken(ValRange.getEnd(), "}"); commit.replaceWithInner(MsgRange, ValRange); return true; } if (Sel == NS.getNSDictionarySelector( NSAPI::NSDict_dictionaryWithObjectsAndKeys) || Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsAndKeys)) { if (Msg->getNumArgs() % 2 != 1) return false; unsigned SentinelIdx = Msg->getNumArgs() - 1; const Expr *SentinelExpr = Msg->getArg(SentinelIdx); if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) return false; if (Msg->getNumArgs() == 1) { commit.replace(MsgRange, "@{}"); return true; } for (unsigned i = 0; i < SentinelIdx; i += 2) { objectifyExpr(Msg->getArg(i), commit); objectifyExpr(Msg->getArg(i+1), commit); SourceRange ValRange = Msg->getArg(i)->getSourceRange(); SourceRange KeyRange = Msg->getArg(i+1)->getSourceRange(); // Insert value after key. commit.insertAfterToken(KeyRange.getEnd(), ": "); commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true); commit.remove(CharSourceRange::getCharRange(ValRange.getBegin(), KeyRange.getBegin())); } // Range of arguments up until and including the last key. // The sentinel and first value are cut off, the value will move after the // key. SourceRange ArgRange(Msg->getArg(1)->getBeginLoc(), Msg->getArg(SentinelIdx - 1)->getEndLoc()); commit.insertWrap("@{", ArgRange, "}"); commit.replaceWithInner(MsgRange, ArgRange); return true; } if (Sel == NS.getNSDictionarySelector( NSAPI::NSDict_dictionaryWithObjectsForKeys) || Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsForKeys)) { if (Msg->getNumArgs() != 2) return false; SmallVector Vals; if (!getNSArrayObjects(Msg->getArg(0), NS, Vals)) return false; SmallVector Keys; if (!getNSArrayObjects(Msg->getArg(1), NS, Keys)) return false; if (Vals.size() != Keys.size()) return false; if (Vals.empty()) { commit.replace(MsgRange, "@{}"); return true; } for (unsigned i = 0, n = Vals.size(); i < n; ++i) { objectifyExpr(Vals[i], commit); objectifyExpr(Keys[i], commit); SourceRange ValRange = Vals[i]->getSourceRange(); SourceRange KeyRange = Keys[i]->getSourceRange(); // Insert value after key. commit.insertAfterToken(KeyRange.getEnd(), ": "); commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true); } // Range of arguments up until and including the last key. // The first value is cut off, the value will move after the key. SourceRange ArgRange(Keys.front()->getBeginLoc(), Keys.back()->getEndLoc()); commit.insertWrap("@{", ArgRange, "}"); commit.replaceWithInner(MsgRange, ArgRange); return true; } return false; } static bool shouldNotRewriteImmediateMessageArgs(const ObjCMessageExpr *Msg, const NSAPI &NS) { if (!Msg) return false; IdentifierInfo *II = nullptr; if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts())) return false; if (II != NS.getNSClassId(NSAPI::ClassId_NSDictionary)) return false; Selector Sel = Msg->getSelector(); if (Sel == NS.getNSDictionarySelector( NSAPI::NSDict_dictionaryWithObjectsForKeys) || Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsForKeys)) { if (Msg->getNumArgs() != 2) return false; SmallVector Vals; if (!getNSArrayObjects(Msg->getArg(0), NS, Vals)) return false; SmallVector Keys; if (!getNSArrayObjects(Msg->getArg(1), NS, Keys)) return false; if (Vals.size() != Keys.size()) return false; return true; } return false; } //===----------------------------------------------------------------------===// // rewriteToNumberLiteral. //===----------------------------------------------------------------------===// static bool rewriteToCharLiteral(const ObjCMessageExpr *Msg, const CharacterLiteral *Arg, const NSAPI &NS, Commit &commit) { if (Arg->getKind() != CharacterLiteral::Ascii) return false; if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithChar, Msg->getSelector())) { SourceRange ArgRange = Arg->getSourceRange(); commit.replaceWithInner(Msg->getSourceRange(), ArgRange); commit.insert(ArgRange.getBegin(), "@"); return true; } return rewriteToNumericBoxedExpression(Msg, NS, commit); } static bool rewriteToBoolLiteral(const ObjCMessageExpr *Msg, const Expr *Arg, const NSAPI &NS, Commit &commit) { if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithBool, Msg->getSelector())) { SourceRange ArgRange = Arg->getSourceRange(); commit.replaceWithInner(Msg->getSourceRange(), ArgRange); commit.insert(ArgRange.getBegin(), "@"); return true; } return rewriteToNumericBoxedExpression(Msg, NS, commit); } namespace { struct LiteralInfo { bool Hex, Octal; StringRef U, F, L, LL; CharSourceRange WithoutSuffRange; }; } static bool getLiteralInfo(SourceRange literalRange, bool isFloat, bool isIntZero, ASTContext &Ctx, LiteralInfo &Info) { if (literalRange.getBegin().isMacroID() || literalRange.getEnd().isMacroID()) return false; StringRef text = Lexer::getSourceText( CharSourceRange::getTokenRange(literalRange), Ctx.getSourceManager(), Ctx.getLangOpts()); if (text.empty()) return false; Optional UpperU, UpperL; bool UpperF = false; struct Suff { static bool has(StringRef suff, StringRef &text) { if (text.endswith(suff)) { text = text.substr(0, text.size()-suff.size()); return true; } return false; } }; while (1) { if (Suff::has("u", text)) { UpperU = false; } else if (Suff::has("U", text)) { UpperU = true; } else if (Suff::has("ll", text)) { UpperL = false; } else if (Suff::has("LL", text)) { UpperL = true; } else if (Suff::has("l", text)) { UpperL = false; } else if (Suff::has("L", text)) { UpperL = true; } else if (isFloat && Suff::has("f", text)) { UpperF = false; } else if (isFloat && Suff::has("F", text)) { UpperF = true; } else break; } if (!UpperU.hasValue() && !UpperL.hasValue()) UpperU = UpperL = true; else if (UpperU.hasValue() && !UpperL.hasValue()) UpperL = UpperU; else if (UpperL.hasValue() && !UpperU.hasValue()) UpperU = UpperL; Info.U = *UpperU ? "U" : "u"; Info.L = *UpperL ? "L" : "l"; Info.LL = *UpperL ? "LL" : "ll"; Info.F = UpperF ? "F" : "f"; Info.Hex = Info.Octal = false; if (text.startswith("0x")) Info.Hex = true; else if (!isFloat && !isIntZero && text.startswith("0")) Info.Octal = true; SourceLocation B = literalRange.getBegin(); Info.WithoutSuffRange = CharSourceRange::getCharRange(B, B.getLocWithOffset(text.size())); return true; } static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { if (Msg->getNumArgs() != 1) return false; const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts(); if (const CharacterLiteral *CharE = dyn_cast(Arg)) return rewriteToCharLiteral(Msg, CharE, NS, commit); if (const ObjCBoolLiteralExpr *BE = dyn_cast(Arg)) return rewriteToBoolLiteral(Msg, BE, NS, commit); if (const CXXBoolLiteralExpr *BE = dyn_cast(Arg)) return rewriteToBoolLiteral(Msg, BE, NS, commit); const Expr *literalE = Arg; if (const UnaryOperator *UOE = dyn_cast(literalE)) { if (UOE->getOpcode() == UO_Plus || UOE->getOpcode() == UO_Minus) literalE = UOE->getSubExpr(); } // Only integer and floating literals, otherwise try to rewrite to boxed // expression. if (!isa(literalE) && !isa(literalE)) return rewriteToNumericBoxedExpression(Msg, NS, commit); ASTContext &Ctx = NS.getASTContext(); Selector Sel = Msg->getSelector(); Optional MKOpt = NS.getNSNumberLiteralMethodKind(Sel); if (!MKOpt) return false; NSAPI::NSNumberLiteralMethodKind MK = *MKOpt; bool CallIsUnsigned = false, CallIsLong = false, CallIsLongLong = false; bool CallIsFloating = false, CallIsDouble = false; switch (MK) { // We cannot have these calls with int/float literals. case NSAPI::NSNumberWithChar: case NSAPI::NSNumberWithUnsignedChar: case NSAPI::NSNumberWithShort: case NSAPI::NSNumberWithUnsignedShort: case NSAPI::NSNumberWithBool: return rewriteToNumericBoxedExpression(Msg, NS, commit); case NSAPI::NSNumberWithUnsignedInt: case NSAPI::NSNumberWithUnsignedInteger: CallIsUnsigned = true; LLVM_FALLTHROUGH; case NSAPI::NSNumberWithInt: case NSAPI::NSNumberWithInteger: break; case NSAPI::NSNumberWithUnsignedLong: CallIsUnsigned = true; LLVM_FALLTHROUGH; case NSAPI::NSNumberWithLong: CallIsLong = true; break; case NSAPI::NSNumberWithUnsignedLongLong: CallIsUnsigned = true; LLVM_FALLTHROUGH; case NSAPI::NSNumberWithLongLong: CallIsLongLong = true; break; case NSAPI::NSNumberWithDouble: CallIsDouble = true; LLVM_FALLTHROUGH; case NSAPI::NSNumberWithFloat: CallIsFloating = true; break; } SourceRange ArgRange = Arg->getSourceRange(); QualType ArgTy = Arg->getType(); QualType CallTy = Msg->getArg(0)->getType(); // Check for the easy case, the literal maps directly to the call. if (Ctx.hasSameType(ArgTy, CallTy)) { commit.replaceWithInner(Msg->getSourceRange(), ArgRange); commit.insert(ArgRange.getBegin(), "@"); return true; } // We will need to modify the literal suffix to get the same type as the call. // Try with boxed expression if it came from a macro. if (ArgRange.getBegin().isMacroID()) return rewriteToNumericBoxedExpression(Msg, NS, commit); bool LitIsFloat = ArgTy->isFloatingType(); // For a float passed to integer call, don't try rewriting to objc literal. // It is difficult and a very uncommon case anyway. // But try with boxed expression. if (LitIsFloat && !CallIsFloating) return rewriteToNumericBoxedExpression(Msg, NS, commit); // Try to modify the literal make it the same type as the method call. // -Modify the suffix, and/or // -Change integer to float LiteralInfo LitInfo; bool isIntZero = false; if (const IntegerLiteral *IntE = dyn_cast(literalE)) isIntZero = !IntE->getValue().getBoolValue(); if (!getLiteralInfo(ArgRange, LitIsFloat, isIntZero, Ctx, LitInfo)) return rewriteToNumericBoxedExpression(Msg, NS, commit); // Not easy to do int -> float with hex/octal and uncommon anyway. if (!LitIsFloat && CallIsFloating && (LitInfo.Hex || LitInfo.Octal)) return rewriteToNumericBoxedExpression(Msg, NS, commit); SourceLocation LitB = LitInfo.WithoutSuffRange.getBegin(); SourceLocation LitE = LitInfo.WithoutSuffRange.getEnd(); commit.replaceWithInner(CharSourceRange::getTokenRange(Msg->getSourceRange()), LitInfo.WithoutSuffRange); commit.insert(LitB, "@"); if (!LitIsFloat && CallIsFloating) commit.insert(LitE, ".0"); if (CallIsFloating) { if (!CallIsDouble) commit.insert(LitE, LitInfo.F); } else { if (CallIsUnsigned) commit.insert(LitE, LitInfo.U); if (CallIsLong) commit.insert(LitE, LitInfo.L); else if (CallIsLongLong) commit.insert(LitE, LitInfo.LL); } return true; } // FIXME: Make determination of operator precedence more general and // make it broadly available. static bool subscriptOperatorNeedsParens(const Expr *FullExpr) { const Expr* Expr = FullExpr->IgnoreImpCasts(); if (isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(FullExpr) || isa(Expr) || isa(Expr)) return false; return true; } static bool castOperatorNeedsParens(const Expr *FullExpr) { const Expr* Expr = FullExpr->IgnoreImpCasts(); if (isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || isa(FullExpr) || isa(Expr) || isa(Expr) || isa(Expr)) return false; return true; } static void objectifyExpr(const Expr *E, Commit &commit) { if (!E) return; QualType T = E->getType(); if (T->isObjCObjectPointerType()) { if (const ImplicitCastExpr *ICE = dyn_cast(E)) { if (ICE->getCastKind() != CK_CPointerToObjCPointerCast) return; } else { return; } } else if (!T->isPointerType()) { return; } SourceRange Range = E->getSourceRange(); if (castOperatorNeedsParens(E)) commit.insertWrap("(", Range, ")"); commit.insertBefore(Range.getBegin(), "(id)"); } //===----------------------------------------------------------------------===// // rewriteToNumericBoxedExpression. //===----------------------------------------------------------------------===// static bool isEnumConstant(const Expr *E) { if (const DeclRefExpr *DRE = dyn_cast(E->IgnoreParenImpCasts())) if (const ValueDecl *VD = DRE->getDecl()) return isa(VD); return false; } static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { if (Msg->getNumArgs() != 1) return false; const Expr *Arg = Msg->getArg(0); if (Arg->isTypeDependent()) return false; ASTContext &Ctx = NS.getASTContext(); Selector Sel = Msg->getSelector(); Optional MKOpt = NS.getNSNumberLiteralMethodKind(Sel); if (!MKOpt) return false; NSAPI::NSNumberLiteralMethodKind MK = *MKOpt; const Expr *OrigArg = Arg->IgnoreImpCasts(); QualType FinalTy = Arg->getType(); QualType OrigTy = OrigArg->getType(); uint64_t FinalTySize = Ctx.getTypeSize(FinalTy); uint64_t OrigTySize = Ctx.getTypeSize(OrigTy); bool isTruncated = FinalTySize < OrigTySize; bool needsCast = false; if (const ImplicitCastExpr *ICE = dyn_cast(Arg)) { switch (ICE->getCastKind()) { case CK_LValueToRValue: case CK_NoOp: case CK_UserDefinedConversion: break; case CK_IntegralCast: { if (MK == NSAPI::NSNumberWithBool && OrigTy->isBooleanType()) break; // Be more liberal with Integer/UnsignedInteger which are very commonly // used. if ((MK == NSAPI::NSNumberWithInteger || MK == NSAPI::NSNumberWithUnsignedInteger) && !isTruncated) { if (OrigTy->getAs() || isEnumConstant(OrigArg)) break; if ((MK==NSAPI::NSNumberWithInteger) == OrigTy->isSignedIntegerType() && OrigTySize >= Ctx.getTypeSize(Ctx.IntTy)) break; } needsCast = true; break; } case CK_PointerToBoolean: case CK_IntegralToBoolean: case CK_IntegralToFloating: case CK_FloatingToIntegral: case CK_FloatingToBoolean: case CK_FloatingCast: case CK_FloatingComplexToReal: case CK_FloatingComplexToBoolean: case CK_IntegralComplexToReal: case CK_IntegralComplexToBoolean: case CK_AtomicToNonAtomic: case CK_AddressSpaceConversion: needsCast = true; break; case CK_Dependent: case CK_BitCast: case CK_LValueBitCast: case CK_LValueToRValueBitCast: case CK_BaseToDerived: case CK_DerivedToBase: case CK_UncheckedDerivedToBase: case CK_Dynamic: case CK_ToUnion: case CK_ArrayToPointerDecay: case CK_FunctionToPointerDecay: case CK_NullToPointer: case CK_NullToMemberPointer: case CK_BaseToDerivedMemberPointer: case CK_DerivedToBaseMemberPointer: case CK_MemberPointerToBoolean: case CK_ReinterpretMemberPointer: case CK_ConstructorConversion: case CK_IntegralToPointer: case CK_PointerToIntegral: case CK_ToVoid: case CK_VectorSplat: case CK_CPointerToObjCPointerCast: case CK_BlockPointerToObjCPointerCast: case CK_AnyPointerToBlockPointerCast: case CK_ObjCObjectLValueCast: case CK_FloatingRealToComplex: case CK_FloatingComplexCast: case CK_FloatingComplexToIntegralComplex: case CK_IntegralRealToComplex: case CK_IntegralComplexCast: case CK_IntegralComplexToFloatingComplex: case CK_ARCProduceObject: case CK_ARCConsumeObject: case CK_ARCReclaimReturnedObject: case CK_ARCExtendBlockObject: case CK_NonAtomicToAtomic: case CK_CopyAndAutoreleaseBlockObject: case CK_BuiltinFnToFnPtr: case CK_ZeroToOCLOpaqueType: case CK_IntToOCLSampler: return false; case CK_BooleanToSignedIntegral: llvm_unreachable("OpenCL-specific cast in Objective-C?"); case CK_FloatingToFixedPoint: case CK_FixedPointToFloating: case CK_FixedPointCast: case CK_FixedPointToBoolean: case CK_FixedPointToIntegral: case CK_IntegralToFixedPoint: llvm_unreachable("Fixed point types are disabled for Objective-C"); } } if (needsCast) { DiagnosticsEngine &Diags = Ctx.getDiagnostics(); // FIXME: Use a custom category name to distinguish migration diagnostics. unsigned diagID = Diags.getCustomDiagID(DiagnosticsEngine::Warning, "converting to boxing syntax requires casting %0 to %1"); Diags.Report(Msg->getExprLoc(), diagID) << OrigTy << FinalTy << Msg->getSourceRange(); return false; } SourceRange ArgRange = OrigArg->getSourceRange(); commit.replaceWithInner(Msg->getSourceRange(), ArgRange); if (isa(OrigArg) || isa(OrigArg)) commit.insertBefore(ArgRange.getBegin(), "@"); else commit.insertWrap("@(", ArgRange, ")"); return true; } //===----------------------------------------------------------------------===// // rewriteToStringBoxedExpression. //===----------------------------------------------------------------------===// static bool doRewriteToUTF8StringBoxedExpressionHelper( const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { const Expr *Arg = Msg->getArg(0); if (Arg->isTypeDependent()) return false; ASTContext &Ctx = NS.getASTContext(); const Expr *OrigArg = Arg->IgnoreImpCasts(); QualType OrigTy = OrigArg->getType(); if (OrigTy->isArrayType()) OrigTy = Ctx.getArrayDecayedType(OrigTy); if (const StringLiteral * StrE = dyn_cast(OrigArg->IgnoreParens())) { commit.replaceWithInner(Msg->getSourceRange(), StrE->getSourceRange()); commit.insert(StrE->getBeginLoc(), "@"); return true; } if (const PointerType *PT = OrigTy->getAs()) { QualType PointeeType = PT->getPointeeType(); if (Ctx.hasSameUnqualifiedType(PointeeType, Ctx.CharTy)) { SourceRange ArgRange = OrigArg->getSourceRange(); commit.replaceWithInner(Msg->getSourceRange(), ArgRange); if (isa(OrigArg) || isa(OrigArg)) commit.insertBefore(ArgRange.getBegin(), "@"); else commit.insertWrap("@(", ArgRange, ")"); return true; } } return false; } static bool rewriteToStringBoxedExpression(const ObjCMessageExpr *Msg, const NSAPI &NS, Commit &commit) { Selector Sel = Msg->getSelector(); if (Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithUTF8String) || Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithCString) || Sel == NS.getNSStringSelector(NSAPI::NSStr_initWithUTF8String)) { if (Msg->getNumArgs() != 1) return false; return doRewriteToUTF8StringBoxedExpressionHelper(Msg, NS, commit); } if (Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithCStringEncoding)) { if (Msg->getNumArgs() != 2) return false; const Expr *encodingArg = Msg->getArg(1); if (NS.isNSUTF8StringEncodingConstant(encodingArg) || NS.isNSASCIIStringEncodingConstant(encodingArg)) return doRewriteToUTF8StringBoxedExpressionHelper(Msg, NS, commit); } return false; }