1191 lines · cpp
1//===--- RewriteObjCFoundationAPI.cpp - Foundation API Rewriter -----------===//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-exception6//7//===----------------------------------------------------------------------===//8//9// Rewrites legacy method calls to modern syntax.10//11//===----------------------------------------------------------------------===//12 13#include "clang/Edit/Rewriters.h"14#include "clang/AST/ASTContext.h"15#include "clang/AST/ExprCXX.h"16#include "clang/AST/ExprObjC.h"17#include "clang/AST/NSAPI.h"18#include "clang/AST/ParentMap.h"19#include "clang/Edit/Commit.h"20#include "clang/Lex/Lexer.h"21#include <optional>22 23using namespace clang;24using namespace edit;25 26static bool checkForLiteralCreation(const ObjCMessageExpr *Msg,27 IdentifierInfo *&ClassId,28 const LangOptions &LangOpts) {29 if (!Msg || Msg->isImplicit() || !Msg->getMethodDecl())30 return false;31 32 const ObjCInterfaceDecl *Receiver = Msg->getReceiverInterface();33 if (!Receiver)34 return false;35 ClassId = Receiver->getIdentifier();36 37 if (Msg->getReceiverKind() == ObjCMessageExpr::Class)38 return true;39 40 // When in ARC mode we also convert "[[.. alloc] init]" messages to literals,41 // since the change from +1 to +0 will be handled fine by ARC.42 if (LangOpts.ObjCAutoRefCount) {43 if (Msg->getReceiverKind() == ObjCMessageExpr::Instance) {44 if (const ObjCMessageExpr *Rec = dyn_cast<ObjCMessageExpr>(45 Msg->getInstanceReceiver()->IgnoreParenImpCasts())) {46 if (Rec->getMethodFamily() == OMF_alloc)47 return true;48 }49 }50 }51 52 return false;53}54 55//===----------------------------------------------------------------------===//56// rewriteObjCRedundantCallWithLiteral.57//===----------------------------------------------------------------------===//58 59bool edit::rewriteObjCRedundantCallWithLiteral(const ObjCMessageExpr *Msg,60 const NSAPI &NS, Commit &commit) {61 IdentifierInfo *II = nullptr;62 if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts()))63 return false;64 if (Msg->getNumArgs() != 1)65 return false;66 67 const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts();68 Selector Sel = Msg->getSelector();69 70 if ((isa<ObjCStringLiteral>(Arg) &&71 NS.getNSClassId(NSAPI::ClassId_NSString) == II &&72 (NS.getNSStringSelector(NSAPI::NSStr_stringWithString) == Sel ||73 NS.getNSStringSelector(NSAPI::NSStr_initWithString) == Sel)) ||74 75 (isa<ObjCArrayLiteral>(Arg) &&76 NS.getNSClassId(NSAPI::ClassId_NSArray) == II &&77 (NS.getNSArraySelector(NSAPI::NSArr_arrayWithArray) == Sel ||78 NS.getNSArraySelector(NSAPI::NSArr_initWithArray) == Sel)) ||79 80 (isa<ObjCDictionaryLiteral>(Arg) &&81 NS.getNSClassId(NSAPI::ClassId_NSDictionary) == II &&82 (NS.getNSDictionarySelector(83 NSAPI::NSDict_dictionaryWithDictionary) == Sel ||84 NS.getNSDictionarySelector(NSAPI::NSDict_initWithDictionary) == Sel))) {85 86 commit.replaceWithInner(Msg->getSourceRange(),87 Msg->getArg(0)->getSourceRange());88 return true;89 }90 91 return false;92}93 94//===----------------------------------------------------------------------===//95// rewriteToObjCSubscriptSyntax.96//===----------------------------------------------------------------------===//97 98/// Check for classes that accept 'objectForKey:' (or the other selectors99/// that the migrator handles) but return their instances as 'id', resulting100/// in the compiler resolving 'objectForKey:' as the method from NSDictionary.101///102/// When checking if we can convert to subscripting syntax, check whether103/// the receiver is a result of a class method from a hardcoded list of104/// such classes. In such a case return the specific class as the interface105/// of the receiver.106///107/// FIXME: Remove this when these classes start using 'instancetype'.108static const ObjCInterfaceDecl *109maybeAdjustInterfaceForSubscriptingCheck(const ObjCInterfaceDecl *IFace,110 const Expr *Receiver,111 ASTContext &Ctx) {112 assert(IFace && Receiver);113 114 // If the receiver has type 'id'...115 if (!Ctx.isObjCIdType(Receiver->getType().getUnqualifiedType()))116 return IFace;117 118 const ObjCMessageExpr *119 InnerMsg = dyn_cast<ObjCMessageExpr>(Receiver->IgnoreParenCasts());120 if (!InnerMsg)121 return IFace;122 123 QualType ClassRec;124 switch (InnerMsg->getReceiverKind()) {125 case ObjCMessageExpr::Instance:126 case ObjCMessageExpr::SuperInstance:127 return IFace;128 129 case ObjCMessageExpr::Class:130 ClassRec = InnerMsg->getClassReceiver();131 break;132 case ObjCMessageExpr::SuperClass:133 ClassRec = InnerMsg->getSuperType();134 break;135 }136 137 if (ClassRec.isNull())138 return IFace;139 140 // ...and it is the result of a class message...141 142 const ObjCObjectType *ObjTy = ClassRec->getAs<ObjCObjectType>();143 if (!ObjTy)144 return IFace;145 const ObjCInterfaceDecl *OID = ObjTy->getInterface();146 147 // ...and the receiving class is NSMapTable or NSLocale, return that148 // class as the receiving interface.149 if (OID->getName() == "NSMapTable" ||150 OID->getName() == "NSLocale")151 return OID;152 153 return IFace;154}155 156static bool canRewriteToSubscriptSyntax(const ObjCInterfaceDecl *&IFace,157 const ObjCMessageExpr *Msg,158 ASTContext &Ctx,159 Selector subscriptSel) {160 const Expr *Rec = Msg->getInstanceReceiver();161 if (!Rec)162 return false;163 IFace = maybeAdjustInterfaceForSubscriptingCheck(IFace, Rec, Ctx);164 165 if (const ObjCMethodDecl *MD = IFace->lookupInstanceMethod(subscriptSel)) {166 if (!MD->isUnavailable())167 return true;168 }169 return false;170}171 172static bool subscriptOperatorNeedsParens(const Expr *FullExpr);173 174static void maybePutParensOnReceiver(const Expr *Receiver, Commit &commit) {175 if (subscriptOperatorNeedsParens(Receiver)) {176 SourceRange RecRange = Receiver->getSourceRange();177 commit.insertWrap("(", RecRange, ")");178 }179}180 181static bool rewriteToSubscriptGetCommon(const ObjCMessageExpr *Msg,182 Commit &commit) {183 if (Msg->getNumArgs() != 1)184 return false;185 const Expr *Rec = Msg->getInstanceReceiver();186 if (!Rec)187 return false;188 189 SourceRange MsgRange = Msg->getSourceRange();190 SourceRange RecRange = Rec->getSourceRange();191 SourceRange ArgRange = Msg->getArg(0)->getSourceRange();192 193 commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(),194 ArgRange.getBegin()),195 CharSourceRange::getTokenRange(RecRange));196 commit.replaceWithInner(SourceRange(ArgRange.getBegin(), MsgRange.getEnd()),197 ArgRange);198 commit.insertWrap("[", ArgRange, "]");199 maybePutParensOnReceiver(Rec, commit);200 return true;201}202 203static bool rewriteToArraySubscriptGet(const ObjCInterfaceDecl *IFace,204 const ObjCMessageExpr *Msg,205 const NSAPI &NS,206 Commit &commit) {207 if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(),208 NS.getObjectAtIndexedSubscriptSelector()))209 return false;210 return rewriteToSubscriptGetCommon(Msg, commit);211}212 213static bool rewriteToDictionarySubscriptGet(const ObjCInterfaceDecl *IFace,214 const ObjCMessageExpr *Msg,215 const NSAPI &NS,216 Commit &commit) {217 if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(),218 NS.getObjectForKeyedSubscriptSelector()))219 return false;220 return rewriteToSubscriptGetCommon(Msg, commit);221}222 223static bool rewriteToArraySubscriptSet(const ObjCInterfaceDecl *IFace,224 const ObjCMessageExpr *Msg,225 const NSAPI &NS,226 Commit &commit) {227 if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(),228 NS.getSetObjectAtIndexedSubscriptSelector()))229 return false;230 231 if (Msg->getNumArgs() != 2)232 return false;233 const Expr *Rec = Msg->getInstanceReceiver();234 if (!Rec)235 return false;236 237 SourceRange MsgRange = Msg->getSourceRange();238 SourceRange RecRange = Rec->getSourceRange();239 SourceRange Arg0Range = Msg->getArg(0)->getSourceRange();240 SourceRange Arg1Range = Msg->getArg(1)->getSourceRange();241 242 commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(),243 Arg0Range.getBegin()),244 CharSourceRange::getTokenRange(RecRange));245 commit.replaceWithInner(CharSourceRange::getCharRange(Arg0Range.getBegin(),246 Arg1Range.getBegin()),247 CharSourceRange::getTokenRange(Arg0Range));248 commit.replaceWithInner(SourceRange(Arg1Range.getBegin(), MsgRange.getEnd()),249 Arg1Range);250 commit.insertWrap("[", CharSourceRange::getCharRange(Arg0Range.getBegin(),251 Arg1Range.getBegin()),252 "] = ");253 maybePutParensOnReceiver(Rec, commit);254 return true;255}256 257static bool rewriteToDictionarySubscriptSet(const ObjCInterfaceDecl *IFace,258 const ObjCMessageExpr *Msg,259 const NSAPI &NS,260 Commit &commit) {261 if (!canRewriteToSubscriptSyntax(IFace, Msg, NS.getASTContext(),262 NS.getSetObjectForKeyedSubscriptSelector()))263 return false;264 265 if (Msg->getNumArgs() != 2)266 return false;267 const Expr *Rec = Msg->getInstanceReceiver();268 if (!Rec)269 return false;270 271 SourceRange MsgRange = Msg->getSourceRange();272 SourceRange RecRange = Rec->getSourceRange();273 SourceRange Arg0Range = Msg->getArg(0)->getSourceRange();274 SourceRange Arg1Range = Msg->getArg(1)->getSourceRange();275 276 SourceLocation LocBeforeVal = Arg0Range.getBegin();277 commit.insertBefore(LocBeforeVal, "] = ");278 commit.insertFromRange(LocBeforeVal, Arg1Range, /*afterToken=*/false,279 /*beforePreviousInsertions=*/true);280 commit.insertBefore(LocBeforeVal, "[");281 commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(),282 Arg0Range.getBegin()),283 CharSourceRange::getTokenRange(RecRange));284 commit.replaceWithInner(SourceRange(Arg0Range.getBegin(), MsgRange.getEnd()),285 Arg0Range);286 maybePutParensOnReceiver(Rec, commit);287 return true;288}289 290bool edit::rewriteToObjCSubscriptSyntax(const ObjCMessageExpr *Msg,291 const NSAPI &NS, Commit &commit) {292 if (!Msg || Msg->isImplicit() ||293 Msg->getReceiverKind() != ObjCMessageExpr::Instance)294 return false;295 const ObjCMethodDecl *Method = Msg->getMethodDecl();296 if (!Method)297 return false;298 299 const ObjCInterfaceDecl *IFace =300 NS.getASTContext().getObjContainingInterface(Method);301 if (!IFace)302 return false;303 Selector Sel = Msg->getSelector();304 305 if (Sel == NS.getNSArraySelector(NSAPI::NSArr_objectAtIndex))306 return rewriteToArraySubscriptGet(IFace, Msg, NS, commit);307 308 if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_objectForKey))309 return rewriteToDictionarySubscriptGet(IFace, Msg, NS, commit);310 311 if (Msg->getNumArgs() != 2)312 return false;313 314 if (Sel == NS.getNSArraySelector(NSAPI::NSMutableArr_replaceObjectAtIndex))315 return rewriteToArraySubscriptSet(IFace, Msg, NS, commit);316 317 if (Sel == NS.getNSDictionarySelector(NSAPI::NSMutableDict_setObjectForKey))318 return rewriteToDictionarySubscriptSet(IFace, Msg, NS, commit);319 320 return false;321}322 323//===----------------------------------------------------------------------===//324// rewriteToObjCLiteralSyntax.325//===----------------------------------------------------------------------===//326 327static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg,328 const NSAPI &NS, Commit &commit,329 const ParentMap *PMap);330static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg,331 const NSAPI &NS, Commit &commit);332static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg,333 const NSAPI &NS, Commit &commit);334static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg,335 const NSAPI &NS, Commit &commit);336static bool rewriteToStringBoxedExpression(const ObjCMessageExpr *Msg,337 const NSAPI &NS, Commit &commit);338 339bool edit::rewriteToObjCLiteralSyntax(const ObjCMessageExpr *Msg,340 const NSAPI &NS, Commit &commit,341 const ParentMap *PMap) {342 IdentifierInfo *II = nullptr;343 if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts()))344 return false;345 346 if (II == NS.getNSClassId(NSAPI::ClassId_NSArray))347 return rewriteToArrayLiteral(Msg, NS, commit, PMap);348 if (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary))349 return rewriteToDictionaryLiteral(Msg, NS, commit);350 if (II == NS.getNSClassId(NSAPI::ClassId_NSNumber))351 return rewriteToNumberLiteral(Msg, NS, commit);352 if (II == NS.getNSClassId(NSAPI::ClassId_NSString))353 return rewriteToStringBoxedExpression(Msg, NS, commit);354 355 return false;356}357 358/// Returns true if the immediate message arguments of \c Msg should not359/// be rewritten because it will interfere with the rewrite of the parent360/// message expression. e.g.361/// \code362/// [NSDictionary dictionaryWithObjects:363/// [NSArray arrayWithObjects:@"1", @"2", nil]364/// forKeys:[NSArray arrayWithObjects:@"A", @"B", nil]];365/// \endcode366/// It will return true for this because we are going to rewrite this directly367/// to a dictionary literal without any array literals.368static bool shouldNotRewriteImmediateMessageArgs(const ObjCMessageExpr *Msg,369 const NSAPI &NS);370 371//===----------------------------------------------------------------------===//372// rewriteToArrayLiteral.373//===----------------------------------------------------------------------===//374 375/// Adds an explicit cast to 'id' if the type is not objc object.376static void objectifyExpr(const Expr *E, Commit &commit);377 378static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg,379 const NSAPI &NS, Commit &commit,380 const ParentMap *PMap) {381 if (PMap) {382 const ObjCMessageExpr *ParentMsg =383 dyn_cast_or_null<ObjCMessageExpr>(PMap->getParentIgnoreParenCasts(Msg));384 if (shouldNotRewriteImmediateMessageArgs(ParentMsg, NS))385 return false;386 }387 388 Selector Sel = Msg->getSelector();389 SourceRange MsgRange = Msg->getSourceRange();390 391 if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array)) {392 if (Msg->getNumArgs() != 0)393 return false;394 commit.replace(MsgRange, "@[]");395 return true;396 }397 398 if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) {399 if (Msg->getNumArgs() != 1)400 return false;401 objectifyExpr(Msg->getArg(0), commit);402 SourceRange ArgRange = Msg->getArg(0)->getSourceRange();403 commit.replaceWithInner(MsgRange, ArgRange);404 commit.insertWrap("@[", ArgRange, "]");405 return true;406 }407 408 if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects) ||409 Sel == NS.getNSArraySelector(NSAPI::NSArr_initWithObjects)) {410 if (Msg->getNumArgs() == 0)411 return false;412 const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1);413 if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr))414 return false;415 416 for (unsigned i = 0, e = Msg->getNumArgs() - 1; i != e; ++i)417 objectifyExpr(Msg->getArg(i), commit);418 419 if (Msg->getNumArgs() == 1) {420 commit.replace(MsgRange, "@[]");421 return true;422 }423 SourceRange ArgRange(Msg->getArg(0)->getBeginLoc(),424 Msg->getArg(Msg->getNumArgs() - 2)->getEndLoc());425 commit.replaceWithInner(MsgRange, ArgRange);426 commit.insertWrap("@[", ArgRange, "]");427 return true;428 }429 430 return false;431}432 433//===----------------------------------------------------------------------===//434// rewriteToDictionaryLiteral.435//===----------------------------------------------------------------------===//436 437/// If \c Msg is an NSArray creation message or literal, this gets the438/// objects that were used to create it.439/// \returns true if it is an NSArray and we got objects, or false otherwise.440static bool getNSArrayObjects(const Expr *E, const NSAPI &NS,441 SmallVectorImpl<const Expr *> &Objs) {442 if (!E)443 return false;444 445 E = E->IgnoreParenCasts();446 if (!E)447 return false;448 449 if (const ObjCMessageExpr *Msg = dyn_cast<ObjCMessageExpr>(E)) {450 IdentifierInfo *Cls = nullptr;451 if (!checkForLiteralCreation(Msg, Cls, NS.getASTContext().getLangOpts()))452 return false;453 454 if (Cls != NS.getNSClassId(NSAPI::ClassId_NSArray))455 return false;456 457 Selector Sel = Msg->getSelector();458 if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array))459 return true; // empty array.460 461 if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) {462 if (Msg->getNumArgs() != 1)463 return false;464 Objs.push_back(Msg->getArg(0));465 return true;466 }467 468 if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects) ||469 Sel == NS.getNSArraySelector(NSAPI::NSArr_initWithObjects)) {470 if (Msg->getNumArgs() == 0)471 return false;472 const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1);473 if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr))474 return false;475 476 for (unsigned i = 0, e = Msg->getNumArgs() - 1; i != e; ++i)477 Objs.push_back(Msg->getArg(i));478 return true;479 }480 481 } else if (const ObjCArrayLiteral *ArrLit = dyn_cast<ObjCArrayLiteral>(E)) {482 for (unsigned i = 0, e = ArrLit->getNumElements(); i != e; ++i)483 Objs.push_back(ArrLit->getElement(i));484 return true;485 }486 487 return false;488}489 490static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg,491 const NSAPI &NS, Commit &commit) {492 Selector Sel = Msg->getSelector();493 SourceRange MsgRange = Msg->getSourceRange();494 495 if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_dictionary)) {496 if (Msg->getNumArgs() != 0)497 return false;498 commit.replace(MsgRange, "@{}");499 return true;500 }501 502 if (Sel == NS.getNSDictionarySelector(503 NSAPI::NSDict_dictionaryWithObjectForKey)) {504 if (Msg->getNumArgs() != 2)505 return false;506 507 objectifyExpr(Msg->getArg(0), commit);508 objectifyExpr(Msg->getArg(1), commit);509 510 SourceRange ValRange = Msg->getArg(0)->getSourceRange();511 SourceRange KeyRange = Msg->getArg(1)->getSourceRange();512 // Insert key before the value.513 commit.insertBefore(ValRange.getBegin(), ": ");514 commit.insertFromRange(ValRange.getBegin(),515 CharSourceRange::getTokenRange(KeyRange),516 /*afterToken=*/false, /*beforePreviousInsertions=*/true);517 commit.insertBefore(ValRange.getBegin(), "@{");518 commit.insertAfterToken(ValRange.getEnd(), "}");519 commit.replaceWithInner(MsgRange, ValRange);520 return true;521 }522 523 if (Sel == NS.getNSDictionarySelector(524 NSAPI::NSDict_dictionaryWithObjectsAndKeys) ||525 Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsAndKeys)) {526 if (Msg->getNumArgs() % 2 != 1)527 return false;528 unsigned SentinelIdx = Msg->getNumArgs() - 1;529 const Expr *SentinelExpr = Msg->getArg(SentinelIdx);530 if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr))531 return false;532 533 if (Msg->getNumArgs() == 1) {534 commit.replace(MsgRange, "@{}");535 return true;536 }537 538 for (unsigned i = 0; i < SentinelIdx; i += 2) {539 objectifyExpr(Msg->getArg(i), commit);540 objectifyExpr(Msg->getArg(i+1), commit);541 542 SourceRange ValRange = Msg->getArg(i)->getSourceRange();543 SourceRange KeyRange = Msg->getArg(i+1)->getSourceRange();544 // Insert value after key.545 commit.insertAfterToken(KeyRange.getEnd(), ": ");546 commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true);547 commit.remove(CharSourceRange::getCharRange(ValRange.getBegin(),548 KeyRange.getBegin()));549 }550 // Range of arguments up until and including the last key.551 // The sentinel and first value are cut off, the value will move after the552 // key.553 SourceRange ArgRange(Msg->getArg(1)->getBeginLoc(),554 Msg->getArg(SentinelIdx - 1)->getEndLoc());555 commit.insertWrap("@{", ArgRange, "}");556 commit.replaceWithInner(MsgRange, ArgRange);557 return true;558 }559 560 if (Sel == NS.getNSDictionarySelector(561 NSAPI::NSDict_dictionaryWithObjectsForKeys) ||562 Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsForKeys)) {563 if (Msg->getNumArgs() != 2)564 return false;565 566 SmallVector<const Expr *, 8> Vals;567 if (!getNSArrayObjects(Msg->getArg(0), NS, Vals))568 return false;569 570 SmallVector<const Expr *, 8> Keys;571 if (!getNSArrayObjects(Msg->getArg(1), NS, Keys))572 return false;573 574 if (Vals.size() != Keys.size())575 return false;576 577 if (Vals.empty()) {578 commit.replace(MsgRange, "@{}");579 return true;580 }581 582 for (unsigned i = 0, n = Vals.size(); i < n; ++i) {583 objectifyExpr(Vals[i], commit);584 objectifyExpr(Keys[i], commit);585 586 SourceRange ValRange = Vals[i]->getSourceRange();587 SourceRange KeyRange = Keys[i]->getSourceRange();588 // Insert value after key.589 commit.insertAfterToken(KeyRange.getEnd(), ": ");590 commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true);591 }592 // Range of arguments up until and including the last key.593 // The first value is cut off, the value will move after the key.594 SourceRange ArgRange(Keys.front()->getBeginLoc(), Keys.back()->getEndLoc());595 commit.insertWrap("@{", ArgRange, "}");596 commit.replaceWithInner(MsgRange, ArgRange);597 return true;598 }599 600 return false;601}602 603static bool shouldNotRewriteImmediateMessageArgs(const ObjCMessageExpr *Msg,604 const NSAPI &NS) {605 if (!Msg)606 return false;607 608 IdentifierInfo *II = nullptr;609 if (!checkForLiteralCreation(Msg, II, NS.getASTContext().getLangOpts()))610 return false;611 612 if (II != NS.getNSClassId(NSAPI::ClassId_NSDictionary))613 return false;614 615 Selector Sel = Msg->getSelector();616 if (Sel == NS.getNSDictionarySelector(617 NSAPI::NSDict_dictionaryWithObjectsForKeys) ||618 Sel == NS.getNSDictionarySelector(NSAPI::NSDict_initWithObjectsForKeys)) {619 if (Msg->getNumArgs() != 2)620 return false;621 622 SmallVector<const Expr *, 8> Vals;623 if (!getNSArrayObjects(Msg->getArg(0), NS, Vals))624 return false;625 626 SmallVector<const Expr *, 8> Keys;627 if (!getNSArrayObjects(Msg->getArg(1), NS, Keys))628 return false;629 630 if (Vals.size() != Keys.size())631 return false;632 633 return true;634 }635 636 return false;637}638 639//===----------------------------------------------------------------------===//640// rewriteToNumberLiteral.641//===----------------------------------------------------------------------===//642 643static bool rewriteToCharLiteral(const ObjCMessageExpr *Msg,644 const CharacterLiteral *Arg,645 const NSAPI &NS, Commit &commit) {646 if (Arg->getKind() != CharacterLiteralKind::Ascii)647 return false;648 if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithChar,649 Msg->getSelector())) {650 SourceRange ArgRange = Arg->getSourceRange();651 commit.replaceWithInner(Msg->getSourceRange(), ArgRange);652 commit.insert(ArgRange.getBegin(), "@");653 return true;654 }655 656 return rewriteToNumericBoxedExpression(Msg, NS, commit);657}658 659static bool rewriteToBoolLiteral(const ObjCMessageExpr *Msg,660 const Expr *Arg,661 const NSAPI &NS, Commit &commit) {662 if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithBool,663 Msg->getSelector())) {664 SourceRange ArgRange = Arg->getSourceRange();665 commit.replaceWithInner(Msg->getSourceRange(), ArgRange);666 commit.insert(ArgRange.getBegin(), "@");667 return true;668 }669 670 return rewriteToNumericBoxedExpression(Msg, NS, commit);671}672 673namespace {674 675struct LiteralInfo {676 bool Hex, Octal;677 StringRef U, F, L, LL;678 CharSourceRange WithoutSuffRange;679};680 681}682 683static bool getLiteralInfo(SourceRange literalRange,684 bool isFloat, bool isIntZero,685 ASTContext &Ctx, LiteralInfo &Info) {686 if (literalRange.getBegin().isMacroID() ||687 literalRange.getEnd().isMacroID())688 return false;689 StringRef text = Lexer::getSourceText(690 CharSourceRange::getTokenRange(literalRange),691 Ctx.getSourceManager(), Ctx.getLangOpts());692 if (text.empty())693 return false;694 695 std::optional<bool> UpperU, UpperL;696 bool UpperF = false;697 698 struct Suff {699 static bool has(StringRef suff, StringRef &text) {700 return text.consume_back(suff);701 }702 };703 704 while (true) {705 if (Suff::has("u", text)) {706 UpperU = false;707 } else if (Suff::has("U", text)) {708 UpperU = true;709 } else if (Suff::has("ll", text)) {710 UpperL = false;711 } else if (Suff::has("LL", text)) {712 UpperL = true;713 } else if (Suff::has("l", text)) {714 UpperL = false;715 } else if (Suff::has("L", text)) {716 UpperL = true;717 } else if (isFloat && Suff::has("f", text)) {718 UpperF = false;719 } else if (isFloat && Suff::has("F", text)) {720 UpperF = true;721 } else722 break;723 }724 725 if (!UpperU && !UpperL)726 UpperU = UpperL = true;727 else if (UpperU && !UpperL)728 UpperL = UpperU;729 else if (UpperL && !UpperU)730 UpperU = UpperL;731 732 Info.U = *UpperU ? "U" : "u";733 Info.L = *UpperL ? "L" : "l";734 Info.LL = *UpperL ? "LL" : "ll";735 Info.F = UpperF ? "F" : "f";736 737 Info.Hex = Info.Octal = false;738 if (text.starts_with("0x"))739 Info.Hex = true;740 else if (!isFloat && !isIntZero && text.starts_with("0"))741 Info.Octal = true;742 743 SourceLocation B = literalRange.getBegin();744 Info.WithoutSuffRange =745 CharSourceRange::getCharRange(B, B.getLocWithOffset(text.size()));746 return true;747}748 749static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg,750 const NSAPI &NS, Commit &commit) {751 if (Msg->getNumArgs() != 1)752 return false;753 754 const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts();755 if (const CharacterLiteral *CharE = dyn_cast<CharacterLiteral>(Arg))756 return rewriteToCharLiteral(Msg, CharE, NS, commit);757 if (const ObjCBoolLiteralExpr *BE = dyn_cast<ObjCBoolLiteralExpr>(Arg))758 return rewriteToBoolLiteral(Msg, BE, NS, commit);759 if (const CXXBoolLiteralExpr *BE = dyn_cast<CXXBoolLiteralExpr>(Arg))760 return rewriteToBoolLiteral(Msg, BE, NS, commit);761 762 const Expr *literalE = Arg;763 if (const UnaryOperator *UOE = dyn_cast<UnaryOperator>(literalE)) {764 if (UOE->getOpcode() == UO_Plus || UOE->getOpcode() == UO_Minus)765 literalE = UOE->getSubExpr();766 }767 768 // Only integer and floating literals, otherwise try to rewrite to boxed769 // expression.770 if (!isa<IntegerLiteral>(literalE) && !isa<FloatingLiteral>(literalE))771 return rewriteToNumericBoxedExpression(Msg, NS, commit);772 773 ASTContext &Ctx = NS.getASTContext();774 Selector Sel = Msg->getSelector();775 std::optional<NSAPI::NSNumberLiteralMethodKind> MKOpt =776 NS.getNSNumberLiteralMethodKind(Sel);777 if (!MKOpt)778 return false;779 NSAPI::NSNumberLiteralMethodKind MK = *MKOpt;780 781 bool CallIsUnsigned = false, CallIsLong = false, CallIsLongLong = false;782 bool CallIsFloating = false, CallIsDouble = false;783 784 switch (MK) {785 // We cannot have these calls with int/float literals.786 case NSAPI::NSNumberWithChar:787 case NSAPI::NSNumberWithUnsignedChar:788 case NSAPI::NSNumberWithShort:789 case NSAPI::NSNumberWithUnsignedShort:790 case NSAPI::NSNumberWithBool:791 return rewriteToNumericBoxedExpression(Msg, NS, commit);792 793 case NSAPI::NSNumberWithUnsignedInt:794 case NSAPI::NSNumberWithUnsignedInteger:795 CallIsUnsigned = true;796 [[fallthrough]];797 case NSAPI::NSNumberWithInt:798 case NSAPI::NSNumberWithInteger:799 break;800 801 case NSAPI::NSNumberWithUnsignedLong:802 CallIsUnsigned = true;803 [[fallthrough]];804 case NSAPI::NSNumberWithLong:805 CallIsLong = true;806 break;807 808 case NSAPI::NSNumberWithUnsignedLongLong:809 CallIsUnsigned = true;810 [[fallthrough]];811 case NSAPI::NSNumberWithLongLong:812 CallIsLongLong = true;813 break;814 815 case NSAPI::NSNumberWithDouble:816 CallIsDouble = true;817 [[fallthrough]];818 case NSAPI::NSNumberWithFloat:819 CallIsFloating = true;820 break;821 }822 823 SourceRange ArgRange = Arg->getSourceRange();824 QualType ArgTy = Arg->getType();825 QualType CallTy = Msg->getArg(0)->getType();826 827 // Check for the easy case, the literal maps directly to the call.828 if (Ctx.hasSameType(ArgTy, CallTy)) {829 commit.replaceWithInner(Msg->getSourceRange(), ArgRange);830 commit.insert(ArgRange.getBegin(), "@");831 return true;832 }833 834 // We will need to modify the literal suffix to get the same type as the call.835 // Try with boxed expression if it came from a macro.836 if (ArgRange.getBegin().isMacroID())837 return rewriteToNumericBoxedExpression(Msg, NS, commit);838 839 bool LitIsFloat = ArgTy->isFloatingType();840 // For a float passed to integer call, don't try rewriting to objc literal.841 // It is difficult and a very uncommon case anyway.842 // But try with boxed expression.843 if (LitIsFloat && !CallIsFloating)844 return rewriteToNumericBoxedExpression(Msg, NS, commit);845 846 // Try to modify the literal make it the same type as the method call.847 // -Modify the suffix, and/or848 // -Change integer to float849 850 LiteralInfo LitInfo;851 bool isIntZero = false;852 if (const IntegerLiteral *IntE = dyn_cast<IntegerLiteral>(literalE))853 isIntZero = !IntE->getValue().getBoolValue();854 if (!getLiteralInfo(ArgRange, LitIsFloat, isIntZero, Ctx, LitInfo))855 return rewriteToNumericBoxedExpression(Msg, NS, commit);856 857 // Not easy to do int -> float with hex/octal and uncommon anyway.858 if (!LitIsFloat && CallIsFloating && (LitInfo.Hex || LitInfo.Octal))859 return rewriteToNumericBoxedExpression(Msg, NS, commit);860 861 SourceLocation LitB = LitInfo.WithoutSuffRange.getBegin();862 SourceLocation LitE = LitInfo.WithoutSuffRange.getEnd();863 864 commit.replaceWithInner(CharSourceRange::getTokenRange(Msg->getSourceRange()),865 LitInfo.WithoutSuffRange);866 commit.insert(LitB, "@");867 868 if (!LitIsFloat && CallIsFloating)869 commit.insert(LitE, ".0");870 871 if (CallIsFloating) {872 if (!CallIsDouble)873 commit.insert(LitE, LitInfo.F);874 } else {875 if (CallIsUnsigned)876 commit.insert(LitE, LitInfo.U);877 878 if (CallIsLong)879 commit.insert(LitE, LitInfo.L);880 else if (CallIsLongLong)881 commit.insert(LitE, LitInfo.LL);882 }883 return true;884}885 886// FIXME: Make determination of operator precedence more general and887// make it broadly available.888static bool subscriptOperatorNeedsParens(const Expr *FullExpr) {889 const Expr* Expr = FullExpr->IgnoreImpCasts();890 if (isa<ArraySubscriptExpr>(Expr) ||891 isa<CallExpr>(Expr) ||892 isa<DeclRefExpr>(Expr) ||893 isa<CXXNamedCastExpr>(Expr) ||894 isa<CXXConstructExpr>(Expr) ||895 isa<CXXThisExpr>(Expr) ||896 isa<CXXTypeidExpr>(Expr) ||897 isa<CXXUnresolvedConstructExpr>(Expr) ||898 isa<ObjCMessageExpr>(Expr) ||899 isa<ObjCPropertyRefExpr>(Expr) ||900 isa<ObjCProtocolExpr>(Expr) ||901 isa<MemberExpr>(Expr) ||902 isa<ObjCIvarRefExpr>(Expr) ||903 isa<ParenExpr>(FullExpr) ||904 isa<ParenListExpr>(Expr) ||905 isa<SizeOfPackExpr>(Expr))906 return false;907 908 return true;909}910static bool castOperatorNeedsParens(const Expr *FullExpr) {911 const Expr* Expr = FullExpr->IgnoreImpCasts();912 if (isa<ArraySubscriptExpr>(Expr) ||913 isa<CallExpr>(Expr) ||914 isa<DeclRefExpr>(Expr) ||915 isa<CastExpr>(Expr) ||916 isa<CXXNewExpr>(Expr) ||917 isa<CXXConstructExpr>(Expr) ||918 isa<CXXDeleteExpr>(Expr) ||919 isa<CXXNoexceptExpr>(Expr) ||920 isa<CXXPseudoDestructorExpr>(Expr) ||921 isa<CXXScalarValueInitExpr>(Expr) ||922 isa<CXXThisExpr>(Expr) ||923 isa<CXXTypeidExpr>(Expr) ||924 isa<CXXUnresolvedConstructExpr>(Expr) ||925 isa<ObjCMessageExpr>(Expr) ||926 isa<ObjCPropertyRefExpr>(Expr) ||927 isa<ObjCProtocolExpr>(Expr) ||928 isa<MemberExpr>(Expr) ||929 isa<ObjCIvarRefExpr>(Expr) ||930 isa<ParenExpr>(FullExpr) ||931 isa<ParenListExpr>(Expr) ||932 isa<SizeOfPackExpr>(Expr) ||933 isa<UnaryOperator>(Expr))934 return false;935 936 return true;937}938 939static void objectifyExpr(const Expr *E, Commit &commit) {940 if (!E) return;941 942 QualType T = E->getType();943 if (T->isObjCObjectPointerType()) {944 if (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(E)) {945 if (ICE->getCastKind() != CK_CPointerToObjCPointerCast)946 return;947 } else {948 return;949 }950 } else if (!T->isPointerType()) {951 return;952 }953 954 SourceRange Range = E->getSourceRange();955 if (castOperatorNeedsParens(E))956 commit.insertWrap("(", Range, ")");957 commit.insertBefore(Range.getBegin(), "(id)");958}959 960//===----------------------------------------------------------------------===//961// rewriteToNumericBoxedExpression.962//===----------------------------------------------------------------------===//963 964static bool isEnumConstant(const Expr *E) {965 if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParenImpCasts()))966 if (const ValueDecl *VD = DRE->getDecl())967 return isa<EnumConstantDecl>(VD);968 969 return false;970}971 972static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg,973 const NSAPI &NS, Commit &commit) {974 if (Msg->getNumArgs() != 1)975 return false;976 977 const Expr *Arg = Msg->getArg(0);978 if (Arg->isTypeDependent())979 return false;980 981 ASTContext &Ctx = NS.getASTContext();982 Selector Sel = Msg->getSelector();983 std::optional<NSAPI::NSNumberLiteralMethodKind> MKOpt =984 NS.getNSNumberLiteralMethodKind(Sel);985 if (!MKOpt)986 return false;987 NSAPI::NSNumberLiteralMethodKind MK = *MKOpt;988 989 const Expr *OrigArg = Arg->IgnoreImpCasts();990 QualType FinalTy = Arg->getType();991 QualType OrigTy = OrigArg->getType();992 uint64_t FinalTySize = Ctx.getTypeSize(FinalTy);993 uint64_t OrigTySize = Ctx.getTypeSize(OrigTy);994 995 bool isTruncated = FinalTySize < OrigTySize;996 bool needsCast = false;997 998 if (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(Arg)) {999 switch (ICE->getCastKind()) {1000 case CK_LValueToRValue:1001 case CK_NoOp:1002 case CK_UserDefinedConversion:1003 case CK_HLSLArrayRValue:1004 break;1005 1006 case CK_IntegralCast: {1007 if (MK == NSAPI::NSNumberWithBool && OrigTy->isBooleanType())1008 break;1009 // Be more liberal with Integer/UnsignedInteger which are very commonly1010 // used.1011 if ((MK == NSAPI::NSNumberWithInteger ||1012 MK == NSAPI::NSNumberWithUnsignedInteger) &&1013 !isTruncated) {1014 if (OrigTy->isEnumeralType() || isEnumConstant(OrigArg))1015 break;1016 if ((MK==NSAPI::NSNumberWithInteger) == OrigTy->isSignedIntegerType() &&1017 OrigTySize >= Ctx.getTypeSize(Ctx.IntTy))1018 break;1019 }1020 1021 needsCast = true;1022 break;1023 }1024 1025 case CK_PointerToBoolean:1026 case CK_IntegralToBoolean:1027 case CK_IntegralToFloating:1028 case CK_FloatingToIntegral:1029 case CK_FloatingToBoolean:1030 case CK_FloatingCast:1031 case CK_FloatingComplexToReal:1032 case CK_FloatingComplexToBoolean:1033 case CK_IntegralComplexToReal:1034 case CK_IntegralComplexToBoolean:1035 case CK_AtomicToNonAtomic:1036 case CK_AddressSpaceConversion:1037 needsCast = true;1038 break;1039 1040 case CK_Dependent:1041 case CK_BitCast:1042 case CK_LValueBitCast:1043 case CK_LValueToRValueBitCast:1044 case CK_BaseToDerived:1045 case CK_DerivedToBase:1046 case CK_UncheckedDerivedToBase:1047 case CK_Dynamic:1048 case CK_ToUnion:1049 case CK_ArrayToPointerDecay:1050 case CK_FunctionToPointerDecay:1051 case CK_NullToPointer:1052 case CK_NullToMemberPointer:1053 case CK_BaseToDerivedMemberPointer:1054 case CK_DerivedToBaseMemberPointer:1055 case CK_MemberPointerToBoolean:1056 case CK_ReinterpretMemberPointer:1057 case CK_ConstructorConversion:1058 case CK_IntegralToPointer:1059 case CK_PointerToIntegral:1060 case CK_ToVoid:1061 case CK_VectorSplat:1062 case CK_CPointerToObjCPointerCast:1063 case CK_BlockPointerToObjCPointerCast:1064 case CK_AnyPointerToBlockPointerCast:1065 case CK_ObjCObjectLValueCast:1066 case CK_FloatingRealToComplex:1067 case CK_FloatingComplexCast:1068 case CK_FloatingComplexToIntegralComplex:1069 case CK_IntegralRealToComplex:1070 case CK_IntegralComplexCast:1071 case CK_IntegralComplexToFloatingComplex:1072 case CK_ARCProduceObject:1073 case CK_ARCConsumeObject:1074 case CK_ARCReclaimReturnedObject:1075 case CK_ARCExtendBlockObject:1076 case CK_NonAtomicToAtomic:1077 case CK_CopyAndAutoreleaseBlockObject:1078 case CK_BuiltinFnToFnPtr:1079 case CK_ZeroToOCLOpaqueType:1080 case CK_IntToOCLSampler:1081 case CK_MatrixCast:1082 return false;1083 1084 case CK_BooleanToSignedIntegral:1085 llvm_unreachable("OpenCL-specific cast in Objective-C?");1086 1087 case CK_HLSLVectorTruncation:1088 case CK_HLSLElementwiseCast:1089 case CK_HLSLAggregateSplatCast:1090 llvm_unreachable("HLSL-specific cast in Objective-C?");1091 break;1092 1093 case CK_FloatingToFixedPoint:1094 case CK_FixedPointToFloating:1095 case CK_FixedPointCast:1096 case CK_FixedPointToBoolean:1097 case CK_FixedPointToIntegral:1098 case CK_IntegralToFixedPoint:1099 llvm_unreachable("Fixed point types are disabled for Objective-C");1100 }1101 }1102 1103 if (needsCast) {1104 DiagnosticsEngine &Diags = Ctx.getDiagnostics();1105 // FIXME: Use a custom category name to distinguish migration diagnostics.1106 unsigned diagID = Diags.getCustomDiagID(DiagnosticsEngine::Warning,1107 "converting to boxing syntax requires casting %0 to %1");1108 Diags.Report(Msg->getExprLoc(), diagID) << OrigTy << FinalTy1109 << Msg->getSourceRange();1110 return false;1111 }1112 1113 SourceRange ArgRange = OrigArg->getSourceRange();1114 commit.replaceWithInner(Msg->getSourceRange(), ArgRange);1115 1116 if (isa<ParenExpr>(OrigArg) || isa<IntegerLiteral>(OrigArg))1117 commit.insertBefore(ArgRange.getBegin(), "@");1118 else1119 commit.insertWrap("@(", ArgRange, ")");1120 1121 return true;1122}1123 1124//===----------------------------------------------------------------------===//1125// rewriteToStringBoxedExpression.1126//===----------------------------------------------------------------------===//1127 1128static bool doRewriteToUTF8StringBoxedExpressionHelper(1129 const ObjCMessageExpr *Msg,1130 const NSAPI &NS, Commit &commit) {1131 const Expr *Arg = Msg->getArg(0);1132 if (Arg->isTypeDependent())1133 return false;1134 1135 ASTContext &Ctx = NS.getASTContext();1136 1137 const Expr *OrigArg = Arg->IgnoreImpCasts();1138 QualType OrigTy = OrigArg->getType();1139 if (OrigTy->isArrayType())1140 OrigTy = Ctx.getArrayDecayedType(OrigTy);1141 1142 if (const StringLiteral *1143 StrE = dyn_cast<StringLiteral>(OrigArg->IgnoreParens())) {1144 commit.replaceWithInner(Msg->getSourceRange(), StrE->getSourceRange());1145 commit.insert(StrE->getBeginLoc(), "@");1146 return true;1147 }1148 1149 if (const PointerType *PT = OrigTy->getAs<PointerType>()) {1150 QualType PointeeType = PT->getPointeeType();1151 if (Ctx.hasSameUnqualifiedType(PointeeType, Ctx.CharTy)) {1152 SourceRange ArgRange = OrigArg->getSourceRange();1153 commit.replaceWithInner(Msg->getSourceRange(), ArgRange);1154 1155 if (isa<ParenExpr>(OrigArg) || isa<IntegerLiteral>(OrigArg))1156 commit.insertBefore(ArgRange.getBegin(), "@");1157 else1158 commit.insertWrap("@(", ArgRange, ")");1159 1160 return true;1161 }1162 }1163 1164 return false;1165}1166 1167static bool rewriteToStringBoxedExpression(const ObjCMessageExpr *Msg,1168 const NSAPI &NS, Commit &commit) {1169 Selector Sel = Msg->getSelector();1170 1171 if (Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithUTF8String) ||1172 Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithCString) ||1173 Sel == NS.getNSStringSelector(NSAPI::NSStr_initWithUTF8String)) {1174 if (Msg->getNumArgs() != 1)1175 return false;1176 return doRewriteToUTF8StringBoxedExpressionHelper(Msg, NS, commit);1177 }1178 1179 if (Sel == NS.getNSStringSelector(NSAPI::NSStr_stringWithCStringEncoding)) {1180 if (Msg->getNumArgs() != 2)1181 return false;1182 1183 const Expr *encodingArg = Msg->getArg(1);1184 if (NS.isNSUTF8StringEncodingConstant(encodingArg) ||1185 NS.isNSASCIIStringEncodingConstant(encodingArg))1186 return doRewriteToUTF8StringBoxedExpressionHelper(Msg, NS, commit);1187 }1188 1189 return false;1190}1191