diff options
Diffstat (limited to 'contrib/llvm/tools/clang/lib/ARCMigrate')
22 files changed, 8295 insertions, 0 deletions
diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/ARCMT.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/ARCMT.cpp new file mode 100644 index 0000000..8c04c83 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/ARCMT.cpp @@ -0,0 +1,616 @@ +//===--- ARCMT.cpp - Migration to ARC mode --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Internals.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/Basic/DiagnosticCategories.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Frontend/Utils.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "clang/Serialization/ASTReader.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Support/MemoryBuffer.h" +using namespace clang; +using namespace arcmt; + +bool CapturedDiagList::clearDiagnostic(ArrayRef<unsigned> IDs, + SourceRange range) { + if (range.isInvalid()) + return false; + + bool cleared = false; + ListTy::iterator I = List.begin(); + while (I != List.end()) { + FullSourceLoc diagLoc = I->getLocation(); + if ((IDs.empty() || // empty means clear all diagnostics in the range. + std::find(IDs.begin(), IDs.end(), I->getID()) != IDs.end()) && + !diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) && + (diagLoc == range.getEnd() || + diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) { + cleared = true; + ListTy::iterator eraseS = I++; + if (eraseS->getLevel() != DiagnosticsEngine::Note) + while (I != List.end() && I->getLevel() == DiagnosticsEngine::Note) + ++I; + // Clear the diagnostic and any notes following it. + I = List.erase(eraseS, I); + continue; + } + + ++I; + } + + return cleared; +} + +bool CapturedDiagList::hasDiagnostic(ArrayRef<unsigned> IDs, + SourceRange range) const { + if (range.isInvalid()) + return false; + + ListTy::const_iterator I = List.begin(); + while (I != List.end()) { + FullSourceLoc diagLoc = I->getLocation(); + if ((IDs.empty() || // empty means any diagnostic in the range. + std::find(IDs.begin(), IDs.end(), I->getID()) != IDs.end()) && + !diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) && + (diagLoc == range.getEnd() || + diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) { + return true; + } + + ++I; + } + + return false; +} + +void CapturedDiagList::reportDiagnostics(DiagnosticsEngine &Diags) const { + for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I) + Diags.Report(*I); +} + +bool CapturedDiagList::hasErrors() const { + for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I) + if (I->getLevel() >= DiagnosticsEngine::Error) + return true; + + return false; +} + +namespace { + +class CaptureDiagnosticConsumer : public DiagnosticConsumer { + DiagnosticsEngine &Diags; + DiagnosticConsumer &DiagClient; + CapturedDiagList &CapturedDiags; + bool HasBegunSourceFile; +public: + CaptureDiagnosticConsumer(DiagnosticsEngine &diags, + DiagnosticConsumer &client, + CapturedDiagList &capturedDiags) + : Diags(diags), DiagClient(client), CapturedDiags(capturedDiags), + HasBegunSourceFile(false) { } + + void BeginSourceFile(const LangOptions &Opts, + const Preprocessor *PP) override { + // Pass BeginSourceFile message onto DiagClient on first call. + // The corresponding EndSourceFile call will be made from an + // explicit call to FinishCapture. + if (!HasBegunSourceFile) { + DiagClient.BeginSourceFile(Opts, PP); + HasBegunSourceFile = true; + } + } + + void FinishCapture() { + // Call EndSourceFile on DiagClient on completion of capture to + // enable VerifyDiagnosticConsumer to check diagnostics *after* + // it has received the diagnostic list. + if (HasBegunSourceFile) { + DiagClient.EndSourceFile(); + HasBegunSourceFile = false; + } + } + + ~CaptureDiagnosticConsumer() override { + assert(!HasBegunSourceFile && "FinishCapture not called!"); + } + + void HandleDiagnostic(DiagnosticsEngine::Level level, + const Diagnostic &Info) override { + if (DiagnosticIDs::isARCDiagnostic(Info.getID()) || + level >= DiagnosticsEngine::Error || level == DiagnosticsEngine::Note) { + if (Info.getLocation().isValid()) + CapturedDiags.push_back(StoredDiagnostic(level, Info)); + return; + } + + // Non-ARC warnings are ignored. + Diags.setLastDiagnosticIgnored(); + } +}; + +} // end anonymous namespace + +static bool HasARCRuntime(CompilerInvocation &origCI) { + // This duplicates some functionality from Darwin::AddDeploymentTarget + // but this function is well defined, so keep it decoupled from the driver + // and avoid unrelated complications. + llvm::Triple triple(origCI.getTargetOpts().Triple); + + if (triple.isiOS()) + return triple.getOSMajorVersion() >= 5; + + if (triple.isWatchOS()) + return true; + + if (triple.getOS() == llvm::Triple::Darwin) + return triple.getOSMajorVersion() >= 11; + + if (triple.getOS() == llvm::Triple::MacOSX) { + unsigned Major, Minor, Micro; + triple.getOSVersion(Major, Minor, Micro); + return Major > 10 || (Major == 10 && Minor >= 7); + } + + return false; +} + +static CompilerInvocation * +createInvocationForMigration(CompilerInvocation &origCI, + const PCHContainerReader &PCHContainerRdr) { + std::unique_ptr<CompilerInvocation> CInvok; + CInvok.reset(new CompilerInvocation(origCI)); + PreprocessorOptions &PPOpts = CInvok->getPreprocessorOpts(); + if (!PPOpts.ImplicitPCHInclude.empty()) { + // We can't use a PCH because it was likely built in non-ARC mode and we + // want to parse in ARC. Include the original header. + FileManager FileMgr(origCI.getFileSystemOpts()); + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags( + new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(), + new IgnoringDiagConsumer())); + std::string OriginalFile = ASTReader::getOriginalSourceFile( + PPOpts.ImplicitPCHInclude, FileMgr, PCHContainerRdr, *Diags); + if (!OriginalFile.empty()) + PPOpts.Includes.insert(PPOpts.Includes.begin(), OriginalFile); + PPOpts.ImplicitPCHInclude.clear(); + } + // FIXME: Get the original header of a PTH as well. + CInvok->getPreprocessorOpts().ImplicitPTHInclude.clear(); + std::string define = getARCMTMacroName(); + define += '='; + CInvok->getPreprocessorOpts().addMacroDef(define); + CInvok->getLangOpts()->ObjCAutoRefCount = true; + CInvok->getLangOpts()->setGC(LangOptions::NonGC); + CInvok->getDiagnosticOpts().ErrorLimit = 0; + CInvok->getDiagnosticOpts().PedanticErrors = 0; + + // Ignore -Werror flags when migrating. + std::vector<std::string> WarnOpts; + for (std::vector<std::string>::iterator + I = CInvok->getDiagnosticOpts().Warnings.begin(), + E = CInvok->getDiagnosticOpts().Warnings.end(); I != E; ++I) { + if (!StringRef(*I).startswith("error")) + WarnOpts.push_back(*I); + } + WarnOpts.push_back("error=arc-unsafe-retained-assign"); + CInvok->getDiagnosticOpts().Warnings = std::move(WarnOpts); + + CInvok->getLangOpts()->ObjCWeakRuntime = HasARCRuntime(origCI); + CInvok->getLangOpts()->ObjCWeak = CInvok->getLangOpts()->ObjCWeakRuntime; + + return CInvok.release(); +} + +static void emitPremigrationErrors(const CapturedDiagList &arcDiags, + DiagnosticOptions *diagOpts, + Preprocessor &PP) { + TextDiagnosticPrinter printer(llvm::errs(), diagOpts); + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags( + new DiagnosticsEngine(DiagID, diagOpts, &printer, + /*ShouldOwnClient=*/false)); + Diags->setSourceManager(&PP.getSourceManager()); + + printer.BeginSourceFile(PP.getLangOpts(), &PP); + arcDiags.reportDiagnostics(*Diags); + printer.EndSourceFile(); +} + +//===----------------------------------------------------------------------===// +// checkForManualIssues. +//===----------------------------------------------------------------------===// + +bool arcmt::checkForManualIssues( + CompilerInvocation &origCI, const FrontendInputFile &Input, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagClient, bool emitPremigrationARCErrors, + StringRef plistOut) { + if (!origCI.getLangOpts()->ObjC1) + return false; + + LangOptions::GCMode OrigGCMode = origCI.getLangOpts()->getGC(); + bool NoNSAllocReallocError = origCI.getMigratorOpts().NoNSAllocReallocError; + bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval; + + std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode, + NoFinalizeRemoval); + assert(!transforms.empty()); + + std::unique_ptr<CompilerInvocation> CInvok; + CInvok.reset( + createInvocationForMigration(origCI, PCHContainerOps->getRawReader())); + CInvok->getFrontendOpts().Inputs.clear(); + CInvok->getFrontendOpts().Inputs.push_back(Input); + + CapturedDiagList capturedDiags; + + assert(DiagClient); + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags( + new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(), + DiagClient, /*ShouldOwnClient=*/false)); + + // Filter of all diagnostics. + CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags); + Diags->setClient(&errRec, /*ShouldOwnClient=*/false); + + std::unique_ptr<ASTUnit> Unit(ASTUnit::LoadFromCompilerInvocationAction( + CInvok.release(), PCHContainerOps, Diags)); + if (!Unit) { + errRec.FinishCapture(); + return true; + } + + // Don't filter diagnostics anymore. + Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); + + ASTContext &Ctx = Unit->getASTContext(); + + if (Diags->hasFatalErrorOccurred()) { + Diags->Reset(); + DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor()); + capturedDiags.reportDiagnostics(*Diags); + DiagClient->EndSourceFile(); + errRec.FinishCapture(); + return true; + } + + if (emitPremigrationARCErrors) + emitPremigrationErrors(capturedDiags, &origCI.getDiagnosticOpts(), + Unit->getPreprocessor()); + if (!plistOut.empty()) { + SmallVector<StoredDiagnostic, 8> arcDiags; + for (CapturedDiagList::iterator + I = capturedDiags.begin(), E = capturedDiags.end(); I != E; ++I) + arcDiags.push_back(*I); + writeARCDiagsToPlist(plistOut, arcDiags, + Ctx.getSourceManager(), Ctx.getLangOpts()); + } + + // After parsing of source files ended, we want to reuse the + // diagnostics objects to emit further diagnostics. + // We call BeginSourceFile because DiagnosticConsumer requires that + // diagnostics with source range information are emitted only in between + // BeginSourceFile() and EndSourceFile(). + DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor()); + + // No macros will be added since we are just checking and we won't modify + // source code. + std::vector<SourceLocation> ARCMTMacroLocs; + + TransformActions testAct(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); + MigrationPass pass(Ctx, OrigGCMode, Unit->getSema(), testAct, capturedDiags, + ARCMTMacroLocs); + pass.setNoFinalizeRemoval(NoFinalizeRemoval); + if (!NoNSAllocReallocError) + Diags->setSeverity(diag::warn_arcmt_nsalloc_realloc, diag::Severity::Error, + SourceLocation()); + + for (unsigned i=0, e = transforms.size(); i != e; ++i) + transforms[i](pass); + + capturedDiags.reportDiagnostics(*Diags); + + DiagClient->EndSourceFile(); + errRec.FinishCapture(); + + return capturedDiags.hasErrors() || testAct.hasReportedErrors(); +} + +//===----------------------------------------------------------------------===// +// applyTransformations. +//===----------------------------------------------------------------------===// + +static bool +applyTransforms(CompilerInvocation &origCI, const FrontendInputFile &Input, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagClient, StringRef outputDir, + bool emitPremigrationARCErrors, StringRef plistOut) { + if (!origCI.getLangOpts()->ObjC1) + return false; + + LangOptions::GCMode OrigGCMode = origCI.getLangOpts()->getGC(); + + // Make sure checking is successful first. + CompilerInvocation CInvokForCheck(origCI); + if (arcmt::checkForManualIssues(CInvokForCheck, Input, PCHContainerOps, + DiagClient, emitPremigrationARCErrors, + plistOut)) + return true; + + CompilerInvocation CInvok(origCI); + CInvok.getFrontendOpts().Inputs.clear(); + CInvok.getFrontendOpts().Inputs.push_back(Input); + + MigrationProcess migration(CInvok, PCHContainerOps, DiagClient, outputDir); + bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval; + + std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode, + NoFinalizeRemoval); + assert(!transforms.empty()); + + for (unsigned i=0, e = transforms.size(); i != e; ++i) { + bool err = migration.applyTransform(transforms[i]); + if (err) return true; + } + + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags( + new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(), + DiagClient, /*ShouldOwnClient=*/false)); + + if (outputDir.empty()) { + origCI.getLangOpts()->ObjCAutoRefCount = true; + return migration.getRemapper().overwriteOriginal(*Diags); + } else { + return migration.getRemapper().flushToDisk(outputDir, *Diags); + } +} + +bool arcmt::applyTransformations( + CompilerInvocation &origCI, const FrontendInputFile &Input, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagClient) { + return applyTransforms(origCI, Input, PCHContainerOps, DiagClient, + StringRef(), false, StringRef()); +} + +bool arcmt::migrateWithTemporaryFiles( + CompilerInvocation &origCI, const FrontendInputFile &Input, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagClient, StringRef outputDir, + bool emitPremigrationARCErrors, StringRef plistOut) { + assert(!outputDir.empty() && "Expected output directory path"); + return applyTransforms(origCI, Input, PCHContainerOps, DiagClient, outputDir, + emitPremigrationARCErrors, plistOut); +} + +bool arcmt::getFileRemappings(std::vector<std::pair<std::string,std::string> > & + remap, + StringRef outputDir, + DiagnosticConsumer *DiagClient) { + assert(!outputDir.empty()); + + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags( + new DiagnosticsEngine(DiagID, new DiagnosticOptions, + DiagClient, /*ShouldOwnClient=*/false)); + + FileRemapper remapper; + bool err = remapper.initFromDisk(outputDir, *Diags, + /*ignoreIfFilesChanged=*/true); + if (err) + return true; + + PreprocessorOptions PPOpts; + remapper.applyMappings(PPOpts); + remap = PPOpts.RemappedFiles; + + return false; +} + + +//===----------------------------------------------------------------------===// +// CollectTransformActions. +//===----------------------------------------------------------------------===// + +namespace { + +class ARCMTMacroTrackerPPCallbacks : public PPCallbacks { + std::vector<SourceLocation> &ARCMTMacroLocs; + +public: + ARCMTMacroTrackerPPCallbacks(std::vector<SourceLocation> &ARCMTMacroLocs) + : ARCMTMacroLocs(ARCMTMacroLocs) { } + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override { + if (MacroNameTok.getIdentifierInfo()->getName() == getARCMTMacroName()) + ARCMTMacroLocs.push_back(MacroNameTok.getLocation()); + } +}; + +class ARCMTMacroTrackerAction : public ASTFrontendAction { + std::vector<SourceLocation> &ARCMTMacroLocs; + +public: + ARCMTMacroTrackerAction(std::vector<SourceLocation> &ARCMTMacroLocs) + : ARCMTMacroLocs(ARCMTMacroLocs) { } + + std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override { + CI.getPreprocessor().addPPCallbacks( + llvm::make_unique<ARCMTMacroTrackerPPCallbacks>(ARCMTMacroLocs)); + return llvm::make_unique<ASTConsumer>(); + } +}; + +class RewritesApplicator : public TransformActions::RewriteReceiver { + Rewriter &rewriter; + MigrationProcess::RewriteListener *Listener; + +public: + RewritesApplicator(Rewriter &rewriter, ASTContext &ctx, + MigrationProcess::RewriteListener *listener) + : rewriter(rewriter), Listener(listener) { + if (Listener) + Listener->start(ctx); + } + ~RewritesApplicator() override { + if (Listener) + Listener->finish(); + } + + void insert(SourceLocation loc, StringRef text) override { + bool err = rewriter.InsertText(loc, text, /*InsertAfter=*/true, + /*indentNewLines=*/true); + if (!err && Listener) + Listener->insert(loc, text); + } + + void remove(CharSourceRange range) override { + Rewriter::RewriteOptions removeOpts; + removeOpts.IncludeInsertsAtBeginOfRange = false; + removeOpts.IncludeInsertsAtEndOfRange = false; + removeOpts.RemoveLineIfEmpty = true; + + bool err = rewriter.RemoveText(range, removeOpts); + if (!err && Listener) + Listener->remove(range); + } + + void increaseIndentation(CharSourceRange range, + SourceLocation parentIndent) override { + rewriter.IncreaseIndentation(range, parentIndent); + } +}; + +} // end anonymous namespace. + +/// \brief Anchor for VTable. +MigrationProcess::RewriteListener::~RewriteListener() { } + +MigrationProcess::MigrationProcess( + const CompilerInvocation &CI, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *diagClient, StringRef outputDir) + : OrigCI(CI), PCHContainerOps(PCHContainerOps), DiagClient(diagClient), + HadARCErrors(false) { + if (!outputDir.empty()) { + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags( + new DiagnosticsEngine(DiagID, &CI.getDiagnosticOpts(), + DiagClient, /*ShouldOwnClient=*/false)); + Remapper.initFromDisk(outputDir, *Diags, /*ignoreIfFilesChanges=*/true); + } +} + +bool MigrationProcess::applyTransform(TransformFn trans, + RewriteListener *listener) { + std::unique_ptr<CompilerInvocation> CInvok; + CInvok.reset( + createInvocationForMigration(OrigCI, PCHContainerOps->getRawReader())); + CInvok->getDiagnosticOpts().IgnoreWarnings = true; + + Remapper.applyMappings(CInvok->getPreprocessorOpts()); + + CapturedDiagList capturedDiags; + std::vector<SourceLocation> ARCMTMacroLocs; + + assert(DiagClient); + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags( + new DiagnosticsEngine(DiagID, new DiagnosticOptions, + DiagClient, /*ShouldOwnClient=*/false)); + + // Filter of all diagnostics. + CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags); + Diags->setClient(&errRec, /*ShouldOwnClient=*/false); + + std::unique_ptr<ARCMTMacroTrackerAction> ASTAction; + ASTAction.reset(new ARCMTMacroTrackerAction(ARCMTMacroLocs)); + + std::unique_ptr<ASTUnit> Unit(ASTUnit::LoadFromCompilerInvocationAction( + CInvok.release(), PCHContainerOps, Diags, ASTAction.get())); + if (!Unit) { + errRec.FinishCapture(); + return true; + } + Unit->setOwnsRemappedFileBuffers(false); // FileRemapper manages that. + + HadARCErrors = HadARCErrors || capturedDiags.hasErrors(); + + // Don't filter diagnostics anymore. + Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); + + ASTContext &Ctx = Unit->getASTContext(); + + if (Diags->hasFatalErrorOccurred()) { + Diags->Reset(); + DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor()); + capturedDiags.reportDiagnostics(*Diags); + DiagClient->EndSourceFile(); + errRec.FinishCapture(); + return true; + } + + // After parsing of source files ended, we want to reuse the + // diagnostics objects to emit further diagnostics. + // We call BeginSourceFile because DiagnosticConsumer requires that + // diagnostics with source range information are emitted only in between + // BeginSourceFile() and EndSourceFile(). + DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor()); + + Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOpts()); + TransformActions TA(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); + MigrationPass pass(Ctx, OrigCI.getLangOpts()->getGC(), + Unit->getSema(), TA, capturedDiags, ARCMTMacroLocs); + + trans(pass); + + { + RewritesApplicator applicator(rewriter, Ctx, listener); + TA.applyRewrites(applicator); + } + + DiagClient->EndSourceFile(); + errRec.FinishCapture(); + + if (DiagClient->getNumErrors()) + return true; + + for (Rewriter::buffer_iterator + I = rewriter.buffer_begin(), E = rewriter.buffer_end(); I != E; ++I) { + FileID FID = I->first; + RewriteBuffer &buf = I->second; + const FileEntry *file = Ctx.getSourceManager().getFileEntryForID(FID); + assert(file); + std::string newFname = file->getName(); + newFname += "-trans"; + SmallString<512> newText; + llvm::raw_svector_ostream vecOS(newText); + buf.write(vecOS); + std::unique_ptr<llvm::MemoryBuffer> memBuf( + llvm::MemoryBuffer::getMemBufferCopy( + StringRef(newText.data(), newText.size()), newFname)); + SmallString<64> filePath(file->getName()); + Unit->getFileManager().FixupRelativePath(filePath); + Remapper.remap(filePath.str(), std::move(memBuf)); + } + + return false; +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/ARCMTActions.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/ARCMTActions.cpp new file mode 100644 index 0000000..39a922f --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/ARCMTActions.cpp @@ -0,0 +1,60 @@ +//===--- ARCMTActions.cpp - ARC Migrate Tool Frontend Actions ---*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/ARCMigrate/ARCMTActions.h" +#include "clang/ARCMigrate/ARCMT.h" +#include "clang/Frontend/CompilerInstance.h" + +using namespace clang; +using namespace arcmt; + +bool CheckAction::BeginInvocation(CompilerInstance &CI) { + if (arcmt::checkForManualIssues(CI.getInvocation(), getCurrentInput(), + CI.getPCHContainerOperations(), + CI.getDiagnostics().getClient())) + return false; // errors, stop the action. + + // We only want to see warnings reported from arcmt::checkForManualIssues. + CI.getDiagnostics().setIgnoreAllWarnings(true); + return true; +} + +CheckAction::CheckAction(FrontendAction *WrappedAction) + : WrapperFrontendAction(WrappedAction) {} + +bool ModifyAction::BeginInvocation(CompilerInstance &CI) { + return !arcmt::applyTransformations(CI.getInvocation(), getCurrentInput(), + CI.getPCHContainerOperations(), + CI.getDiagnostics().getClient()); +} + +ModifyAction::ModifyAction(FrontendAction *WrappedAction) + : WrapperFrontendAction(WrappedAction) {} + +bool MigrateAction::BeginInvocation(CompilerInstance &CI) { + if (arcmt::migrateWithTemporaryFiles( + CI.getInvocation(), getCurrentInput(), CI.getPCHContainerOperations(), + CI.getDiagnostics().getClient(), MigrateDir, EmitPremigrationARCErros, + PlistOut)) + return false; // errors, stop the action. + + // We only want to see diagnostics emitted by migrateWithTemporaryFiles. + CI.getDiagnostics().setIgnoreAllWarnings(true); + return true; +} + +MigrateAction::MigrateAction(FrontendAction *WrappedAction, + StringRef migrateDir, + StringRef plistOut, + bool emitPremigrationARCErrors) + : WrapperFrontendAction(WrappedAction), MigrateDir(migrateDir), + PlistOut(plistOut), EmitPremigrationARCErros(emitPremigrationARCErrors) { + if (MigrateDir.empty()) + MigrateDir = "."; // user current directory if none is given. +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/FileRemapper.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/FileRemapper.cpp new file mode 100644 index 0000000..2cf2069 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/FileRemapper.cpp @@ -0,0 +1,257 @@ +//===--- FileRemapper.cpp - File Remapping Helper -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/ARCMigrate/FileRemapper.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include <fstream> + +using namespace clang; +using namespace arcmt; + +FileRemapper::FileRemapper() { + FileMgr.reset(new FileManager(FileSystemOptions())); +} + +FileRemapper::~FileRemapper() { + clear(); +} + +void FileRemapper::clear(StringRef outputDir) { + for (MappingsTy::iterator + I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) + resetTarget(I->second); + FromToMappings.clear(); + assert(ToFromMappings.empty()); + if (!outputDir.empty()) { + std::string infoFile = getRemapInfoFile(outputDir); + llvm::sys::fs::remove(infoFile); + } +} + +std::string FileRemapper::getRemapInfoFile(StringRef outputDir) { + assert(!outputDir.empty()); + SmallString<128> InfoFile = outputDir; + llvm::sys::path::append(InfoFile, "remap"); + return InfoFile.str(); +} + +bool FileRemapper::initFromDisk(StringRef outputDir, DiagnosticsEngine &Diag, + bool ignoreIfFilesChanged) { + std::string infoFile = getRemapInfoFile(outputDir); + return initFromFile(infoFile, Diag, ignoreIfFilesChanged); +} + +bool FileRemapper::initFromFile(StringRef filePath, DiagnosticsEngine &Diag, + bool ignoreIfFilesChanged) { + assert(FromToMappings.empty() && + "initFromDisk should be called before any remap calls"); + std::string infoFile = filePath; + if (!llvm::sys::fs::exists(infoFile)) + return false; + + std::vector<std::pair<const FileEntry *, const FileEntry *> > pairs; + + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> fileBuf = + llvm::MemoryBuffer::getFile(infoFile.c_str()); + if (!fileBuf) + return report("Error opening file: " + infoFile, Diag); + + SmallVector<StringRef, 64> lines; + fileBuf.get()->getBuffer().split(lines, "\n"); + + for (unsigned idx = 0; idx+3 <= lines.size(); idx += 3) { + StringRef fromFilename = lines[idx]; + unsigned long long timeModified; + if (lines[idx+1].getAsInteger(10, timeModified)) + return report("Invalid file data: '" + lines[idx+1] + "' not a number", + Diag); + StringRef toFilename = lines[idx+2]; + + const FileEntry *origFE = FileMgr->getFile(fromFilename); + if (!origFE) { + if (ignoreIfFilesChanged) + continue; + return report("File does not exist: " + fromFilename, Diag); + } + const FileEntry *newFE = FileMgr->getFile(toFilename); + if (!newFE) { + if (ignoreIfFilesChanged) + continue; + return report("File does not exist: " + toFilename, Diag); + } + + if ((uint64_t)origFE->getModificationTime() != timeModified) { + if (ignoreIfFilesChanged) + continue; + return report("File was modified: " + fromFilename, Diag); + } + + pairs.push_back(std::make_pair(origFE, newFE)); + } + + for (unsigned i = 0, e = pairs.size(); i != e; ++i) + remap(pairs[i].first, pairs[i].second); + + return false; +} + +bool FileRemapper::flushToDisk(StringRef outputDir, DiagnosticsEngine &Diag) { + using namespace llvm::sys; + + if (fs::create_directory(outputDir)) + return report("Could not create directory: " + outputDir, Diag); + + std::string infoFile = getRemapInfoFile(outputDir); + return flushToFile(infoFile, Diag); +} + +bool FileRemapper::flushToFile(StringRef outputPath, DiagnosticsEngine &Diag) { + using namespace llvm::sys; + + std::error_code EC; + std::string infoFile = outputPath; + llvm::raw_fd_ostream infoOut(infoFile, EC, llvm::sys::fs::F_None); + if (EC) + return report(EC.message(), Diag); + + for (MappingsTy::iterator + I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { + + const FileEntry *origFE = I->first; + SmallString<200> origPath = StringRef(origFE->getName()); + fs::make_absolute(origPath); + infoOut << origPath << '\n'; + infoOut << (uint64_t)origFE->getModificationTime() << '\n'; + + if (const FileEntry *FE = I->second.dyn_cast<const FileEntry *>()) { + SmallString<200> newPath = StringRef(FE->getName()); + fs::make_absolute(newPath); + infoOut << newPath << '\n'; + } else { + + SmallString<64> tempPath; + int fd; + if (fs::createTemporaryFile(path::filename(origFE->getName()), + path::extension(origFE->getName()).drop_front(), fd, + tempPath)) + return report("Could not create file: " + tempPath.str(), Diag); + + llvm::raw_fd_ostream newOut(fd, /*shouldClose=*/true); + llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>(); + newOut.write(mem->getBufferStart(), mem->getBufferSize()); + newOut.close(); + + const FileEntry *newE = FileMgr->getFile(tempPath); + remap(origFE, newE); + infoOut << newE->getName() << '\n'; + } + } + + infoOut.close(); + return false; +} + +bool FileRemapper::overwriteOriginal(DiagnosticsEngine &Diag, + StringRef outputDir) { + using namespace llvm::sys; + + for (MappingsTy::iterator + I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { + const FileEntry *origFE = I->first; + assert(I->second.is<llvm::MemoryBuffer *>()); + if (!fs::exists(origFE->getName())) + return report(StringRef("File does not exist: ") + origFE->getName(), + Diag); + + std::error_code EC; + llvm::raw_fd_ostream Out(origFE->getName(), EC, llvm::sys::fs::F_None); + if (EC) + return report(EC.message(), Diag); + + llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>(); + Out.write(mem->getBufferStart(), mem->getBufferSize()); + Out.close(); + } + + clear(outputDir); + return false; +} + +void FileRemapper::applyMappings(PreprocessorOptions &PPOpts) const { + for (MappingsTy::const_iterator + I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { + if (const FileEntry *FE = I->second.dyn_cast<const FileEntry *>()) { + PPOpts.addRemappedFile(I->first->getName(), FE->getName()); + } else { + llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>(); + PPOpts.addRemappedFile(I->first->getName(), mem); + } + } + + PPOpts.RetainRemappedFileBuffers = true; +} + +void FileRemapper::remap(StringRef filePath, + std::unique_ptr<llvm::MemoryBuffer> memBuf) { + remap(getOriginalFile(filePath), std::move(memBuf)); +} + +void FileRemapper::remap(const FileEntry *file, + std::unique_ptr<llvm::MemoryBuffer> memBuf) { + assert(file); + Target &targ = FromToMappings[file]; + resetTarget(targ); + targ = memBuf.release(); +} + +void FileRemapper::remap(const FileEntry *file, const FileEntry *newfile) { + assert(file && newfile); + Target &targ = FromToMappings[file]; + resetTarget(targ); + targ = newfile; + ToFromMappings[newfile] = file; +} + +const FileEntry *FileRemapper::getOriginalFile(StringRef filePath) { + const FileEntry *file = FileMgr->getFile(filePath); + // If we are updating a file that overriden an original file, + // actually update the original file. + llvm::DenseMap<const FileEntry *, const FileEntry *>::iterator + I = ToFromMappings.find(file); + if (I != ToFromMappings.end()) { + file = I->second; + assert(FromToMappings.find(file) != FromToMappings.end() && + "Original file not in mappings!"); + } + return file; +} + +void FileRemapper::resetTarget(Target &targ) { + if (!targ) + return; + + if (llvm::MemoryBuffer *oldmem = targ.dyn_cast<llvm::MemoryBuffer *>()) { + delete oldmem; + } else { + const FileEntry *toFE = targ.get<const FileEntry *>(); + ToFromMappings.erase(toFE); + } +} + +bool FileRemapper::report(const Twine &err, DiagnosticsEngine &Diag) { + Diag.Report(Diag.getCustomDiagID(DiagnosticsEngine::Error, "%0")) + << err.str(); + return true; +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/Internals.h b/contrib/llvm/tools/clang/lib/ARCMigrate/Internals.h new file mode 100644 index 0000000..4f153b1 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/Internals.h @@ -0,0 +1,181 @@ +//===-- Internals.h - Implementation Details---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_ARCMIGRATE_INTERNALS_H +#define LLVM_CLANG_LIB_ARCMIGRATE_INTERNALS_H + +#include "clang/ARCMigrate/ARCMT.h" +#include "clang/Basic/Diagnostic.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include <list> + +namespace clang { + class Sema; + class Stmt; + +namespace arcmt { + +class CapturedDiagList { + typedef std::list<StoredDiagnostic> ListTy; + ListTy List; + +public: + void push_back(const StoredDiagnostic &diag) { List.push_back(diag); } + + bool clearDiagnostic(ArrayRef<unsigned> IDs, SourceRange range); + bool hasDiagnostic(ArrayRef<unsigned> IDs, SourceRange range) const; + + void reportDiagnostics(DiagnosticsEngine &diags) const; + + bool hasErrors() const; + + typedef ListTy::const_iterator iterator; + iterator begin() const { return List.begin(); } + iterator end() const { return List.end(); } +}; + +void writeARCDiagsToPlist(const std::string &outPath, + ArrayRef<StoredDiagnostic> diags, + SourceManager &SM, const LangOptions &LangOpts); + +class TransformActions { + DiagnosticsEngine &Diags; + CapturedDiagList &CapturedDiags; + void *Impl; // TransformActionsImpl. + +public: + TransformActions(DiagnosticsEngine &diag, CapturedDiagList &capturedDiags, + ASTContext &ctx, Preprocessor &PP); + ~TransformActions(); + + void startTransaction(); + bool commitTransaction(); + void abortTransaction(); + + void insert(SourceLocation loc, StringRef text); + void insertAfterToken(SourceLocation loc, StringRef text); + void remove(SourceRange range); + void removeStmt(Stmt *S); + void replace(SourceRange range, StringRef text); + void replace(SourceRange range, SourceRange replacementRange); + void replaceStmt(Stmt *S, StringRef text); + void replaceText(SourceLocation loc, StringRef text, + StringRef replacementText); + void increaseIndentation(SourceRange range, + SourceLocation parentIndent); + + bool clearDiagnostic(ArrayRef<unsigned> IDs, SourceRange range); + bool clearAllDiagnostics(SourceRange range) { + return clearDiagnostic(None, range); + } + bool clearDiagnostic(unsigned ID1, unsigned ID2, SourceRange range) { + unsigned IDs[] = { ID1, ID2 }; + return clearDiagnostic(IDs, range); + } + bool clearDiagnostic(unsigned ID1, unsigned ID2, unsigned ID3, + SourceRange range) { + unsigned IDs[] = { ID1, ID2, ID3 }; + return clearDiagnostic(IDs, range); + } + + bool hasDiagnostic(unsigned ID, SourceRange range) { + return CapturedDiags.hasDiagnostic(ID, range); + } + + bool hasDiagnostic(unsigned ID1, unsigned ID2, SourceRange range) { + unsigned IDs[] = { ID1, ID2 }; + return CapturedDiags.hasDiagnostic(IDs, range); + } + + DiagnosticBuilder report(SourceLocation loc, unsigned diagId, + SourceRange range = SourceRange()); + void reportError(StringRef error, SourceLocation loc, + SourceRange range = SourceRange()); + void reportWarning(StringRef warning, SourceLocation loc, + SourceRange range = SourceRange()); + void reportNote(StringRef note, SourceLocation loc, + SourceRange range = SourceRange()); + + bool hasReportedErrors() const { + return Diags.hasUnrecoverableErrorOccurred(); + } + + class RewriteReceiver { + public: + virtual ~RewriteReceiver(); + + virtual void insert(SourceLocation loc, StringRef text) = 0; + virtual void remove(CharSourceRange range) = 0; + virtual void increaseIndentation(CharSourceRange range, + SourceLocation parentIndent) = 0; + }; + + void applyRewrites(RewriteReceiver &receiver); +}; + +class Transaction { + TransformActions &TA; + bool Aborted; + +public: + Transaction(TransformActions &TA) : TA(TA), Aborted(false) { + TA.startTransaction(); + } + + ~Transaction() { + if (!isAborted()) + TA.commitTransaction(); + } + + void abort() { + TA.abortTransaction(); + Aborted = true; + } + + bool isAborted() const { return Aborted; } +}; + +class MigrationPass { +public: + ASTContext &Ctx; + LangOptions::GCMode OrigGCMode; + MigratorOptions MigOptions; + Sema &SemaRef; + TransformActions &TA; + const CapturedDiagList &CapturedDiags; + std::vector<SourceLocation> &ARCMTMacroLocs; + Optional<bool> EnableCFBridgeFns; + + MigrationPass(ASTContext &Ctx, LangOptions::GCMode OrigGCMode, + Sema &sema, TransformActions &TA, + const CapturedDiagList &capturedDiags, + std::vector<SourceLocation> &ARCMTMacroLocs) + : Ctx(Ctx), OrigGCMode(OrigGCMode), MigOptions(), + SemaRef(sema), TA(TA), CapturedDiags(capturedDiags), + ARCMTMacroLocs(ARCMTMacroLocs) { } + + const CapturedDiagList &getDiags() const { return CapturedDiags; } + + bool isGCMigration() const { return OrigGCMode != LangOptions::NonGC; } + bool noFinalizeRemoval() const { return MigOptions.NoFinalizeRemoval; } + void setNoFinalizeRemoval(bool val) {MigOptions.NoFinalizeRemoval = val; } + + bool CFBridgingFunctionsDefined(); +}; + +static inline StringRef getARCMTMacroName() { + return "__IMPL_ARCMT_REMOVED_EXPR__"; +} + +} // end namespace arcmt + +} // end namespace clang + +#endif diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/ObjCMT.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/ObjCMT.cpp new file mode 100644 index 0000000..50b1136 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/ObjCMT.cpp @@ -0,0 +1,2276 @@ +//===--- ObjCMT.cpp - ObjC Migrate Tool -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "clang/ARCMigrate/ARCMT.h" +#include "clang/ARCMigrate/ARCMTActions.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/NSAPI.h" +#include "clang/AST/ParentMap.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Edit/Commit.h" +#include "clang/Edit/EditedSource.h" +#include "clang/Edit/EditsReceiver.h" +#include "clang/Edit/Rewriters.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "clang/Lex/PPConditionalDirectiveRecord.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/StaticAnalyzer/Checkers/ObjCRetainCount.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/YAMLParser.h" + +using namespace clang; +using namespace arcmt; +using namespace ento::objc_retain; + +namespace { + +class ObjCMigrateASTConsumer : public ASTConsumer { + enum CF_BRIDGING_KIND { + CF_BRIDGING_NONE, + CF_BRIDGING_ENABLE, + CF_BRIDGING_MAY_INCLUDE + }; + + void migrateDecl(Decl *D); + void migrateObjCContainerDecl(ASTContext &Ctx, ObjCContainerDecl *D); + void migrateProtocolConformance(ASTContext &Ctx, + const ObjCImplementationDecl *ImpDecl); + void CacheObjCNSIntegerTypedefed(const TypedefDecl *TypedefDcl); + bool migrateNSEnumDecl(ASTContext &Ctx, const EnumDecl *EnumDcl, + const TypedefDecl *TypedefDcl); + void migrateAllMethodInstaceType(ASTContext &Ctx, ObjCContainerDecl *CDecl); + void migrateMethodInstanceType(ASTContext &Ctx, ObjCContainerDecl *CDecl, + ObjCMethodDecl *OM); + bool migrateProperty(ASTContext &Ctx, ObjCContainerDecl *D, ObjCMethodDecl *OM); + void migrateNsReturnsInnerPointer(ASTContext &Ctx, ObjCMethodDecl *OM); + void migratePropertyNsReturnsInnerPointer(ASTContext &Ctx, ObjCPropertyDecl *P); + void migrateFactoryMethod(ASTContext &Ctx, ObjCContainerDecl *CDecl, + ObjCMethodDecl *OM, + ObjCInstanceTypeFamily OIT_Family = OIT_None); + + void migrateCFAnnotation(ASTContext &Ctx, const Decl *Decl); + void AddCFAnnotations(ASTContext &Ctx, const CallEffects &CE, + const FunctionDecl *FuncDecl, bool ResultAnnotated); + void AddCFAnnotations(ASTContext &Ctx, const CallEffects &CE, + const ObjCMethodDecl *MethodDecl, bool ResultAnnotated); + + void AnnotateImplicitBridging(ASTContext &Ctx); + + CF_BRIDGING_KIND migrateAddFunctionAnnotation(ASTContext &Ctx, + const FunctionDecl *FuncDecl); + + void migrateARCSafeAnnotation(ASTContext &Ctx, ObjCContainerDecl *CDecl); + + void migrateAddMethodAnnotation(ASTContext &Ctx, + const ObjCMethodDecl *MethodDecl); + + void inferDesignatedInitializers(ASTContext &Ctx, + const ObjCImplementationDecl *ImplD); + + bool InsertFoundation(ASTContext &Ctx, SourceLocation Loc); + +public: + std::string MigrateDir; + unsigned ASTMigrateActions; + FileID FileId; + const TypedefDecl *NSIntegerTypedefed; + const TypedefDecl *NSUIntegerTypedefed; + std::unique_ptr<NSAPI> NSAPIObj; + std::unique_ptr<edit::EditedSource> Editor; + FileRemapper &Remapper; + FileManager &FileMgr; + const PPConditionalDirectiveRecord *PPRec; + Preprocessor &PP; + bool IsOutputFile; + bool FoundationIncluded; + llvm::SmallPtrSet<ObjCProtocolDecl *, 32> ObjCProtocolDecls; + llvm::SmallVector<const Decl *, 8> CFFunctionIBCandidates; + llvm::StringSet<> WhiteListFilenames; + + ObjCMigrateASTConsumer(StringRef migrateDir, + unsigned astMigrateActions, + FileRemapper &remapper, + FileManager &fileMgr, + const PPConditionalDirectiveRecord *PPRec, + Preprocessor &PP, + bool isOutputFile, + ArrayRef<std::string> WhiteList) + : MigrateDir(migrateDir), + ASTMigrateActions(astMigrateActions), + NSIntegerTypedefed(nullptr), NSUIntegerTypedefed(nullptr), + Remapper(remapper), FileMgr(fileMgr), PPRec(PPRec), PP(PP), + IsOutputFile(isOutputFile), + FoundationIncluded(false){ + + // FIXME: StringSet should have insert(iter, iter) to use here. + for (const std::string &Val : WhiteList) + WhiteListFilenames.insert(Val); + } + +protected: + void Initialize(ASTContext &Context) override { + NSAPIObj.reset(new NSAPI(Context)); + Editor.reset(new edit::EditedSource(Context.getSourceManager(), + Context.getLangOpts(), + PPRec)); + } + + bool HandleTopLevelDecl(DeclGroupRef DG) override { + for (DeclGroupRef::iterator I = DG.begin(), E = DG.end(); I != E; ++I) + migrateDecl(*I); + return true; + } + void HandleInterestingDecl(DeclGroupRef DG) override { + // Ignore decls from the PCH. + } + void HandleTopLevelDeclInObjCContainer(DeclGroupRef DG) override { + ObjCMigrateASTConsumer::HandleTopLevelDecl(DG); + } + + void HandleTranslationUnit(ASTContext &Ctx) override; + + bool canModifyFile(StringRef Path) { + if (WhiteListFilenames.empty()) + return true; + return WhiteListFilenames.find(llvm::sys::path::filename(Path)) + != WhiteListFilenames.end(); + } + bool canModifyFile(const FileEntry *FE) { + if (!FE) + return false; + return canModifyFile(FE->getName()); + } + bool canModifyFile(FileID FID) { + if (FID.isInvalid()) + return false; + return canModifyFile(PP.getSourceManager().getFileEntryForID(FID)); + } + + bool canModify(const Decl *D) { + if (!D) + return false; + if (const ObjCCategoryImplDecl *CatImpl = dyn_cast<ObjCCategoryImplDecl>(D)) + return canModify(CatImpl->getCategoryDecl()); + if (const ObjCImplementationDecl *Impl = dyn_cast<ObjCImplementationDecl>(D)) + return canModify(Impl->getClassInterface()); + if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) + return canModify(cast<Decl>(MD->getDeclContext())); + + FileID FID = PP.getSourceManager().getFileID(D->getLocation()); + return canModifyFile(FID); + } +}; + +} + +ObjCMigrateAction::ObjCMigrateAction(FrontendAction *WrappedAction, + StringRef migrateDir, + unsigned migrateAction) + : WrapperFrontendAction(WrappedAction), MigrateDir(migrateDir), + ObjCMigAction(migrateAction), + CompInst(nullptr) { + if (MigrateDir.empty()) + MigrateDir = "."; // user current directory if none is given. +} + +std::unique_ptr<ASTConsumer> +ObjCMigrateAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { + PPConditionalDirectiveRecord * + PPRec = new PPConditionalDirectiveRecord(CompInst->getSourceManager()); + CI.getPreprocessor().addPPCallbacks(std::unique_ptr<PPCallbacks>(PPRec)); + std::vector<std::unique_ptr<ASTConsumer>> Consumers; + Consumers.push_back(WrapperFrontendAction::CreateASTConsumer(CI, InFile)); + Consumers.push_back(llvm::make_unique<ObjCMigrateASTConsumer>( + MigrateDir, ObjCMigAction, Remapper, CompInst->getFileManager(), PPRec, + CompInst->getPreprocessor(), false, None)); + return llvm::make_unique<MultiplexConsumer>(std::move(Consumers)); +} + +bool ObjCMigrateAction::BeginInvocation(CompilerInstance &CI) { + Remapper.initFromDisk(MigrateDir, CI.getDiagnostics(), + /*ignoreIfFilesChanges=*/true); + CompInst = &CI; + CI.getDiagnostics().setIgnoreAllWarnings(true); + return true; +} + +namespace { + // FIXME. This duplicates one in RewriteObjCFoundationAPI.cpp + bool subscriptOperatorNeedsParens(const Expr *FullExpr) { + const Expr* Expr = FullExpr->IgnoreImpCasts(); + return !(isa<ArraySubscriptExpr>(Expr) || isa<CallExpr>(Expr) || + isa<DeclRefExpr>(Expr) || isa<CXXNamedCastExpr>(Expr) || + isa<CXXConstructExpr>(Expr) || isa<CXXThisExpr>(Expr) || + isa<CXXTypeidExpr>(Expr) || + isa<CXXUnresolvedConstructExpr>(Expr) || + isa<ObjCMessageExpr>(Expr) || isa<ObjCPropertyRefExpr>(Expr) || + isa<ObjCProtocolExpr>(Expr) || isa<MemberExpr>(Expr) || + isa<ObjCIvarRefExpr>(Expr) || isa<ParenExpr>(FullExpr) || + isa<ParenListExpr>(Expr) || isa<SizeOfPackExpr>(Expr)); + } + + /// \brief - Rewrite message expression for Objective-C setter and getters into + /// property-dot syntax. + bool rewriteToPropertyDotSyntax(const ObjCMessageExpr *Msg, + Preprocessor &PP, + const NSAPI &NS, edit::Commit &commit, + const ParentMap *PMap) { + if (!Msg || Msg->isImplicit() || + (Msg->getReceiverKind() != ObjCMessageExpr::Instance && + Msg->getReceiverKind() != ObjCMessageExpr::SuperInstance)) + return false; + if (const Expr *Receiver = Msg->getInstanceReceiver()) + if (Receiver->getType()->isObjCBuiltinType()) + return false; + + const ObjCMethodDecl *Method = Msg->getMethodDecl(); + if (!Method) + return false; + if (!Method->isPropertyAccessor()) + return false; + + const ObjCPropertyDecl *Prop = Method->findPropertyDecl(); + if (!Prop) + return false; + + SourceRange MsgRange = Msg->getSourceRange(); + bool ReceiverIsSuper = + (Msg->getReceiverKind() == ObjCMessageExpr::SuperInstance); + // for 'super' receiver is nullptr. + const Expr *receiver = Msg->getInstanceReceiver(); + bool NeedsParen = + ReceiverIsSuper ? false : subscriptOperatorNeedsParens(receiver); + bool IsGetter = (Msg->getNumArgs() == 0); + if (IsGetter) { + // Find space location range between receiver expression and getter method. + SourceLocation BegLoc = + ReceiverIsSuper ? Msg->getSuperLoc() : receiver->getLocEnd(); + BegLoc = PP.getLocForEndOfToken(BegLoc); + SourceLocation EndLoc = Msg->getSelectorLoc(0); + SourceRange SpaceRange(BegLoc, EndLoc); + std::string PropertyDotString; + // rewrite getter method expression into: receiver.property or + // (receiver).property + if (NeedsParen) { + commit.insertBefore(receiver->getLocStart(), "("); + PropertyDotString = ")."; + } + else + PropertyDotString = "."; + PropertyDotString += Prop->getName(); + commit.replace(SpaceRange, PropertyDotString); + + // remove '[' ']' + commit.replace(SourceRange(MsgRange.getBegin(), MsgRange.getBegin()), ""); + commit.replace(SourceRange(MsgRange.getEnd(), MsgRange.getEnd()), ""); + } else { + if (NeedsParen) + commit.insertWrap("(", receiver->getSourceRange(), ")"); + std::string PropertyDotString = "."; + PropertyDotString += Prop->getName(); + PropertyDotString += " ="; + const Expr*const* Args = Msg->getArgs(); + const Expr *RHS = Args[0]; + if (!RHS) + return false; + SourceLocation BegLoc = + ReceiverIsSuper ? Msg->getSuperLoc() : receiver->getLocEnd(); + BegLoc = PP.getLocForEndOfToken(BegLoc); + SourceLocation EndLoc = RHS->getLocStart(); + EndLoc = EndLoc.getLocWithOffset(-1); + const char *colon = PP.getSourceManager().getCharacterData(EndLoc); + // Add a space after '=' if there is no space between RHS and '=' + if (colon && colon[0] == ':') + PropertyDotString += " "; + SourceRange Range(BegLoc, EndLoc); + commit.replace(Range, PropertyDotString); + // remove '[' ']' + commit.replace(SourceRange(MsgRange.getBegin(), MsgRange.getBegin()), ""); + commit.replace(SourceRange(MsgRange.getEnd(), MsgRange.getEnd()), ""); + } + return true; + } + + +class ObjCMigrator : public RecursiveASTVisitor<ObjCMigrator> { + ObjCMigrateASTConsumer &Consumer; + ParentMap &PMap; + +public: + ObjCMigrator(ObjCMigrateASTConsumer &consumer, ParentMap &PMap) + : Consumer(consumer), PMap(PMap) { } + + bool shouldVisitTemplateInstantiations() const { return false; } + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitObjCMessageExpr(ObjCMessageExpr *E) { + if (Consumer.ASTMigrateActions & FrontendOptions::ObjCMT_Literals) { + edit::Commit commit(*Consumer.Editor); + edit::rewriteToObjCLiteralSyntax(E, *Consumer.NSAPIObj, commit, &PMap); + Consumer.Editor->commit(commit); + } + + if (Consumer.ASTMigrateActions & FrontendOptions::ObjCMT_Subscripting) { + edit::Commit commit(*Consumer.Editor); + edit::rewriteToObjCSubscriptSyntax(E, *Consumer.NSAPIObj, commit); + Consumer.Editor->commit(commit); + } + + if (Consumer.ASTMigrateActions & FrontendOptions::ObjCMT_PropertyDotSyntax) { + edit::Commit commit(*Consumer.Editor); + rewriteToPropertyDotSyntax(E, Consumer.PP, *Consumer.NSAPIObj, + commit, &PMap); + Consumer.Editor->commit(commit); + } + + return true; + } + + bool TraverseObjCMessageExpr(ObjCMessageExpr *E) { + // Do depth first; we want to rewrite the subexpressions first so that if + // we have to move expressions we will move them already rewritten. + for (Stmt *SubStmt : E->children()) + if (!TraverseStmt(SubStmt)) + return false; + + return WalkUpFromObjCMessageExpr(E); + } +}; + +class BodyMigrator : public RecursiveASTVisitor<BodyMigrator> { + ObjCMigrateASTConsumer &Consumer; + std::unique_ptr<ParentMap> PMap; + +public: + BodyMigrator(ObjCMigrateASTConsumer &consumer) : Consumer(consumer) { } + + bool shouldVisitTemplateInstantiations() const { return false; } + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool TraverseStmt(Stmt *S) { + PMap.reset(new ParentMap(S)); + ObjCMigrator(Consumer, *PMap).TraverseStmt(S); + return true; + } +}; +} + +void ObjCMigrateASTConsumer::migrateDecl(Decl *D) { + if (!D) + return; + if (isa<ObjCMethodDecl>(D)) + return; // Wait for the ObjC container declaration. + + BodyMigrator(*this).TraverseDecl(D); +} + +static void append_attr(std::string &PropertyString, const char *attr, + bool &LParenAdded) { + if (!LParenAdded) { + PropertyString += "("; + LParenAdded = true; + } + else + PropertyString += ", "; + PropertyString += attr; +} + +static +void MigrateBlockOrFunctionPointerTypeVariable(std::string & PropertyString, + const std::string& TypeString, + const char *name) { + const char *argPtr = TypeString.c_str(); + int paren = 0; + while (*argPtr) { + switch (*argPtr) { + case '(': + PropertyString += *argPtr; + paren++; + break; + case ')': + PropertyString += *argPtr; + paren--; + break; + case '^': + case '*': + PropertyString += (*argPtr); + if (paren == 1) { + PropertyString += name; + name = ""; + } + break; + default: + PropertyString += *argPtr; + break; + } + argPtr++; + } +} + +static const char *PropertyMemoryAttribute(ASTContext &Context, QualType ArgType) { + Qualifiers::ObjCLifetime propertyLifetime = ArgType.getObjCLifetime(); + bool RetainableObject = ArgType->isObjCRetainableType(); + if (RetainableObject && + (propertyLifetime == Qualifiers::OCL_Strong + || propertyLifetime == Qualifiers::OCL_None)) { + if (const ObjCObjectPointerType *ObjPtrTy = + ArgType->getAs<ObjCObjectPointerType>()) { + ObjCInterfaceDecl *IDecl = ObjPtrTy->getObjectType()->getInterface(); + if (IDecl && + IDecl->lookupNestedProtocol(&Context.Idents.get("NSCopying"))) + return "copy"; + else + return "strong"; + } + else if (ArgType->isBlockPointerType()) + return "copy"; + } else if (propertyLifetime == Qualifiers::OCL_Weak) + // TODO. More precise determination of 'weak' attribute requires + // looking into setter's implementation for backing weak ivar. + return "weak"; + else if (RetainableObject) + return ArgType->isBlockPointerType() ? "copy" : "strong"; + return nullptr; +} + +static void rewriteToObjCProperty(const ObjCMethodDecl *Getter, + const ObjCMethodDecl *Setter, + const NSAPI &NS, edit::Commit &commit, + unsigned LengthOfPrefix, + bool Atomic, bool UseNsIosOnlyMacro, + bool AvailabilityArgsMatch) { + ASTContext &Context = NS.getASTContext(); + bool LParenAdded = false; + std::string PropertyString = "@property "; + if (UseNsIosOnlyMacro && NS.isMacroDefined("NS_NONATOMIC_IOSONLY")) { + PropertyString += "(NS_NONATOMIC_IOSONLY"; + LParenAdded = true; + } else if (!Atomic) { + PropertyString += "(nonatomic"; + LParenAdded = true; + } + + std::string PropertyNameString = Getter->getNameAsString(); + StringRef PropertyName(PropertyNameString); + if (LengthOfPrefix > 0) { + if (!LParenAdded) { + PropertyString += "(getter="; + LParenAdded = true; + } + else + PropertyString += ", getter="; + PropertyString += PropertyNameString; + } + // Property with no setter may be suggested as a 'readonly' property. + if (!Setter) + append_attr(PropertyString, "readonly", LParenAdded); + + + // Short circuit 'delegate' properties that contain the name "delegate" or + // "dataSource", or have exact name "target" to have 'assign' attribute. + if (PropertyName.equals("target") || + (PropertyName.find("delegate") != StringRef::npos) || + (PropertyName.find("dataSource") != StringRef::npos)) { + QualType QT = Getter->getReturnType(); + if (!QT->isRealType()) + append_attr(PropertyString, "assign", LParenAdded); + } else if (!Setter) { + QualType ResType = Context.getCanonicalType(Getter->getReturnType()); + if (const char *MemoryManagementAttr = PropertyMemoryAttribute(Context, ResType)) + append_attr(PropertyString, MemoryManagementAttr, LParenAdded); + } else { + const ParmVarDecl *argDecl = *Setter->param_begin(); + QualType ArgType = Context.getCanonicalType(argDecl->getType()); + if (const char *MemoryManagementAttr = PropertyMemoryAttribute(Context, ArgType)) + append_attr(PropertyString, MemoryManagementAttr, LParenAdded); + } + if (LParenAdded) + PropertyString += ')'; + QualType RT = Getter->getReturnType(); + if (!isa<TypedefType>(RT)) { + // strip off any ARC lifetime qualifier. + QualType CanResultTy = Context.getCanonicalType(RT); + if (CanResultTy.getQualifiers().hasObjCLifetime()) { + Qualifiers Qs = CanResultTy.getQualifiers(); + Qs.removeObjCLifetime(); + RT = Context.getQualifiedType(CanResultTy.getUnqualifiedType(), Qs); + } + } + PropertyString += " "; + PrintingPolicy SubPolicy(Context.getPrintingPolicy()); + SubPolicy.SuppressStrongLifetime = true; + SubPolicy.SuppressLifetimeQualifiers = true; + std::string TypeString = RT.getAsString(SubPolicy); + if (LengthOfPrefix > 0) { + // property name must strip off "is" and lower case the first character + // after that; e.g. isContinuous will become continuous. + StringRef PropertyNameStringRef(PropertyNameString); + PropertyNameStringRef = PropertyNameStringRef.drop_front(LengthOfPrefix); + PropertyNameString = PropertyNameStringRef; + bool NoLowering = (isUppercase(PropertyNameString[0]) && + PropertyNameString.size() > 1 && + isUppercase(PropertyNameString[1])); + if (!NoLowering) + PropertyNameString[0] = toLowercase(PropertyNameString[0]); + } + if (RT->isBlockPointerType() || RT->isFunctionPointerType()) + MigrateBlockOrFunctionPointerTypeVariable(PropertyString, + TypeString, + PropertyNameString.c_str()); + else { + char LastChar = TypeString[TypeString.size()-1]; + PropertyString += TypeString; + if (LastChar != '*') + PropertyString += ' '; + PropertyString += PropertyNameString; + } + SourceLocation StartGetterSelectorLoc = Getter->getSelectorStartLoc(); + Selector GetterSelector = Getter->getSelector(); + + SourceLocation EndGetterSelectorLoc = + StartGetterSelectorLoc.getLocWithOffset(GetterSelector.getNameForSlot(0).size()); + commit.replace(CharSourceRange::getCharRange(Getter->getLocStart(), + EndGetterSelectorLoc), + PropertyString); + if (Setter && AvailabilityArgsMatch) { + SourceLocation EndLoc = Setter->getDeclaratorEndLoc(); + // Get location past ';' + EndLoc = EndLoc.getLocWithOffset(1); + SourceLocation BeginOfSetterDclLoc = Setter->getLocStart(); + // FIXME. This assumes that setter decl; is immediately preceded by eoln. + // It is trying to remove the setter method decl. line entirely. + BeginOfSetterDclLoc = BeginOfSetterDclLoc.getLocWithOffset(-1); + commit.remove(SourceRange(BeginOfSetterDclLoc, EndLoc)); + } +} + +static bool IsCategoryNameWithDeprecatedSuffix(ObjCContainerDecl *D) { + if (ObjCCategoryDecl *CatDecl = dyn_cast<ObjCCategoryDecl>(D)) { + StringRef Name = CatDecl->getName(); + return Name.endswith("Deprecated"); + } + return false; +} + +void ObjCMigrateASTConsumer::migrateObjCContainerDecl(ASTContext &Ctx, + ObjCContainerDecl *D) { + if (D->isDeprecated() || IsCategoryNameWithDeprecatedSuffix(D)) + return; + + for (auto *Method : D->methods()) { + if (Method->isDeprecated()) + continue; + bool PropertyInferred = migrateProperty(Ctx, D, Method); + // If a property is inferred, do not attempt to attach NS_RETURNS_INNER_POINTER to + // the getter method as it ends up on the property itself which we don't want + // to do unless -objcmt-returns-innerpointer-property option is on. + if (!PropertyInferred || + (ASTMigrateActions & FrontendOptions::ObjCMT_ReturnsInnerPointerProperty)) + if (ASTMigrateActions & FrontendOptions::ObjCMT_Annotation) + migrateNsReturnsInnerPointer(Ctx, Method); + } + if (!(ASTMigrateActions & FrontendOptions::ObjCMT_ReturnsInnerPointerProperty)) + return; + + for (auto *Prop : D->properties()) { + if ((ASTMigrateActions & FrontendOptions::ObjCMT_Annotation) && + !Prop->isDeprecated()) + migratePropertyNsReturnsInnerPointer(Ctx, Prop); + } +} + +static bool +ClassImplementsAllMethodsAndProperties(ASTContext &Ctx, + const ObjCImplementationDecl *ImpDecl, + const ObjCInterfaceDecl *IDecl, + ObjCProtocolDecl *Protocol) { + // In auto-synthesis, protocol properties are not synthesized. So, + // a conforming protocol must have its required properties declared + // in class interface. + bool HasAtleastOneRequiredProperty = false; + if (const ObjCProtocolDecl *PDecl = Protocol->getDefinition()) + for (const auto *Property : PDecl->properties()) { + if (Property->getPropertyImplementation() == ObjCPropertyDecl::Optional) + continue; + HasAtleastOneRequiredProperty = true; + DeclContext::lookup_result R = IDecl->lookup(Property->getDeclName()); + if (R.size() == 0) { + // Relax the rule and look into class's implementation for a synthesize + // or dynamic declaration. Class is implementing a property coming from + // another protocol. This still makes the target protocol as conforming. + if (!ImpDecl->FindPropertyImplDecl( + Property->getDeclName().getAsIdentifierInfo())) + return false; + } + else if (ObjCPropertyDecl *ClassProperty = dyn_cast<ObjCPropertyDecl>(R[0])) { + if ((ClassProperty->getPropertyAttributes() + != Property->getPropertyAttributes()) || + !Ctx.hasSameType(ClassProperty->getType(), Property->getType())) + return false; + } + else + return false; + } + + // At this point, all required properties in this protocol conform to those + // declared in the class. + // Check that class implements the required methods of the protocol too. + bool HasAtleastOneRequiredMethod = false; + if (const ObjCProtocolDecl *PDecl = Protocol->getDefinition()) { + if (PDecl->meth_begin() == PDecl->meth_end()) + return HasAtleastOneRequiredProperty; + for (const auto *MD : PDecl->methods()) { + if (MD->isImplicit()) + continue; + if (MD->getImplementationControl() == ObjCMethodDecl::Optional) + continue; + DeclContext::lookup_result R = ImpDecl->lookup(MD->getDeclName()); + if (R.size() == 0) + return false; + bool match = false; + HasAtleastOneRequiredMethod = true; + for (unsigned I = 0, N = R.size(); I != N; ++I) + if (ObjCMethodDecl *ImpMD = dyn_cast<ObjCMethodDecl>(R[0])) + if (Ctx.ObjCMethodsAreEqual(MD, ImpMD)) { + match = true; + break; + } + if (!match) + return false; + } + } + return HasAtleastOneRequiredProperty || HasAtleastOneRequiredMethod; +} + +static bool rewriteToObjCInterfaceDecl(const ObjCInterfaceDecl *IDecl, + llvm::SmallVectorImpl<ObjCProtocolDecl*> &ConformingProtocols, + const NSAPI &NS, edit::Commit &commit) { + const ObjCList<ObjCProtocolDecl> &Protocols = IDecl->getReferencedProtocols(); + std::string ClassString; + SourceLocation EndLoc = + IDecl->getSuperClass() ? IDecl->getSuperClassLoc() : IDecl->getLocation(); + + if (Protocols.empty()) { + ClassString = '<'; + for (unsigned i = 0, e = ConformingProtocols.size(); i != e; i++) { + ClassString += ConformingProtocols[i]->getNameAsString(); + if (i != (e-1)) + ClassString += ", "; + } + ClassString += "> "; + } + else { + ClassString = ", "; + for (unsigned i = 0, e = ConformingProtocols.size(); i != e; i++) { + ClassString += ConformingProtocols[i]->getNameAsString(); + if (i != (e-1)) + ClassString += ", "; + } + ObjCInterfaceDecl::protocol_loc_iterator PL = IDecl->protocol_loc_end() - 1; + EndLoc = *PL; + } + + commit.insertAfterToken(EndLoc, ClassString); + return true; +} + +static StringRef GetUnsignedName(StringRef NSIntegerName) { + StringRef UnsignedName = llvm::StringSwitch<StringRef>(NSIntegerName) + .Case("int8_t", "uint8_t") + .Case("int16_t", "uint16_t") + .Case("int32_t", "uint32_t") + .Case("NSInteger", "NSUInteger") + .Case("int64_t", "uint64_t") + .Default(NSIntegerName); + return UnsignedName; +} + +static bool rewriteToNSEnumDecl(const EnumDecl *EnumDcl, + const TypedefDecl *TypedefDcl, + const NSAPI &NS, edit::Commit &commit, + StringRef NSIntegerName, + bool NSOptions) { + std::string ClassString; + if (NSOptions) { + ClassString = "typedef NS_OPTIONS("; + ClassString += GetUnsignedName(NSIntegerName); + } + else { + ClassString = "typedef NS_ENUM("; + ClassString += NSIntegerName; + } + ClassString += ", "; + + ClassString += TypedefDcl->getIdentifier()->getName(); + ClassString += ')'; + SourceRange R(EnumDcl->getLocStart(), EnumDcl->getLocStart()); + commit.replace(R, ClassString); + SourceLocation EndOfEnumDclLoc = EnumDcl->getLocEnd(); + EndOfEnumDclLoc = trans::findSemiAfterLocation(EndOfEnumDclLoc, + NS.getASTContext(), /*IsDecl*/true); + if (EndOfEnumDclLoc.isValid()) { + SourceRange EnumDclRange(EnumDcl->getLocStart(), EndOfEnumDclLoc); + commit.insertFromRange(TypedefDcl->getLocStart(), EnumDclRange); + } + else + return false; + + SourceLocation EndTypedefDclLoc = TypedefDcl->getLocEnd(); + EndTypedefDclLoc = trans::findSemiAfterLocation(EndTypedefDclLoc, + NS.getASTContext(), /*IsDecl*/true); + if (EndTypedefDclLoc.isValid()) { + SourceRange TDRange(TypedefDcl->getLocStart(), EndTypedefDclLoc); + commit.remove(TDRange); + } + else + return false; + + EndOfEnumDclLoc = trans::findLocationAfterSemi(EnumDcl->getLocEnd(), NS.getASTContext(), + /*IsDecl*/true); + if (EndOfEnumDclLoc.isValid()) { + SourceLocation BeginOfEnumDclLoc = EnumDcl->getLocStart(); + // FIXME. This assumes that enum decl; is immediately preceded by eoln. + // It is trying to remove the enum decl. lines entirely. + BeginOfEnumDclLoc = BeginOfEnumDclLoc.getLocWithOffset(-1); + commit.remove(SourceRange(BeginOfEnumDclLoc, EndOfEnumDclLoc)); + return true; + } + return false; +} + +static void rewriteToNSMacroDecl(ASTContext &Ctx, + const EnumDecl *EnumDcl, + const TypedefDecl *TypedefDcl, + const NSAPI &NS, edit::Commit &commit, + bool IsNSIntegerType) { + QualType DesignatedEnumType = EnumDcl->getIntegerType(); + assert(!DesignatedEnumType.isNull() + && "rewriteToNSMacroDecl - underlying enum type is null"); + + PrintingPolicy Policy(Ctx.getPrintingPolicy()); + std::string TypeString = DesignatedEnumType.getAsString(Policy); + std::string ClassString = IsNSIntegerType ? "NS_ENUM(" : "NS_OPTIONS("; + ClassString += TypeString; + ClassString += ", "; + + ClassString += TypedefDcl->getIdentifier()->getName(); + ClassString += ')'; + SourceLocation EndLoc; + if (EnumDcl->getIntegerTypeSourceInfo()) { + TypeSourceInfo *TSourceInfo = EnumDcl->getIntegerTypeSourceInfo(); + TypeLoc TLoc = TSourceInfo->getTypeLoc(); + EndLoc = TLoc.getLocEnd(); + const char *lbrace = Ctx.getSourceManager().getCharacterData(EndLoc); + unsigned count = 0; + if (lbrace) + while (lbrace[count] != '{') + ++count; + if (count > 0) + EndLoc = EndLoc.getLocWithOffset(count-1); + } + else + EndLoc = EnumDcl->getLocStart(); + SourceRange R(EnumDcl->getLocStart(), EndLoc); + commit.replace(R, ClassString); + // This is to remove spaces between '}' and typedef name. + SourceLocation StartTypedefLoc = EnumDcl->getLocEnd(); + StartTypedefLoc = StartTypedefLoc.getLocWithOffset(+1); + SourceLocation EndTypedefLoc = TypedefDcl->getLocEnd(); + + commit.remove(SourceRange(StartTypedefLoc, EndTypedefLoc)); +} + +static bool UseNSOptionsMacro(Preprocessor &PP, ASTContext &Ctx, + const EnumDecl *EnumDcl) { + bool PowerOfTwo = true; + bool AllHexdecimalEnumerator = true; + uint64_t MaxPowerOfTwoVal = 0; + for (auto Enumerator : EnumDcl->enumerators()) { + const Expr *InitExpr = Enumerator->getInitExpr(); + if (!InitExpr) { + PowerOfTwo = false; + AllHexdecimalEnumerator = false; + continue; + } + InitExpr = InitExpr->IgnoreParenCasts(); + if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(InitExpr)) + if (BO->isShiftOp() || BO->isBitwiseOp()) + return true; + + uint64_t EnumVal = Enumerator->getInitVal().getZExtValue(); + if (PowerOfTwo && EnumVal) { + if (!llvm::isPowerOf2_64(EnumVal)) + PowerOfTwo = false; + else if (EnumVal > MaxPowerOfTwoVal) + MaxPowerOfTwoVal = EnumVal; + } + if (AllHexdecimalEnumerator && EnumVal) { + bool FoundHexdecimalEnumerator = false; + SourceLocation EndLoc = Enumerator->getLocEnd(); + Token Tok; + if (!PP.getRawToken(EndLoc, Tok, /*IgnoreWhiteSpace=*/true)) + if (Tok.isLiteral() && Tok.getLength() > 2) { + if (const char *StringLit = Tok.getLiteralData()) + FoundHexdecimalEnumerator = + (StringLit[0] == '0' && (toLowercase(StringLit[1]) == 'x')); + } + if (!FoundHexdecimalEnumerator) + AllHexdecimalEnumerator = false; + } + } + return AllHexdecimalEnumerator || (PowerOfTwo && (MaxPowerOfTwoVal > 2)); +} + +void ObjCMigrateASTConsumer::migrateProtocolConformance(ASTContext &Ctx, + const ObjCImplementationDecl *ImpDecl) { + const ObjCInterfaceDecl *IDecl = ImpDecl->getClassInterface(); + if (!IDecl || ObjCProtocolDecls.empty() || IDecl->isDeprecated()) + return; + // Find all implicit conforming protocols for this class + // and make them explicit. + llvm::SmallPtrSet<ObjCProtocolDecl *, 8> ExplicitProtocols; + Ctx.CollectInheritedProtocols(IDecl, ExplicitProtocols); + llvm::SmallVector<ObjCProtocolDecl *, 8> PotentialImplicitProtocols; + + for (ObjCProtocolDecl *ProtDecl : ObjCProtocolDecls) + if (!ExplicitProtocols.count(ProtDecl)) + PotentialImplicitProtocols.push_back(ProtDecl); + + if (PotentialImplicitProtocols.empty()) + return; + + // go through list of non-optional methods and properties in each protocol + // in the PotentialImplicitProtocols list. If class implements every one of the + // methods and properties, then this class conforms to this protocol. + llvm::SmallVector<ObjCProtocolDecl*, 8> ConformingProtocols; + for (unsigned i = 0, e = PotentialImplicitProtocols.size(); i != e; i++) + if (ClassImplementsAllMethodsAndProperties(Ctx, ImpDecl, IDecl, + PotentialImplicitProtocols[i])) + ConformingProtocols.push_back(PotentialImplicitProtocols[i]); + + if (ConformingProtocols.empty()) + return; + + // Further reduce number of conforming protocols. If protocol P1 is in the list + // protocol P2 (P2<P1>), No need to include P1. + llvm::SmallVector<ObjCProtocolDecl*, 8> MinimalConformingProtocols; + for (unsigned i = 0, e = ConformingProtocols.size(); i != e; i++) { + bool DropIt = false; + ObjCProtocolDecl *TargetPDecl = ConformingProtocols[i]; + for (unsigned i1 = 0, e1 = ConformingProtocols.size(); i1 != e1; i1++) { + ObjCProtocolDecl *PDecl = ConformingProtocols[i1]; + if (PDecl == TargetPDecl) + continue; + if (PDecl->lookupProtocolNamed( + TargetPDecl->getDeclName().getAsIdentifierInfo())) { + DropIt = true; + break; + } + } + if (!DropIt) + MinimalConformingProtocols.push_back(TargetPDecl); + } + if (MinimalConformingProtocols.empty()) + return; + edit::Commit commit(*Editor); + rewriteToObjCInterfaceDecl(IDecl, MinimalConformingProtocols, + *NSAPIObj, commit); + Editor->commit(commit); +} + +void ObjCMigrateASTConsumer::CacheObjCNSIntegerTypedefed( + const TypedefDecl *TypedefDcl) { + + QualType qt = TypedefDcl->getTypeSourceInfo()->getType(); + if (NSAPIObj->isObjCNSIntegerType(qt)) + NSIntegerTypedefed = TypedefDcl; + else if (NSAPIObj->isObjCNSUIntegerType(qt)) + NSUIntegerTypedefed = TypedefDcl; +} + +bool ObjCMigrateASTConsumer::migrateNSEnumDecl(ASTContext &Ctx, + const EnumDecl *EnumDcl, + const TypedefDecl *TypedefDcl) { + if (!EnumDcl->isCompleteDefinition() || EnumDcl->getIdentifier() || + EnumDcl->isDeprecated()) + return false; + if (!TypedefDcl) { + if (NSIntegerTypedefed) { + TypedefDcl = NSIntegerTypedefed; + NSIntegerTypedefed = nullptr; + } + else if (NSUIntegerTypedefed) { + TypedefDcl = NSUIntegerTypedefed; + NSUIntegerTypedefed = nullptr; + } + else + return false; + FileID FileIdOfTypedefDcl = + PP.getSourceManager().getFileID(TypedefDcl->getLocation()); + FileID FileIdOfEnumDcl = + PP.getSourceManager().getFileID(EnumDcl->getLocation()); + if (FileIdOfTypedefDcl != FileIdOfEnumDcl) + return false; + } + if (TypedefDcl->isDeprecated()) + return false; + + QualType qt = TypedefDcl->getTypeSourceInfo()->getType(); + StringRef NSIntegerName = NSAPIObj->GetNSIntegralKind(qt); + + if (NSIntegerName.empty()) { + // Also check for typedef enum {...} TD; + if (const EnumType *EnumTy = qt->getAs<EnumType>()) { + if (EnumTy->getDecl() == EnumDcl) { + bool NSOptions = UseNSOptionsMacro(PP, Ctx, EnumDcl); + if (!InsertFoundation(Ctx, TypedefDcl->getLocStart())) + return false; + edit::Commit commit(*Editor); + rewriteToNSMacroDecl(Ctx, EnumDcl, TypedefDcl, *NSAPIObj, commit, !NSOptions); + Editor->commit(commit); + return true; + } + } + return false; + } + + // We may still use NS_OPTIONS based on what we find in the enumertor list. + bool NSOptions = UseNSOptionsMacro(PP, Ctx, EnumDcl); + if (!InsertFoundation(Ctx, TypedefDcl->getLocStart())) + return false; + edit::Commit commit(*Editor); + bool Res = rewriteToNSEnumDecl(EnumDcl, TypedefDcl, *NSAPIObj, + commit, NSIntegerName, NSOptions); + Editor->commit(commit); + return Res; +} + +static void ReplaceWithInstancetype(ASTContext &Ctx, + const ObjCMigrateASTConsumer &ASTC, + ObjCMethodDecl *OM) { + if (OM->getReturnType() == Ctx.getObjCInstanceType()) + return; // already has instancetype. + + SourceRange R; + std::string ClassString; + if (TypeSourceInfo *TSInfo = OM->getReturnTypeSourceInfo()) { + TypeLoc TL = TSInfo->getTypeLoc(); + R = SourceRange(TL.getBeginLoc(), TL.getEndLoc()); + ClassString = "instancetype"; + } + else { + R = SourceRange(OM->getLocStart(), OM->getLocStart()); + ClassString = OM->isInstanceMethod() ? '-' : '+'; + ClassString += " (instancetype)"; + } + edit::Commit commit(*ASTC.Editor); + commit.replace(R, ClassString); + ASTC.Editor->commit(commit); +} + +static void ReplaceWithClasstype(const ObjCMigrateASTConsumer &ASTC, + ObjCMethodDecl *OM) { + ObjCInterfaceDecl *IDecl = OM->getClassInterface(); + SourceRange R; + std::string ClassString; + if (TypeSourceInfo *TSInfo = OM->getReturnTypeSourceInfo()) { + TypeLoc TL = TSInfo->getTypeLoc(); + R = SourceRange(TL.getBeginLoc(), TL.getEndLoc()); { + ClassString = IDecl->getName(); + ClassString += "*"; + } + } + else { + R = SourceRange(OM->getLocStart(), OM->getLocStart()); + ClassString = "+ ("; + ClassString += IDecl->getName(); ClassString += "*)"; + } + edit::Commit commit(*ASTC.Editor); + commit.replace(R, ClassString); + ASTC.Editor->commit(commit); +} + +void ObjCMigrateASTConsumer::migrateMethodInstanceType(ASTContext &Ctx, + ObjCContainerDecl *CDecl, + ObjCMethodDecl *OM) { + ObjCInstanceTypeFamily OIT_Family = + Selector::getInstTypeMethodFamily(OM->getSelector()); + + std::string ClassName; + switch (OIT_Family) { + case OIT_None: + migrateFactoryMethod(Ctx, CDecl, OM); + return; + case OIT_Array: + ClassName = "NSArray"; + break; + case OIT_Dictionary: + ClassName = "NSDictionary"; + break; + case OIT_Singleton: + migrateFactoryMethod(Ctx, CDecl, OM, OIT_Singleton); + return; + case OIT_Init: + if (OM->getReturnType()->isObjCIdType()) + ReplaceWithInstancetype(Ctx, *this, OM); + return; + case OIT_ReturnsSelf: + migrateFactoryMethod(Ctx, CDecl, OM, OIT_ReturnsSelf); + return; + } + if (!OM->getReturnType()->isObjCIdType()) + return; + + ObjCInterfaceDecl *IDecl = dyn_cast<ObjCInterfaceDecl>(CDecl); + if (!IDecl) { + if (ObjCCategoryDecl *CatDecl = dyn_cast<ObjCCategoryDecl>(CDecl)) + IDecl = CatDecl->getClassInterface(); + else if (ObjCImplDecl *ImpDecl = dyn_cast<ObjCImplDecl>(CDecl)) + IDecl = ImpDecl->getClassInterface(); + } + if (!IDecl || + !IDecl->lookupInheritedClass(&Ctx.Idents.get(ClassName))) { + migrateFactoryMethod(Ctx, CDecl, OM); + return; + } + ReplaceWithInstancetype(Ctx, *this, OM); +} + +static bool TypeIsInnerPointer(QualType T) { + if (!T->isAnyPointerType()) + return false; + if (T->isObjCObjectPointerType() || T->isObjCBuiltinType() || + T->isBlockPointerType() || T->isFunctionPointerType() || + ento::coreFoundation::isCFObjectRef(T)) + return false; + // Also, typedef-of-pointer-to-incomplete-struct is something that we assume + // is not an innter pointer type. + QualType OrigT = T; + while (const TypedefType *TD = dyn_cast<TypedefType>(T.getTypePtr())) + T = TD->getDecl()->getUnderlyingType(); + if (OrigT == T || !T->isPointerType()) + return true; + const PointerType* PT = T->getAs<PointerType>(); + QualType UPointeeT = PT->getPointeeType().getUnqualifiedType(); + if (UPointeeT->isRecordType()) { + const RecordType *RecordTy = UPointeeT->getAs<RecordType>(); + if (!RecordTy->getDecl()->isCompleteDefinition()) + return false; + } + return true; +} + +/// \brief Check whether the two versions match. +static bool versionsMatch(const VersionTuple &X, const VersionTuple &Y) { + return (X == Y); +} + +/// AvailabilityAttrsMatch - This routine checks that if comparing two +/// availability attributes, all their components match. It returns +/// true, if not dealing with availability or when all components of +/// availability attributes match. This routine is only called when +/// the attributes are of the same kind. +static bool AvailabilityAttrsMatch(Attr *At1, Attr *At2) { + const AvailabilityAttr *AA1 = dyn_cast<AvailabilityAttr>(At1); + if (!AA1) + return true; + const AvailabilityAttr *AA2 = dyn_cast<AvailabilityAttr>(At2); + + VersionTuple Introduced1 = AA1->getIntroduced(); + VersionTuple Deprecated1 = AA1->getDeprecated(); + VersionTuple Obsoleted1 = AA1->getObsoleted(); + bool IsUnavailable1 = AA1->getUnavailable(); + VersionTuple Introduced2 = AA2->getIntroduced(); + VersionTuple Deprecated2 = AA2->getDeprecated(); + VersionTuple Obsoleted2 = AA2->getObsoleted(); + bool IsUnavailable2 = AA2->getUnavailable(); + return (versionsMatch(Introduced1, Introduced2) && + versionsMatch(Deprecated1, Deprecated2) && + versionsMatch(Obsoleted1, Obsoleted2) && + IsUnavailable1 == IsUnavailable2); + +} + +static bool MatchTwoAttributeLists(const AttrVec &Attrs1, const AttrVec &Attrs2, + bool &AvailabilityArgsMatch) { + // This list is very small, so this need not be optimized. + for (unsigned i = 0, e = Attrs1.size(); i != e; i++) { + bool match = false; + for (unsigned j = 0, f = Attrs2.size(); j != f; j++) { + // Matching attribute kind only. Except for Availabilty attributes, + // we are not getting into details of the attributes. For all practical purposes + // this is sufficient. + if (Attrs1[i]->getKind() == Attrs2[j]->getKind()) { + if (AvailabilityArgsMatch) + AvailabilityArgsMatch = AvailabilityAttrsMatch(Attrs1[i], Attrs2[j]); + match = true; + break; + } + } + if (!match) + return false; + } + return true; +} + +/// AttributesMatch - This routine checks list of attributes for two +/// decls. It returns false, if there is a mismatch in kind of +/// attributes seen in the decls. It returns true if the two decls +/// have list of same kind of attributes. Furthermore, when there +/// are availability attributes in the two decls, it sets the +/// AvailabilityArgsMatch to false if availability attributes have +/// different versions, etc. +static bool AttributesMatch(const Decl *Decl1, const Decl *Decl2, + bool &AvailabilityArgsMatch) { + if (!Decl1->hasAttrs() || !Decl2->hasAttrs()) { + AvailabilityArgsMatch = (Decl1->hasAttrs() == Decl2->hasAttrs()); + return true; + } + AvailabilityArgsMatch = true; + const AttrVec &Attrs1 = Decl1->getAttrs(); + const AttrVec &Attrs2 = Decl2->getAttrs(); + bool match = MatchTwoAttributeLists(Attrs1, Attrs2, AvailabilityArgsMatch); + if (match && (Attrs2.size() > Attrs1.size())) + return MatchTwoAttributeLists(Attrs2, Attrs1, AvailabilityArgsMatch); + return match; +} + +static bool IsValidIdentifier(ASTContext &Ctx, + const char *Name) { + if (!isIdentifierHead(Name[0])) + return false; + std::string NameString = Name; + NameString[0] = toLowercase(NameString[0]); + IdentifierInfo *II = &Ctx.Idents.get(NameString); + return II->getTokenID() == tok::identifier; +} + +bool ObjCMigrateASTConsumer::migrateProperty(ASTContext &Ctx, + ObjCContainerDecl *D, + ObjCMethodDecl *Method) { + if (Method->isPropertyAccessor() || !Method->isInstanceMethod() || + Method->param_size() != 0) + return false; + // Is this method candidate to be a getter? + QualType GRT = Method->getReturnType(); + if (GRT->isVoidType()) + return false; + + Selector GetterSelector = Method->getSelector(); + ObjCInstanceTypeFamily OIT_Family = + Selector::getInstTypeMethodFamily(GetterSelector); + + if (OIT_Family != OIT_None) + return false; + + IdentifierInfo *getterName = GetterSelector.getIdentifierInfoForSlot(0); + Selector SetterSelector = + SelectorTable::constructSetterSelector(PP.getIdentifierTable(), + PP.getSelectorTable(), + getterName); + ObjCMethodDecl *SetterMethod = D->getInstanceMethod(SetterSelector); + unsigned LengthOfPrefix = 0; + if (!SetterMethod) { + // try a different naming convention for getter: isXxxxx + StringRef getterNameString = getterName->getName(); + bool IsPrefix = getterNameString.startswith("is"); + // Note that we don't want to change an isXXX method of retainable object + // type to property (readonly or otherwise). + if (IsPrefix && GRT->isObjCRetainableType()) + return false; + if (IsPrefix || getterNameString.startswith("get")) { + LengthOfPrefix = (IsPrefix ? 2 : 3); + const char *CGetterName = getterNameString.data() + LengthOfPrefix; + // Make sure that first character after "is" or "get" prefix can + // start an identifier. + if (!IsValidIdentifier(Ctx, CGetterName)) + return false; + if (CGetterName[0] && isUppercase(CGetterName[0])) { + getterName = &Ctx.Idents.get(CGetterName); + SetterSelector = + SelectorTable::constructSetterSelector(PP.getIdentifierTable(), + PP.getSelectorTable(), + getterName); + SetterMethod = D->getInstanceMethod(SetterSelector); + } + } + } + + if (SetterMethod) { + if ((ASTMigrateActions & FrontendOptions::ObjCMT_ReadwriteProperty) == 0) + return false; + bool AvailabilityArgsMatch; + if (SetterMethod->isDeprecated() || + !AttributesMatch(Method, SetterMethod, AvailabilityArgsMatch)) + return false; + + // Is this a valid setter, matching the target getter? + QualType SRT = SetterMethod->getReturnType(); + if (!SRT->isVoidType()) + return false; + const ParmVarDecl *argDecl = *SetterMethod->param_begin(); + QualType ArgType = argDecl->getType(); + if (!Ctx.hasSameUnqualifiedType(ArgType, GRT)) + return false; + edit::Commit commit(*Editor); + rewriteToObjCProperty(Method, SetterMethod, *NSAPIObj, commit, + LengthOfPrefix, + (ASTMigrateActions & + FrontendOptions::ObjCMT_AtomicProperty) != 0, + (ASTMigrateActions & + FrontendOptions::ObjCMT_NsAtomicIOSOnlyProperty) != 0, + AvailabilityArgsMatch); + Editor->commit(commit); + return true; + } + else if (ASTMigrateActions & FrontendOptions::ObjCMT_ReadonlyProperty) { + // Try a non-void method with no argument (and no setter or property of same name + // as a 'readonly' property. + edit::Commit commit(*Editor); + rewriteToObjCProperty(Method, nullptr /*SetterMethod*/, *NSAPIObj, commit, + LengthOfPrefix, + (ASTMigrateActions & + FrontendOptions::ObjCMT_AtomicProperty) != 0, + (ASTMigrateActions & + FrontendOptions::ObjCMT_NsAtomicIOSOnlyProperty) != 0, + /*AvailabilityArgsMatch*/false); + Editor->commit(commit); + return true; + } + return false; +} + +void ObjCMigrateASTConsumer::migrateNsReturnsInnerPointer(ASTContext &Ctx, + ObjCMethodDecl *OM) { + if (OM->isImplicit() || + !OM->isInstanceMethod() || + OM->hasAttr<ObjCReturnsInnerPointerAttr>()) + return; + + QualType RT = OM->getReturnType(); + if (!TypeIsInnerPointer(RT) || + !NSAPIObj->isMacroDefined("NS_RETURNS_INNER_POINTER")) + return; + + edit::Commit commit(*Editor); + commit.insertBefore(OM->getLocEnd(), " NS_RETURNS_INNER_POINTER"); + Editor->commit(commit); +} + +void ObjCMigrateASTConsumer::migratePropertyNsReturnsInnerPointer(ASTContext &Ctx, + ObjCPropertyDecl *P) { + QualType T = P->getType(); + + if (!TypeIsInnerPointer(T) || + !NSAPIObj->isMacroDefined("NS_RETURNS_INNER_POINTER")) + return; + edit::Commit commit(*Editor); + commit.insertBefore(P->getLocEnd(), " NS_RETURNS_INNER_POINTER "); + Editor->commit(commit); +} + +void ObjCMigrateASTConsumer::migrateAllMethodInstaceType(ASTContext &Ctx, + ObjCContainerDecl *CDecl) { + if (CDecl->isDeprecated() || IsCategoryNameWithDeprecatedSuffix(CDecl)) + return; + + // migrate methods which can have instancetype as their result type. + for (auto *Method : CDecl->methods()) { + if (Method->isDeprecated()) + continue; + migrateMethodInstanceType(Ctx, CDecl, Method); + } +} + +void ObjCMigrateASTConsumer::migrateFactoryMethod(ASTContext &Ctx, + ObjCContainerDecl *CDecl, + ObjCMethodDecl *OM, + ObjCInstanceTypeFamily OIT_Family) { + if (OM->isInstanceMethod() || + OM->getReturnType() == Ctx.getObjCInstanceType() || + !OM->getReturnType()->isObjCIdType()) + return; + + // Candidate factory methods are + (id) NaMeXXX : ... which belong to a class + // NSYYYNamE with matching names be at least 3 characters long. + ObjCInterfaceDecl *IDecl = dyn_cast<ObjCInterfaceDecl>(CDecl); + if (!IDecl) { + if (ObjCCategoryDecl *CatDecl = dyn_cast<ObjCCategoryDecl>(CDecl)) + IDecl = CatDecl->getClassInterface(); + else if (ObjCImplDecl *ImpDecl = dyn_cast<ObjCImplDecl>(CDecl)) + IDecl = ImpDecl->getClassInterface(); + } + if (!IDecl) + return; + + std::string StringClassName = IDecl->getName(); + StringRef LoweredClassName(StringClassName); + std::string StringLoweredClassName = LoweredClassName.lower(); + LoweredClassName = StringLoweredClassName; + + IdentifierInfo *MethodIdName = OM->getSelector().getIdentifierInfoForSlot(0); + // Handle method with no name at its first selector slot; e.g. + (id):(int)x. + if (!MethodIdName) + return; + + std::string MethodName = MethodIdName->getName(); + if (OIT_Family == OIT_Singleton || OIT_Family == OIT_ReturnsSelf) { + StringRef STRefMethodName(MethodName); + size_t len = 0; + if (STRefMethodName.startswith("standard")) + len = strlen("standard"); + else if (STRefMethodName.startswith("shared")) + len = strlen("shared"); + else if (STRefMethodName.startswith("default")) + len = strlen("default"); + else + return; + MethodName = STRefMethodName.substr(len); + } + std::string MethodNameSubStr = MethodName.substr(0, 3); + StringRef MethodNamePrefix(MethodNameSubStr); + std::string StringLoweredMethodNamePrefix = MethodNamePrefix.lower(); + MethodNamePrefix = StringLoweredMethodNamePrefix; + size_t Ix = LoweredClassName.rfind(MethodNamePrefix); + if (Ix == StringRef::npos) + return; + std::string ClassNamePostfix = LoweredClassName.substr(Ix); + StringRef LoweredMethodName(MethodName); + std::string StringLoweredMethodName = LoweredMethodName.lower(); + LoweredMethodName = StringLoweredMethodName; + if (!LoweredMethodName.startswith(ClassNamePostfix)) + return; + if (OIT_Family == OIT_ReturnsSelf) + ReplaceWithClasstype(*this, OM); + else + ReplaceWithInstancetype(Ctx, *this, OM); +} + +static bool IsVoidStarType(QualType Ty) { + if (!Ty->isPointerType()) + return false; + + while (const TypedefType *TD = dyn_cast<TypedefType>(Ty.getTypePtr())) + Ty = TD->getDecl()->getUnderlyingType(); + + // Is the type void*? + const PointerType* PT = Ty->getAs<PointerType>(); + if (PT->getPointeeType().getUnqualifiedType()->isVoidType()) + return true; + return IsVoidStarType(PT->getPointeeType()); +} + +/// AuditedType - This routine audits the type AT and returns false if it is one of known +/// CF object types or of the "void *" variety. It returns true if we don't care about the type +/// such as a non-pointer or pointers which have no ownership issues (such as "int *"). +static bool AuditedType (QualType AT) { + if (!AT->isAnyPointerType() && !AT->isBlockPointerType()) + return true; + // FIXME. There isn't much we can say about CF pointer type; or is there? + if (ento::coreFoundation::isCFObjectRef(AT) || + IsVoidStarType(AT) || + // If an ObjC object is type, assuming that it is not a CF function and + // that it is an un-audited function. + AT->isObjCObjectPointerType() || AT->isObjCBuiltinType()) + return false; + // All other pointers are assumed audited as harmless. + return true; +} + +void ObjCMigrateASTConsumer::AnnotateImplicitBridging(ASTContext &Ctx) { + if (CFFunctionIBCandidates.empty()) + return; + if (!NSAPIObj->isMacroDefined("CF_IMPLICIT_BRIDGING_ENABLED")) { + CFFunctionIBCandidates.clear(); + FileId = FileID(); + return; + } + // Insert CF_IMPLICIT_BRIDGING_ENABLE/CF_IMPLICIT_BRIDGING_DISABLED + const Decl *FirstFD = CFFunctionIBCandidates[0]; + const Decl *LastFD = + CFFunctionIBCandidates[CFFunctionIBCandidates.size()-1]; + const char *PragmaString = "\nCF_IMPLICIT_BRIDGING_ENABLED\n\n"; + edit::Commit commit(*Editor); + commit.insertBefore(FirstFD->getLocStart(), PragmaString); + PragmaString = "\n\nCF_IMPLICIT_BRIDGING_DISABLED\n"; + SourceLocation EndLoc = LastFD->getLocEnd(); + // get location just past end of function location. + EndLoc = PP.getLocForEndOfToken(EndLoc); + if (isa<FunctionDecl>(LastFD)) { + // For Methods, EndLoc points to the ending semcolon. So, + // not of these extra work is needed. + Token Tok; + // get locaiton of token that comes after end of function. + bool Failed = PP.getRawToken(EndLoc, Tok, /*IgnoreWhiteSpace=*/true); + if (!Failed) + EndLoc = Tok.getLocation(); + } + commit.insertAfterToken(EndLoc, PragmaString); + Editor->commit(commit); + FileId = FileID(); + CFFunctionIBCandidates.clear(); +} + +void ObjCMigrateASTConsumer::migrateCFAnnotation(ASTContext &Ctx, const Decl *Decl) { + if (Decl->isDeprecated()) + return; + + if (Decl->hasAttr<CFAuditedTransferAttr>()) { + assert(CFFunctionIBCandidates.empty() && + "Cannot have audited functions/methods inside user " + "provided CF_IMPLICIT_BRIDGING_ENABLE"); + return; + } + + // Finction must be annotated first. + if (const FunctionDecl *FuncDecl = dyn_cast<FunctionDecl>(Decl)) { + CF_BRIDGING_KIND AuditKind = migrateAddFunctionAnnotation(Ctx, FuncDecl); + if (AuditKind == CF_BRIDGING_ENABLE) { + CFFunctionIBCandidates.push_back(Decl); + if (FileId.isInvalid()) + FileId = PP.getSourceManager().getFileID(Decl->getLocation()); + } + else if (AuditKind == CF_BRIDGING_MAY_INCLUDE) { + if (!CFFunctionIBCandidates.empty()) { + CFFunctionIBCandidates.push_back(Decl); + if (FileId.isInvalid()) + FileId = PP.getSourceManager().getFileID(Decl->getLocation()); + } + } + else + AnnotateImplicitBridging(Ctx); + } + else { + migrateAddMethodAnnotation(Ctx, cast<ObjCMethodDecl>(Decl)); + AnnotateImplicitBridging(Ctx); + } +} + +void ObjCMigrateASTConsumer::AddCFAnnotations(ASTContext &Ctx, + const CallEffects &CE, + const FunctionDecl *FuncDecl, + bool ResultAnnotated) { + // Annotate function. + if (!ResultAnnotated) { + RetEffect Ret = CE.getReturnValue(); + const char *AnnotationString = nullptr; + if (Ret.getObjKind() == RetEffect::CF) { + if (Ret.isOwned() && NSAPIObj->isMacroDefined("CF_RETURNS_RETAINED")) + AnnotationString = " CF_RETURNS_RETAINED"; + else if (Ret.notOwned() && + NSAPIObj->isMacroDefined("CF_RETURNS_NOT_RETAINED")) + AnnotationString = " CF_RETURNS_NOT_RETAINED"; + } + else if (Ret.getObjKind() == RetEffect::ObjC) { + if (Ret.isOwned() && NSAPIObj->isMacroDefined("NS_RETURNS_RETAINED")) + AnnotationString = " NS_RETURNS_RETAINED"; + } + + if (AnnotationString) { + edit::Commit commit(*Editor); + commit.insertAfterToken(FuncDecl->getLocEnd(), AnnotationString); + Editor->commit(commit); + } + } + ArrayRef<ArgEffect> AEArgs = CE.getArgs(); + unsigned i = 0; + for (FunctionDecl::param_const_iterator pi = FuncDecl->param_begin(), + pe = FuncDecl->param_end(); pi != pe; ++pi, ++i) { + const ParmVarDecl *pd = *pi; + ArgEffect AE = AEArgs[i]; + if (AE == DecRef && !pd->hasAttr<CFConsumedAttr>() && + NSAPIObj->isMacroDefined("CF_CONSUMED")) { + edit::Commit commit(*Editor); + commit.insertBefore(pd->getLocation(), "CF_CONSUMED "); + Editor->commit(commit); + } + else if (AE == DecRefMsg && !pd->hasAttr<NSConsumedAttr>() && + NSAPIObj->isMacroDefined("NS_CONSUMED")) { + edit::Commit commit(*Editor); + commit.insertBefore(pd->getLocation(), "NS_CONSUMED "); + Editor->commit(commit); + } + } +} + + +ObjCMigrateASTConsumer::CF_BRIDGING_KIND + ObjCMigrateASTConsumer::migrateAddFunctionAnnotation( + ASTContext &Ctx, + const FunctionDecl *FuncDecl) { + if (FuncDecl->hasBody()) + return CF_BRIDGING_NONE; + + CallEffects CE = CallEffects::getEffect(FuncDecl); + bool FuncIsReturnAnnotated = (FuncDecl->hasAttr<CFReturnsRetainedAttr>() || + FuncDecl->hasAttr<CFReturnsNotRetainedAttr>() || + FuncDecl->hasAttr<NSReturnsRetainedAttr>() || + FuncDecl->hasAttr<NSReturnsNotRetainedAttr>() || + FuncDecl->hasAttr<NSReturnsAutoreleasedAttr>()); + + // Trivial case of when function is annotated and has no argument. + if (FuncIsReturnAnnotated && FuncDecl->getNumParams() == 0) + return CF_BRIDGING_NONE; + + bool ReturnCFAudited = false; + if (!FuncIsReturnAnnotated) { + RetEffect Ret = CE.getReturnValue(); + if (Ret.getObjKind() == RetEffect::CF && + (Ret.isOwned() || Ret.notOwned())) + ReturnCFAudited = true; + else if (!AuditedType(FuncDecl->getReturnType())) + return CF_BRIDGING_NONE; + } + + // At this point result type is audited for potential inclusion. + // Now, how about argument types. + ArrayRef<ArgEffect> AEArgs = CE.getArgs(); + unsigned i = 0; + bool ArgCFAudited = false; + for (FunctionDecl::param_const_iterator pi = FuncDecl->param_begin(), + pe = FuncDecl->param_end(); pi != pe; ++pi, ++i) { + const ParmVarDecl *pd = *pi; + ArgEffect AE = AEArgs[i]; + if (AE == DecRef /*CFConsumed annotated*/ || AE == IncRef) { + if (AE == DecRef && !pd->hasAttr<CFConsumedAttr>()) + ArgCFAudited = true; + else if (AE == IncRef) + ArgCFAudited = true; + } + else { + QualType AT = pd->getType(); + if (!AuditedType(AT)) { + AddCFAnnotations(Ctx, CE, FuncDecl, FuncIsReturnAnnotated); + return CF_BRIDGING_NONE; + } + } + } + if (ReturnCFAudited || ArgCFAudited) + return CF_BRIDGING_ENABLE; + + return CF_BRIDGING_MAY_INCLUDE; +} + +void ObjCMigrateASTConsumer::migrateARCSafeAnnotation(ASTContext &Ctx, + ObjCContainerDecl *CDecl) { + if (!isa<ObjCInterfaceDecl>(CDecl) || CDecl->isDeprecated()) + return; + + // migrate methods which can have instancetype as their result type. + for (const auto *Method : CDecl->methods()) + migrateCFAnnotation(Ctx, Method); +} + +void ObjCMigrateASTConsumer::AddCFAnnotations(ASTContext &Ctx, + const CallEffects &CE, + const ObjCMethodDecl *MethodDecl, + bool ResultAnnotated) { + // Annotate function. + if (!ResultAnnotated) { + RetEffect Ret = CE.getReturnValue(); + const char *AnnotationString = nullptr; + if (Ret.getObjKind() == RetEffect::CF) { + if (Ret.isOwned() && NSAPIObj->isMacroDefined("CF_RETURNS_RETAINED")) + AnnotationString = " CF_RETURNS_RETAINED"; + else if (Ret.notOwned() && + NSAPIObj->isMacroDefined("CF_RETURNS_NOT_RETAINED")) + AnnotationString = " CF_RETURNS_NOT_RETAINED"; + } + else if (Ret.getObjKind() == RetEffect::ObjC) { + ObjCMethodFamily OMF = MethodDecl->getMethodFamily(); + switch (OMF) { + case clang::OMF_alloc: + case clang::OMF_new: + case clang::OMF_copy: + case clang::OMF_init: + case clang::OMF_mutableCopy: + break; + + default: + if (Ret.isOwned() && NSAPIObj->isMacroDefined("NS_RETURNS_RETAINED")) + AnnotationString = " NS_RETURNS_RETAINED"; + break; + } + } + + if (AnnotationString) { + edit::Commit commit(*Editor); + commit.insertBefore(MethodDecl->getLocEnd(), AnnotationString); + Editor->commit(commit); + } + } + ArrayRef<ArgEffect> AEArgs = CE.getArgs(); + unsigned i = 0; + for (ObjCMethodDecl::param_const_iterator pi = MethodDecl->param_begin(), + pe = MethodDecl->param_end(); pi != pe; ++pi, ++i) { + const ParmVarDecl *pd = *pi; + ArgEffect AE = AEArgs[i]; + if (AE == DecRef && !pd->hasAttr<CFConsumedAttr>() && + NSAPIObj->isMacroDefined("CF_CONSUMED")) { + edit::Commit commit(*Editor); + commit.insertBefore(pd->getLocation(), "CF_CONSUMED "); + Editor->commit(commit); + } + } +} + +void ObjCMigrateASTConsumer::migrateAddMethodAnnotation( + ASTContext &Ctx, + const ObjCMethodDecl *MethodDecl) { + if (MethodDecl->hasBody() || MethodDecl->isImplicit()) + return; + + CallEffects CE = CallEffects::getEffect(MethodDecl); + bool MethodIsReturnAnnotated = (MethodDecl->hasAttr<CFReturnsRetainedAttr>() || + MethodDecl->hasAttr<CFReturnsNotRetainedAttr>() || + MethodDecl->hasAttr<NSReturnsRetainedAttr>() || + MethodDecl->hasAttr<NSReturnsNotRetainedAttr>() || + MethodDecl->hasAttr<NSReturnsAutoreleasedAttr>()); + + if (CE.getReceiver() == DecRefMsg && + !MethodDecl->hasAttr<NSConsumesSelfAttr>() && + MethodDecl->getMethodFamily() != OMF_init && + MethodDecl->getMethodFamily() != OMF_release && + NSAPIObj->isMacroDefined("NS_CONSUMES_SELF")) { + edit::Commit commit(*Editor); + commit.insertBefore(MethodDecl->getLocEnd(), " NS_CONSUMES_SELF"); + Editor->commit(commit); + } + + // Trivial case of when function is annotated and has no argument. + if (MethodIsReturnAnnotated && + (MethodDecl->param_begin() == MethodDecl->param_end())) + return; + + if (!MethodIsReturnAnnotated) { + RetEffect Ret = CE.getReturnValue(); + if ((Ret.getObjKind() == RetEffect::CF || + Ret.getObjKind() == RetEffect::ObjC) && + (Ret.isOwned() || Ret.notOwned())) { + AddCFAnnotations(Ctx, CE, MethodDecl, false); + return; + } else if (!AuditedType(MethodDecl->getReturnType())) + return; + } + + // At this point result type is either annotated or audited. + // Now, how about argument types. + ArrayRef<ArgEffect> AEArgs = CE.getArgs(); + unsigned i = 0; + for (ObjCMethodDecl::param_const_iterator pi = MethodDecl->param_begin(), + pe = MethodDecl->param_end(); pi != pe; ++pi, ++i) { + const ParmVarDecl *pd = *pi; + ArgEffect AE = AEArgs[i]; + if ((AE == DecRef && !pd->hasAttr<CFConsumedAttr>()) || AE == IncRef || + !AuditedType(pd->getType())) { + AddCFAnnotations(Ctx, CE, MethodDecl, MethodIsReturnAnnotated); + return; + } + } + return; +} + +namespace { +class SuperInitChecker : public RecursiveASTVisitor<SuperInitChecker> { +public: + bool shouldVisitTemplateInstantiations() const { return false; } + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitObjCMessageExpr(ObjCMessageExpr *E) { + if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance) { + if (E->getMethodFamily() == OMF_init) + return false; + } + return true; + } +}; +} // anonymous namespace + +static bool hasSuperInitCall(const ObjCMethodDecl *MD) { + return !SuperInitChecker().TraverseStmt(MD->getBody()); +} + +void ObjCMigrateASTConsumer::inferDesignatedInitializers( + ASTContext &Ctx, + const ObjCImplementationDecl *ImplD) { + + const ObjCInterfaceDecl *IFace = ImplD->getClassInterface(); + if (!IFace || IFace->hasDesignatedInitializers()) + return; + if (!NSAPIObj->isMacroDefined("NS_DESIGNATED_INITIALIZER")) + return; + + for (const auto *MD : ImplD->instance_methods()) { + if (MD->isDeprecated() || + MD->getMethodFamily() != OMF_init || + MD->isDesignatedInitializerForTheInterface()) + continue; + const ObjCMethodDecl *IFaceM = IFace->getMethod(MD->getSelector(), + /*isInstance=*/true); + if (!IFaceM) + continue; + if (hasSuperInitCall(MD)) { + edit::Commit commit(*Editor); + commit.insert(IFaceM->getLocEnd(), " NS_DESIGNATED_INITIALIZER"); + Editor->commit(commit); + } + } +} + +bool ObjCMigrateASTConsumer::InsertFoundation(ASTContext &Ctx, + SourceLocation Loc) { + if (FoundationIncluded) + return true; + if (Loc.isInvalid()) + return false; + edit::Commit commit(*Editor); + if (Ctx.getLangOpts().Modules) + commit.insert(Loc, "#ifndef NS_ENUM\n@import Foundation;\n#endif\n"); + else + commit.insert(Loc, "#ifndef NS_ENUM\n#import <Foundation/Foundation.h>\n#endif\n"); + Editor->commit(commit); + FoundationIncluded = true; + return true; +} + +namespace { + +class RewritesReceiver : public edit::EditsReceiver { + Rewriter &Rewrite; + +public: + RewritesReceiver(Rewriter &Rewrite) : Rewrite(Rewrite) { } + + void insert(SourceLocation loc, StringRef text) override { + Rewrite.InsertText(loc, text); + } + void replace(CharSourceRange range, StringRef text) override { + Rewrite.ReplaceText(range.getBegin(), Rewrite.getRangeSize(range), text); + } +}; + +class JSONEditWriter : public edit::EditsReceiver { + SourceManager &SourceMgr; + llvm::raw_ostream &OS; + +public: + JSONEditWriter(SourceManager &SM, llvm::raw_ostream &OS) + : SourceMgr(SM), OS(OS) { + OS << "[\n"; + } + ~JSONEditWriter() override { OS << "]\n"; } + +private: + struct EntryWriter { + SourceManager &SourceMgr; + llvm::raw_ostream &OS; + + EntryWriter(SourceManager &SM, llvm::raw_ostream &OS) + : SourceMgr(SM), OS(OS) { + OS << " {\n"; + } + ~EntryWriter() { + OS << " },\n"; + } + + void writeLoc(SourceLocation Loc) { + FileID FID; + unsigned Offset; + std::tie(FID, Offset) = SourceMgr.getDecomposedLoc(Loc); + assert(FID.isValid()); + SmallString<200> Path = + StringRef(SourceMgr.getFileEntryForID(FID)->getName()); + llvm::sys::fs::make_absolute(Path); + OS << " \"file\": \""; + OS.write_escaped(Path.str()) << "\",\n"; + OS << " \"offset\": " << Offset << ",\n"; + } + + void writeRemove(CharSourceRange Range) { + assert(Range.isCharRange()); + std::pair<FileID, unsigned> Begin = + SourceMgr.getDecomposedLoc(Range.getBegin()); + std::pair<FileID, unsigned> End = + SourceMgr.getDecomposedLoc(Range.getEnd()); + assert(Begin.first == End.first); + assert(Begin.second <= End.second); + unsigned Length = End.second - Begin.second; + + OS << " \"remove\": " << Length << ",\n"; + } + + void writeText(StringRef Text) { + OS << " \"text\": \""; + OS.write_escaped(Text) << "\",\n"; + } + }; + + void insert(SourceLocation Loc, StringRef Text) override { + EntryWriter Writer(SourceMgr, OS); + Writer.writeLoc(Loc); + Writer.writeText(Text); + } + + void replace(CharSourceRange Range, StringRef Text) override { + EntryWriter Writer(SourceMgr, OS); + Writer.writeLoc(Range.getBegin()); + Writer.writeRemove(Range); + Writer.writeText(Text); + } + + void remove(CharSourceRange Range) override { + EntryWriter Writer(SourceMgr, OS); + Writer.writeLoc(Range.getBegin()); + Writer.writeRemove(Range); + } +}; + +} + +void ObjCMigrateASTConsumer::HandleTranslationUnit(ASTContext &Ctx) { + + TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl(); + if (ASTMigrateActions & FrontendOptions::ObjCMT_MigrateDecls) { + for (DeclContext::decl_iterator D = TU->decls_begin(), DEnd = TU->decls_end(); + D != DEnd; ++D) { + FileID FID = PP.getSourceManager().getFileID((*D)->getLocation()); + if (FID.isValid()) + if (FileId.isValid() && FileId != FID) { + if (ASTMigrateActions & FrontendOptions::ObjCMT_Annotation) + AnnotateImplicitBridging(Ctx); + } + + if (ObjCInterfaceDecl *CDecl = dyn_cast<ObjCInterfaceDecl>(*D)) + if (canModify(CDecl)) + migrateObjCContainerDecl(Ctx, CDecl); + if (ObjCCategoryDecl *CatDecl = dyn_cast<ObjCCategoryDecl>(*D)) { + if (canModify(CatDecl)) + migrateObjCContainerDecl(Ctx, CatDecl); + } + else if (ObjCProtocolDecl *PDecl = dyn_cast<ObjCProtocolDecl>(*D)) { + ObjCProtocolDecls.insert(PDecl->getCanonicalDecl()); + if (canModify(PDecl)) + migrateObjCContainerDecl(Ctx, PDecl); + } + else if (const ObjCImplementationDecl *ImpDecl = + dyn_cast<ObjCImplementationDecl>(*D)) { + if ((ASTMigrateActions & FrontendOptions::ObjCMT_ProtocolConformance) && + canModify(ImpDecl)) + migrateProtocolConformance(Ctx, ImpDecl); + } + else if (const EnumDecl *ED = dyn_cast<EnumDecl>(*D)) { + if (!(ASTMigrateActions & FrontendOptions::ObjCMT_NsMacros)) + continue; + if (!canModify(ED)) + continue; + DeclContext::decl_iterator N = D; + if (++N != DEnd) { + const TypedefDecl *TD = dyn_cast<TypedefDecl>(*N); + if (migrateNSEnumDecl(Ctx, ED, TD) && TD) + D++; + } + else + migrateNSEnumDecl(Ctx, ED, /*TypedefDecl */nullptr); + } + else if (const TypedefDecl *TD = dyn_cast<TypedefDecl>(*D)) { + if (!(ASTMigrateActions & FrontendOptions::ObjCMT_NsMacros)) + continue; + if (!canModify(TD)) + continue; + DeclContext::decl_iterator N = D; + if (++N == DEnd) + continue; + if (const EnumDecl *ED = dyn_cast<EnumDecl>(*N)) { + if (++N != DEnd) + if (const TypedefDecl *TDF = dyn_cast<TypedefDecl>(*N)) { + // prefer typedef-follows-enum to enum-follows-typedef pattern. + if (migrateNSEnumDecl(Ctx, ED, TDF)) { + ++D; ++D; + CacheObjCNSIntegerTypedefed(TD); + continue; + } + } + if (migrateNSEnumDecl(Ctx, ED, TD)) { + ++D; + continue; + } + } + CacheObjCNSIntegerTypedefed(TD); + } + else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(*D)) { + if ((ASTMigrateActions & FrontendOptions::ObjCMT_Annotation) && + canModify(FD)) + migrateCFAnnotation(Ctx, FD); + } + + if (ObjCContainerDecl *CDecl = dyn_cast<ObjCContainerDecl>(*D)) { + bool CanModify = canModify(CDecl); + // migrate methods which can have instancetype as their result type. + if ((ASTMigrateActions & FrontendOptions::ObjCMT_Instancetype) && + CanModify) + migrateAllMethodInstaceType(Ctx, CDecl); + // annotate methods with CF annotations. + if ((ASTMigrateActions & FrontendOptions::ObjCMT_Annotation) && + CanModify) + migrateARCSafeAnnotation(Ctx, CDecl); + } + + if (const ObjCImplementationDecl * + ImplD = dyn_cast<ObjCImplementationDecl>(*D)) { + if ((ASTMigrateActions & FrontendOptions::ObjCMT_DesignatedInitializer) && + canModify(ImplD)) + inferDesignatedInitializers(Ctx, ImplD); + } + } + if (ASTMigrateActions & FrontendOptions::ObjCMT_Annotation) + AnnotateImplicitBridging(Ctx); + } + + if (IsOutputFile) { + std::error_code EC; + llvm::raw_fd_ostream OS(MigrateDir, EC, llvm::sys::fs::F_None); + if (EC) { + DiagnosticsEngine &Diags = Ctx.getDiagnostics(); + Diags.Report(Diags.getCustomDiagID(DiagnosticsEngine::Error, "%0")) + << EC.message(); + return; + } + + JSONEditWriter Writer(Ctx.getSourceManager(), OS); + Editor->applyRewrites(Writer); + return; + } + + Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOpts()); + RewritesReceiver Rec(rewriter); + Editor->applyRewrites(Rec); + + for (Rewriter::buffer_iterator + I = rewriter.buffer_begin(), E = rewriter.buffer_end(); I != E; ++I) { + FileID FID = I->first; + RewriteBuffer &buf = I->second; + const FileEntry *file = Ctx.getSourceManager().getFileEntryForID(FID); + assert(file); + SmallString<512> newText; + llvm::raw_svector_ostream vecOS(newText); + buf.write(vecOS); + std::unique_ptr<llvm::MemoryBuffer> memBuf( + llvm::MemoryBuffer::getMemBufferCopy( + StringRef(newText.data(), newText.size()), file->getName())); + SmallString<64> filePath(file->getName()); + FileMgr.FixupRelativePath(filePath); + Remapper.remap(filePath.str(), std::move(memBuf)); + } + + if (IsOutputFile) { + Remapper.flushToFile(MigrateDir, Ctx.getDiagnostics()); + } else { + Remapper.flushToDisk(MigrateDir, Ctx.getDiagnostics()); + } +} + +bool MigrateSourceAction::BeginInvocation(CompilerInstance &CI) { + CI.getDiagnostics().setIgnoreAllWarnings(true); + return true; +} + +static std::vector<std::string> getWhiteListFilenames(StringRef DirPath) { + using namespace llvm::sys::fs; + using namespace llvm::sys::path; + + std::vector<std::string> Filenames; + if (DirPath.empty() || !is_directory(DirPath)) + return Filenames; + + std::error_code EC; + directory_iterator DI = directory_iterator(DirPath, EC); + directory_iterator DE; + for (; !EC && DI != DE; DI = DI.increment(EC)) { + if (is_regular_file(DI->path())) + Filenames.push_back(filename(DI->path())); + } + + return Filenames; +} + +std::unique_ptr<ASTConsumer> +MigrateSourceAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { + PPConditionalDirectiveRecord * + PPRec = new PPConditionalDirectiveRecord(CI.getSourceManager()); + unsigned ObjCMTAction = CI.getFrontendOpts().ObjCMTAction; + unsigned ObjCMTOpts = ObjCMTAction; + // These are companion flags, they do not enable transformations. + ObjCMTOpts &= ~(FrontendOptions::ObjCMT_AtomicProperty | + FrontendOptions::ObjCMT_NsAtomicIOSOnlyProperty); + if (ObjCMTOpts == FrontendOptions::ObjCMT_None) { + // If no specific option was given, enable literals+subscripting transforms + // by default. + ObjCMTAction |= FrontendOptions::ObjCMT_Literals | + FrontendOptions::ObjCMT_Subscripting; + } + CI.getPreprocessor().addPPCallbacks(std::unique_ptr<PPCallbacks>(PPRec)); + std::vector<std::string> WhiteList = + getWhiteListFilenames(CI.getFrontendOpts().ObjCMTWhiteListPath); + return llvm::make_unique<ObjCMigrateASTConsumer>( + CI.getFrontendOpts().OutputFile, ObjCMTAction, Remapper, + CI.getFileManager(), PPRec, CI.getPreprocessor(), + /*isOutputFile=*/true, WhiteList); +} + +namespace { +struct EditEntry { + const FileEntry *File; + unsigned Offset; + unsigned RemoveLen; + std::string Text; + + EditEntry() : File(), Offset(), RemoveLen() {} +}; +} + +namespace llvm { +template<> struct DenseMapInfo<EditEntry> { + static inline EditEntry getEmptyKey() { + EditEntry Entry; + Entry.Offset = unsigned(-1); + return Entry; + } + static inline EditEntry getTombstoneKey() { + EditEntry Entry; + Entry.Offset = unsigned(-2); + return Entry; + } + static unsigned getHashValue(const EditEntry& Val) { + llvm::FoldingSetNodeID ID; + ID.AddPointer(Val.File); + ID.AddInteger(Val.Offset); + ID.AddInteger(Val.RemoveLen); + ID.AddString(Val.Text); + return ID.ComputeHash(); + } + static bool isEqual(const EditEntry &LHS, const EditEntry &RHS) { + return LHS.File == RHS.File && + LHS.Offset == RHS.Offset && + LHS.RemoveLen == RHS.RemoveLen && + LHS.Text == RHS.Text; + } +}; +} + +namespace { +class RemapFileParser { + FileManager &FileMgr; + +public: + RemapFileParser(FileManager &FileMgr) : FileMgr(FileMgr) { } + + bool parse(StringRef File, SmallVectorImpl<EditEntry> &Entries) { + using namespace llvm::yaml; + + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileBufOrErr = + llvm::MemoryBuffer::getFile(File); + if (!FileBufOrErr) + return true; + + llvm::SourceMgr SM; + Stream YAMLStream(FileBufOrErr.get()->getMemBufferRef(), SM); + document_iterator I = YAMLStream.begin(); + if (I == YAMLStream.end()) + return true; + Node *Root = I->getRoot(); + if (!Root) + return true; + + SequenceNode *SeqNode = dyn_cast<SequenceNode>(Root); + if (!SeqNode) + return true; + + for (SequenceNode::iterator + AI = SeqNode->begin(), AE = SeqNode->end(); AI != AE; ++AI) { + MappingNode *MapNode = dyn_cast<MappingNode>(&*AI); + if (!MapNode) + continue; + parseEdit(MapNode, Entries); + } + + return false; + } + +private: + void parseEdit(llvm::yaml::MappingNode *Node, + SmallVectorImpl<EditEntry> &Entries) { + using namespace llvm::yaml; + EditEntry Entry; + bool Ignore = false; + + for (MappingNode::iterator + KVI = Node->begin(), KVE = Node->end(); KVI != KVE; ++KVI) { + ScalarNode *KeyString = dyn_cast<ScalarNode>((*KVI).getKey()); + if (!KeyString) + continue; + SmallString<10> KeyStorage; + StringRef Key = KeyString->getValue(KeyStorage); + + ScalarNode *ValueString = dyn_cast<ScalarNode>((*KVI).getValue()); + if (!ValueString) + continue; + SmallString<64> ValueStorage; + StringRef Val = ValueString->getValue(ValueStorage); + + if (Key == "file") { + const FileEntry *FE = FileMgr.getFile(Val); + if (!FE) + Ignore = true; + Entry.File = FE; + } else if (Key == "offset") { + if (Val.getAsInteger(10, Entry.Offset)) + Ignore = true; + } else if (Key == "remove") { + if (Val.getAsInteger(10, Entry.RemoveLen)) + Ignore = true; + } else if (Key == "text") { + Entry.Text = Val; + } + } + + if (!Ignore) + Entries.push_back(Entry); + } +}; +} + +static bool reportDiag(const Twine &Err, DiagnosticsEngine &Diag) { + Diag.Report(Diag.getCustomDiagID(DiagnosticsEngine::Error, "%0")) + << Err.str(); + return true; +} + +static std::string applyEditsToTemp(const FileEntry *FE, + ArrayRef<EditEntry> Edits, + FileManager &FileMgr, + DiagnosticsEngine &Diag) { + using namespace llvm::sys; + + SourceManager SM(Diag, FileMgr); + FileID FID = SM.createFileID(FE, SourceLocation(), SrcMgr::C_User); + LangOptions LangOpts; + edit::EditedSource Editor(SM, LangOpts); + for (ArrayRef<EditEntry>::iterator + I = Edits.begin(), E = Edits.end(); I != E; ++I) { + const EditEntry &Entry = *I; + assert(Entry.File == FE); + SourceLocation Loc = + SM.getLocForStartOfFile(FID).getLocWithOffset(Entry.Offset); + CharSourceRange Range; + if (Entry.RemoveLen != 0) { + Range = CharSourceRange::getCharRange(Loc, + Loc.getLocWithOffset(Entry.RemoveLen)); + } + + edit::Commit commit(Editor); + if (Range.isInvalid()) { + commit.insert(Loc, Entry.Text); + } else if (Entry.Text.empty()) { + commit.remove(Range); + } else { + commit.replace(Range, Entry.Text); + } + Editor.commit(commit); + } + + Rewriter rewriter(SM, LangOpts); + RewritesReceiver Rec(rewriter); + Editor.applyRewrites(Rec); + + const RewriteBuffer *Buf = rewriter.getRewriteBufferFor(FID); + SmallString<512> NewText; + llvm::raw_svector_ostream OS(NewText); + Buf->write(OS); + + SmallString<64> TempPath; + int FD; + if (fs::createTemporaryFile(path::filename(FE->getName()), + path::extension(FE->getName()).drop_front(), FD, + TempPath)) { + reportDiag("Could not create file: " + TempPath.str(), Diag); + return std::string(); + } + + llvm::raw_fd_ostream TmpOut(FD, /*shouldClose=*/true); + TmpOut.write(NewText.data(), NewText.size()); + TmpOut.close(); + + return TempPath.str(); +} + +bool arcmt::getFileRemappingsFromFileList( + std::vector<std::pair<std::string,std::string> > &remap, + ArrayRef<StringRef> remapFiles, + DiagnosticConsumer *DiagClient) { + bool hasErrorOccurred = false; + + FileSystemOptions FSOpts; + FileManager FileMgr(FSOpts); + RemapFileParser Parser(FileMgr); + + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags( + new DiagnosticsEngine(DiagID, new DiagnosticOptions, + DiagClient, /*ShouldOwnClient=*/false)); + + typedef llvm::DenseMap<const FileEntry *, std::vector<EditEntry> > + FileEditEntriesTy; + FileEditEntriesTy FileEditEntries; + + llvm::DenseSet<EditEntry> EntriesSet; + + for (ArrayRef<StringRef>::iterator + I = remapFiles.begin(), E = remapFiles.end(); I != E; ++I) { + SmallVector<EditEntry, 16> Entries; + if (Parser.parse(*I, Entries)) + continue; + + for (SmallVectorImpl<EditEntry>::iterator + EI = Entries.begin(), EE = Entries.end(); EI != EE; ++EI) { + EditEntry &Entry = *EI; + if (!Entry.File) + continue; + std::pair<llvm::DenseSet<EditEntry>::iterator, bool> + Insert = EntriesSet.insert(Entry); + if (!Insert.second) + continue; + + FileEditEntries[Entry.File].push_back(Entry); + } + } + + for (FileEditEntriesTy::iterator + I = FileEditEntries.begin(), E = FileEditEntries.end(); I != E; ++I) { + std::string TempFile = applyEditsToTemp(I->first, I->second, + FileMgr, *Diags); + if (TempFile.empty()) { + hasErrorOccurred = true; + continue; + } + + remap.emplace_back(I->first->getName(), TempFile); + } + + return hasErrorOccurred; +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/PlistReporter.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/PlistReporter.cpp new file mode 100644 index 0000000..9a51690 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/PlistReporter.cpp @@ -0,0 +1,126 @@ +//===--- PlistReporter.cpp - ARC Migrate Tool Plist Reporter ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Internals.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/PlistSupport.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +using namespace clang; +using namespace arcmt; +using namespace markup; + +static StringRef getLevelName(DiagnosticsEngine::Level Level) { + switch (Level) { + case DiagnosticsEngine::Ignored: + llvm_unreachable("ignored"); + case DiagnosticsEngine::Note: + return "note"; + case DiagnosticsEngine::Remark: + case DiagnosticsEngine::Warning: + return "warning"; + case DiagnosticsEngine::Fatal: + case DiagnosticsEngine::Error: + return "error"; + } + llvm_unreachable("Invalid DiagnosticsEngine level!"); +} + +void arcmt::writeARCDiagsToPlist(const std::string &outPath, + ArrayRef<StoredDiagnostic> diags, + SourceManager &SM, + const LangOptions &LangOpts) { + DiagnosticIDs DiagIDs; + + // Build up a set of FIDs that we use by scanning the locations and + // ranges of the diagnostics. + FIDMap FM; + SmallVector<FileID, 10> Fids; + + for (ArrayRef<StoredDiagnostic>::iterator + I = diags.begin(), E = diags.end(); I != E; ++I) { + const StoredDiagnostic &D = *I; + + AddFID(FM, Fids, SM, D.getLocation()); + + for (StoredDiagnostic::range_iterator + RI = D.range_begin(), RE = D.range_end(); RI != RE; ++RI) { + AddFID(FM, Fids, SM, RI->getBegin()); + AddFID(FM, Fids, SM, RI->getEnd()); + } + } + + std::error_code EC; + llvm::raw_fd_ostream o(outPath, EC, llvm::sys::fs::F_Text); + if (EC) { + llvm::errs() << "error: could not create file: " << outPath << '\n'; + return; + } + + EmitPlistHeader(o); + + // Write the root object: a <dict> containing... + // - "files", an <array> mapping from FIDs to file names + // - "diagnostics", an <array> containing the diagnostics + o << "<dict>\n" + " <key>files</key>\n" + " <array>\n"; + + for (FileID FID : Fids) + EmitString(o << " ", SM.getFileEntryForID(FID)->getName()) << '\n'; + + o << " </array>\n" + " <key>diagnostics</key>\n" + " <array>\n"; + + for (ArrayRef<StoredDiagnostic>::iterator + DI = diags.begin(), DE = diags.end(); DI != DE; ++DI) { + + const StoredDiagnostic &D = *DI; + + if (D.getLevel() == DiagnosticsEngine::Ignored) + continue; + + o << " <dict>\n"; + + // Output the diagnostic. + o << " <key>description</key>"; + EmitString(o, D.getMessage()) << '\n'; + o << " <key>category</key>"; + EmitString(o, DiagIDs.getCategoryNameFromID( + DiagIDs.getCategoryNumberForDiag(D.getID()))) << '\n'; + o << " <key>type</key>"; + EmitString(o, getLevelName(D.getLevel())) << '\n'; + + // Output the location of the bug. + o << " <key>location</key>\n"; + EmitLocation(o, SM, D.getLocation(), FM, 2); + + // Output the ranges (if any). + if (!D.getRanges().empty()) { + o << " <key>ranges</key>\n"; + o << " <array>\n"; + for (auto &R : D.getRanges()) { + CharSourceRange ExpansionRange(SM.getExpansionRange(R.getAsRange()), + R.isTokenRange()); + EmitRange(o, SM, Lexer::getAsCharRange(ExpansionRange, SM, LangOpts), + FM, 4); + } + o << " </array>\n"; + } + + // Close up the entry. + o << " </dict>\n"; + } + + o << " </array>\n"; + + // Finish. + o << "</dict>\n</plist>"; +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransAPIUses.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransAPIUses.cpp new file mode 100644 index 0000000..40c8a07 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransAPIUses.cpp @@ -0,0 +1,108 @@ +//===--- TransAPIUses.cpp - Transformations to ARC mode -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// checkAPIUses: +// +// Emits error/fix with some API uses that are obsolete or not safe in ARC mode: +// +// - NSInvocation's [get/set]ReturnValue and [get/set]Argument are only safe +// with __unsafe_unretained objects. +// - Calling -zone gets replaced with 'nil'. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/Sema/SemaDiagnostic.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class APIChecker : public RecursiveASTVisitor<APIChecker> { + MigrationPass &Pass; + + Selector getReturnValueSel, setReturnValueSel; + Selector getArgumentSel, setArgumentSel; + + Selector zoneSel; +public: + APIChecker(MigrationPass &pass) : Pass(pass) { + SelectorTable &sels = Pass.Ctx.Selectors; + IdentifierTable &ids = Pass.Ctx.Idents; + getReturnValueSel = sels.getUnarySelector(&ids.get("getReturnValue")); + setReturnValueSel = sels.getUnarySelector(&ids.get("setReturnValue")); + + IdentifierInfo *selIds[2]; + selIds[0] = &ids.get("getArgument"); + selIds[1] = &ids.get("atIndex"); + getArgumentSel = sels.getSelector(2, selIds); + selIds[0] = &ids.get("setArgument"); + setArgumentSel = sels.getSelector(2, selIds); + + zoneSel = sels.getNullarySelector(&ids.get("zone")); + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *E) { + // NSInvocation. + if (E->isInstanceMessage() && + E->getReceiverInterface() && + E->getReceiverInterface()->getName() == "NSInvocation") { + StringRef selName; + if (E->getSelector() == getReturnValueSel) + selName = "getReturnValue"; + else if (E->getSelector() == setReturnValueSel) + selName = "setReturnValue"; + else if (E->getSelector() == getArgumentSel) + selName = "getArgument"; + else if (E->getSelector() == setArgumentSel) + selName = "setArgument"; + else + return true; + + Expr *parm = E->getArg(0)->IgnoreParenCasts(); + QualType pointee = parm->getType()->getPointeeType(); + if (pointee.isNull()) + return true; + + if (pointee.getObjCLifetime() > Qualifiers::OCL_ExplicitNone) + Pass.TA.report(parm->getLocStart(), + diag::err_arcmt_nsinvocation_ownership, + parm->getSourceRange()) + << selName; + + return true; + } + + // -zone. + if (E->isInstanceMessage() && + E->getInstanceReceiver() && + E->getSelector() == zoneSel && + Pass.TA.hasDiagnostic(diag::err_unavailable, + diag::err_unavailable_message, + E->getSelectorLoc(0))) { + // Calling -zone is meaningless in ARC, change it to nil. + Transaction Trans(Pass.TA); + Pass.TA.clearDiagnostic(diag::err_unavailable, + diag::err_unavailable_message, + E->getSelectorLoc(0)); + Pass.TA.replace(E->getSourceRange(), getNilString(Pass)); + } + return true; + } +}; + +} // anonymous namespace + +void trans::checkAPIUses(MigrationPass &pass) { + APIChecker(pass).TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransARCAssign.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransARCAssign.cpp new file mode 100644 index 0000000..80bfd22 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransARCAssign.cpp @@ -0,0 +1,78 @@ +//===--- TransARCAssign.cpp - Transformations to ARC mode -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// makeAssignARCSafe: +// +// Add '__strong' where appropriate. +// +// for (id x in collection) { +// x = 0; +// } +// ----> +// for (__strong id x in collection) { +// x = 0; +// } +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/Sema/SemaDiagnostic.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class ARCAssignChecker : public RecursiveASTVisitor<ARCAssignChecker> { + MigrationPass &Pass; + llvm::DenseSet<VarDecl *> ModifiedVars; + +public: + ARCAssignChecker(MigrationPass &pass) : Pass(pass) { } + + bool VisitBinaryOperator(BinaryOperator *Exp) { + if (Exp->getType()->isDependentType()) + return true; + + Expr *E = Exp->getLHS(); + SourceLocation OrigLoc = E->getExprLoc(); + SourceLocation Loc = OrigLoc; + DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(E->IgnoreParenCasts()); + if (declRef && isa<VarDecl>(declRef->getDecl())) { + ASTContext &Ctx = Pass.Ctx; + Expr::isModifiableLvalueResult IsLV = E->isModifiableLvalue(Ctx, &Loc); + if (IsLV != Expr::MLV_ConstQualified) + return true; + VarDecl *var = cast<VarDecl>(declRef->getDecl()); + if (var->isARCPseudoStrong()) { + Transaction Trans(Pass.TA); + if (Pass.TA.clearDiagnostic(diag::err_typecheck_arr_assign_enumeration, + Exp->getOperatorLoc())) { + if (!ModifiedVars.count(var)) { + TypeLoc TLoc = var->getTypeSourceInfo()->getTypeLoc(); + Pass.TA.insert(TLoc.getBeginLoc(), "__strong "); + ModifiedVars.insert(var); + } + } + } + } + + return true; + } +}; + +} // anonymous namespace + +void trans::makeAssignARCSafe(MigrationPass &pass) { + ARCAssignChecker assignCheck(pass); + assignCheck.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransAutoreleasePool.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransAutoreleasePool.cpp new file mode 100644 index 0000000..a8a99fa --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransAutoreleasePool.cpp @@ -0,0 +1,435 @@ +//===--- TransAutoreleasePool.cpp - Transformations to ARC mode -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// rewriteAutoreleasePool: +// +// Calls to NSAutoreleasePools will be rewritten as an @autorelease scope. +// +// NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +// ... +// [pool release]; +// ----> +// @autorelease { +// ... +// } +// +// An NSAutoreleasePool will not be touched if: +// - There is not a corresponding -release/-drain in the same scope +// - Not all references of the NSAutoreleasePool variable can be removed +// - There is a variable that is declared inside the intended @autorelease scope +// which is also used outside it. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Sema/SemaDiagnostic.h" +#include <map> + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class ReleaseCollector : public RecursiveASTVisitor<ReleaseCollector> { + Decl *Dcl; + SmallVectorImpl<ObjCMessageExpr *> &Releases; + +public: + ReleaseCollector(Decl *D, SmallVectorImpl<ObjCMessageExpr *> &releases) + : Dcl(D), Releases(releases) { } + + bool VisitObjCMessageExpr(ObjCMessageExpr *E) { + if (!E->isInstanceMessage()) + return true; + if (E->getMethodFamily() != OMF_release) + return true; + Expr *instance = E->getInstanceReceiver()->IgnoreParenCasts(); + if (DeclRefExpr *DE = dyn_cast<DeclRefExpr>(instance)) { + if (DE->getDecl() == Dcl) + Releases.push_back(E); + } + return true; + } +}; + +} + +namespace { + +class AutoreleasePoolRewriter + : public RecursiveASTVisitor<AutoreleasePoolRewriter> { +public: + AutoreleasePoolRewriter(MigrationPass &pass) + : Body(nullptr), Pass(pass) { + PoolII = &pass.Ctx.Idents.get("NSAutoreleasePool"); + DrainSel = pass.Ctx.Selectors.getNullarySelector( + &pass.Ctx.Idents.get("drain")); + } + + void transformBody(Stmt *body, Decl *ParentD) { + Body = body; + TraverseStmt(body); + } + + ~AutoreleasePoolRewriter() { + SmallVector<VarDecl *, 8> VarsToHandle; + + for (std::map<VarDecl *, PoolVarInfo>::iterator + I = PoolVars.begin(), E = PoolVars.end(); I != E; ++I) { + VarDecl *var = I->first; + PoolVarInfo &info = I->second; + + // Check that we can handle/rewrite all references of the pool. + + clearRefsIn(info.Dcl, info.Refs); + for (SmallVectorImpl<PoolScope>::iterator + scpI = info.Scopes.begin(), + scpE = info.Scopes.end(); scpI != scpE; ++scpI) { + PoolScope &scope = *scpI; + clearRefsIn(*scope.Begin, info.Refs); + clearRefsIn(*scope.End, info.Refs); + clearRefsIn(scope.Releases.begin(), scope.Releases.end(), info.Refs); + } + + // Even if one reference is not handled we will not do anything about that + // pool variable. + if (info.Refs.empty()) + VarsToHandle.push_back(var); + } + + for (unsigned i = 0, e = VarsToHandle.size(); i != e; ++i) { + PoolVarInfo &info = PoolVars[VarsToHandle[i]]; + + Transaction Trans(Pass.TA); + + clearUnavailableDiags(info.Dcl); + Pass.TA.removeStmt(info.Dcl); + + // Add "@autoreleasepool { }" + for (SmallVectorImpl<PoolScope>::iterator + scpI = info.Scopes.begin(), + scpE = info.Scopes.end(); scpI != scpE; ++scpI) { + PoolScope &scope = *scpI; + clearUnavailableDiags(*scope.Begin); + clearUnavailableDiags(*scope.End); + if (scope.IsFollowedBySimpleReturnStmt) { + // Include the return in the scope. + Pass.TA.replaceStmt(*scope.Begin, "@autoreleasepool {"); + Pass.TA.removeStmt(*scope.End); + Stmt::child_iterator retI = scope.End; + ++retI; + SourceLocation afterSemi = findLocationAfterSemi((*retI)->getLocEnd(), + Pass.Ctx); + assert(afterSemi.isValid() && + "Didn't we check before setting IsFollowedBySimpleReturnStmt " + "to true?"); + Pass.TA.insertAfterToken(afterSemi, "\n}"); + Pass.TA.increaseIndentation( + SourceRange(scope.getIndentedRange().getBegin(), + (*retI)->getLocEnd()), + scope.CompoundParent->getLocStart()); + } else { + Pass.TA.replaceStmt(*scope.Begin, "@autoreleasepool {"); + Pass.TA.replaceStmt(*scope.End, "}"); + Pass.TA.increaseIndentation(scope.getIndentedRange(), + scope.CompoundParent->getLocStart()); + } + } + + // Remove rest of pool var references. + for (SmallVectorImpl<PoolScope>::iterator + scpI = info.Scopes.begin(), + scpE = info.Scopes.end(); scpI != scpE; ++scpI) { + PoolScope &scope = *scpI; + for (SmallVectorImpl<ObjCMessageExpr *>::iterator + relI = scope.Releases.begin(), + relE = scope.Releases.end(); relI != relE; ++relI) { + clearUnavailableDiags(*relI); + Pass.TA.removeStmt(*relI); + } + } + } + } + + bool VisitCompoundStmt(CompoundStmt *S) { + SmallVector<PoolScope, 4> Scopes; + + for (Stmt::child_iterator + I = S->body_begin(), E = S->body_end(); I != E; ++I) { + Stmt *child = getEssential(*I); + if (DeclStmt *DclS = dyn_cast<DeclStmt>(child)) { + if (DclS->isSingleDecl()) { + if (VarDecl *VD = dyn_cast<VarDecl>(DclS->getSingleDecl())) { + if (isNSAutoreleasePool(VD->getType())) { + PoolVarInfo &info = PoolVars[VD]; + info.Dcl = DclS; + collectRefs(VD, S, info.Refs); + // Does this statement follow the pattern: + // NSAutoreleasePool * pool = [NSAutoreleasePool new]; + if (isPoolCreation(VD->getInit())) { + Scopes.push_back(PoolScope()); + Scopes.back().PoolVar = VD; + Scopes.back().CompoundParent = S; + Scopes.back().Begin = I; + } + } + } + } + } else if (BinaryOperator *bop = dyn_cast<BinaryOperator>(child)) { + if (DeclRefExpr *dref = dyn_cast<DeclRefExpr>(bop->getLHS())) { + if (VarDecl *VD = dyn_cast<VarDecl>(dref->getDecl())) { + // Does this statement follow the pattern: + // pool = [NSAutoreleasePool new]; + if (isNSAutoreleasePool(VD->getType()) && + isPoolCreation(bop->getRHS())) { + Scopes.push_back(PoolScope()); + Scopes.back().PoolVar = VD; + Scopes.back().CompoundParent = S; + Scopes.back().Begin = I; + } + } + } + } + + if (Scopes.empty()) + continue; + + if (isPoolDrain(Scopes.back().PoolVar, child)) { + PoolScope &scope = Scopes.back(); + scope.End = I; + handlePoolScope(scope, S); + Scopes.pop_back(); + } + } + return true; + } + +private: + void clearUnavailableDiags(Stmt *S) { + if (S) + Pass.TA.clearDiagnostic(diag::err_unavailable, + diag::err_unavailable_message, + S->getSourceRange()); + } + + struct PoolScope { + VarDecl *PoolVar; + CompoundStmt *CompoundParent; + Stmt::child_iterator Begin; + Stmt::child_iterator End; + bool IsFollowedBySimpleReturnStmt; + SmallVector<ObjCMessageExpr *, 4> Releases; + + PoolScope() : PoolVar(nullptr), CompoundParent(nullptr), Begin(), End(), + IsFollowedBySimpleReturnStmt(false) { } + + SourceRange getIndentedRange() const { + Stmt::child_iterator rangeS = Begin; + ++rangeS; + if (rangeS == End) + return SourceRange(); + Stmt::child_iterator rangeE = Begin; + for (Stmt::child_iterator I = rangeS; I != End; ++I) + ++rangeE; + return SourceRange((*rangeS)->getLocStart(), (*rangeE)->getLocEnd()); + } + }; + + class NameReferenceChecker : public RecursiveASTVisitor<NameReferenceChecker>{ + ASTContext &Ctx; + SourceRange ScopeRange; + SourceLocation &referenceLoc, &declarationLoc; + + public: + NameReferenceChecker(ASTContext &ctx, PoolScope &scope, + SourceLocation &referenceLoc, + SourceLocation &declarationLoc) + : Ctx(ctx), referenceLoc(referenceLoc), + declarationLoc(declarationLoc) { + ScopeRange = SourceRange((*scope.Begin)->getLocStart(), + (*scope.End)->getLocStart()); + } + + bool VisitDeclRefExpr(DeclRefExpr *E) { + return checkRef(E->getLocation(), E->getDecl()->getLocation()); + } + + bool VisitTypedefTypeLoc(TypedefTypeLoc TL) { + return checkRef(TL.getBeginLoc(), TL.getTypedefNameDecl()->getLocation()); + } + + bool VisitTagTypeLoc(TagTypeLoc TL) { + return checkRef(TL.getBeginLoc(), TL.getDecl()->getLocation()); + } + + private: + bool checkRef(SourceLocation refLoc, SourceLocation declLoc) { + if (isInScope(declLoc)) { + referenceLoc = refLoc; + declarationLoc = declLoc; + return false; + } + return true; + } + + bool isInScope(SourceLocation loc) { + if (loc.isInvalid()) + return false; + + SourceManager &SM = Ctx.getSourceManager(); + if (SM.isBeforeInTranslationUnit(loc, ScopeRange.getBegin())) + return false; + return SM.isBeforeInTranslationUnit(loc, ScopeRange.getEnd()); + } + }; + + void handlePoolScope(PoolScope &scope, CompoundStmt *compoundS) { + // Check that all names declared inside the scope are not used + // outside the scope. + { + bool nameUsedOutsideScope = false; + SourceLocation referenceLoc, declarationLoc; + Stmt::child_iterator SI = scope.End, SE = compoundS->body_end(); + ++SI; + // Check if the autoreleasepool scope is followed by a simple return + // statement, in which case we will include the return in the scope. + if (SI != SE) + if (ReturnStmt *retS = dyn_cast<ReturnStmt>(*SI)) + if ((retS->getRetValue() == nullptr || + isa<DeclRefExpr>(retS->getRetValue()->IgnoreParenCasts())) && + findLocationAfterSemi(retS->getLocEnd(), Pass.Ctx).isValid()) { + scope.IsFollowedBySimpleReturnStmt = true; + ++SI; // the return will be included in scope, don't check it. + } + + for (; SI != SE; ++SI) { + nameUsedOutsideScope = !NameReferenceChecker(Pass.Ctx, scope, + referenceLoc, + declarationLoc).TraverseStmt(*SI); + if (nameUsedOutsideScope) + break; + } + + // If not all references were cleared it means some variables/typenames/etc + // declared inside the pool scope are used outside of it. + // We won't try to rewrite the pool. + if (nameUsedOutsideScope) { + Pass.TA.reportError("a name is referenced outside the " + "NSAutoreleasePool scope that it was declared in", referenceLoc); + Pass.TA.reportNote("name declared here", declarationLoc); + Pass.TA.reportNote("intended @autoreleasepool scope begins here", + (*scope.Begin)->getLocStart()); + Pass.TA.reportNote("intended @autoreleasepool scope ends here", + (*scope.End)->getLocStart()); + return; + } + } + + // Collect all releases of the pool; they will be removed. + { + ReleaseCollector releaseColl(scope.PoolVar, scope.Releases); + Stmt::child_iterator I = scope.Begin; + ++I; + for (; I != scope.End; ++I) + releaseColl.TraverseStmt(*I); + } + + PoolVars[scope.PoolVar].Scopes.push_back(scope); + } + + bool isPoolCreation(Expr *E) { + if (!E) return false; + E = getEssential(E); + ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(E); + if (!ME) return false; + if (ME->getMethodFamily() == OMF_new && + ME->getReceiverKind() == ObjCMessageExpr::Class && + isNSAutoreleasePool(ME->getReceiverInterface())) + return true; + if (ME->getReceiverKind() == ObjCMessageExpr::Instance && + ME->getMethodFamily() == OMF_init) { + Expr *rec = getEssential(ME->getInstanceReceiver()); + if (ObjCMessageExpr *recME = dyn_cast_or_null<ObjCMessageExpr>(rec)) { + if (recME->getMethodFamily() == OMF_alloc && + recME->getReceiverKind() == ObjCMessageExpr::Class && + isNSAutoreleasePool(recME->getReceiverInterface())) + return true; + } + } + + return false; + } + + bool isPoolDrain(VarDecl *poolVar, Stmt *S) { + if (!S) return false; + S = getEssential(S); + ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S); + if (!ME) return false; + if (ME->getReceiverKind() == ObjCMessageExpr::Instance) { + Expr *rec = getEssential(ME->getInstanceReceiver()); + if (DeclRefExpr *dref = dyn_cast<DeclRefExpr>(rec)) + if (dref->getDecl() == poolVar) + return ME->getMethodFamily() == OMF_release || + ME->getSelector() == DrainSel; + } + + return false; + } + + bool isNSAutoreleasePool(ObjCInterfaceDecl *IDecl) { + return IDecl && IDecl->getIdentifier() == PoolII; + } + + bool isNSAutoreleasePool(QualType Ty) { + QualType pointee = Ty->getPointeeType(); + if (pointee.isNull()) + return false; + if (const ObjCInterfaceType *interT = pointee->getAs<ObjCInterfaceType>()) + return isNSAutoreleasePool(interT->getDecl()); + return false; + } + + static Expr *getEssential(Expr *E) { + return cast<Expr>(getEssential((Stmt*)E)); + } + static Stmt *getEssential(Stmt *S) { + if (ExprWithCleanups *EWC = dyn_cast<ExprWithCleanups>(S)) + S = EWC->getSubExpr(); + if (Expr *E = dyn_cast<Expr>(S)) + S = E->IgnoreParenCasts(); + return S; + } + + Stmt *Body; + MigrationPass &Pass; + + IdentifierInfo *PoolII; + Selector DrainSel; + + struct PoolVarInfo { + DeclStmt *Dcl; + ExprSet Refs; + SmallVector<PoolScope, 2> Scopes; + + PoolVarInfo() : Dcl(nullptr) { } + }; + + std::map<VarDecl *, PoolVarInfo> PoolVars; +}; + +} // anonymous namespace + +void trans::rewriteAutoreleasePool(MigrationPass &pass) { + BodyTransform<AutoreleasePoolRewriter> trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransBlockObjCVariable.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransBlockObjCVariable.cpp new file mode 100644 index 0000000..fac6a84 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransBlockObjCVariable.cpp @@ -0,0 +1,147 @@ +//===--- TransBlockObjCVariable.cpp - Transformations to ARC mode ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// rewriteBlockObjCVariable: +// +// Adding __block to an obj-c variable could be either because the variable +// is used for output storage or the user wanted to break a retain cycle. +// This transformation checks whether a reference of the variable for the block +// is actually needed (it is assigned to or its address is taken) or not. +// If the reference is not needed it will assume __block was added to break a +// cycle so it will remove '__block' and add __weak/__unsafe_unretained. +// e.g +// +// __block Foo *x; +// bar(^ { [x cake]; }); +// ----> +// __weak Foo *x; +// bar(^ { [x cake]; }); +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/Basic/SourceManager.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class RootBlockObjCVarRewriter : + public RecursiveASTVisitor<RootBlockObjCVarRewriter> { + llvm::DenseSet<VarDecl *> &VarsToChange; + + class BlockVarChecker : public RecursiveASTVisitor<BlockVarChecker> { + VarDecl *Var; + + typedef RecursiveASTVisitor<BlockVarChecker> base; + public: + BlockVarChecker(VarDecl *var) : Var(var) { } + + bool TraverseImplicitCastExpr(ImplicitCastExpr *castE) { + if (DeclRefExpr * + ref = dyn_cast<DeclRefExpr>(castE->getSubExpr())) { + if (ref->getDecl() == Var) { + if (castE->getCastKind() == CK_LValueToRValue) + return true; // Using the value of the variable. + if (castE->getCastKind() == CK_NoOp && castE->isLValue() && + Var->getASTContext().getLangOpts().CPlusPlus) + return true; // Binding to const C++ reference. + } + } + + return base::TraverseImplicitCastExpr(castE); + } + + bool VisitDeclRefExpr(DeclRefExpr *E) { + if (E->getDecl() == Var) + return false; // The reference of the variable, and not just its value, + // is needed. + return true; + } + }; + +public: + RootBlockObjCVarRewriter(llvm::DenseSet<VarDecl *> &VarsToChange) + : VarsToChange(VarsToChange) { } + + bool VisitBlockDecl(BlockDecl *block) { + SmallVector<VarDecl *, 4> BlockVars; + + for (const auto &I : block->captures()) { + VarDecl *var = I.getVariable(); + if (I.isByRef() && + var->getType()->isObjCObjectPointerType() && + isImplicitStrong(var->getType())) { + BlockVars.push_back(var); + } + } + + for (unsigned i = 0, e = BlockVars.size(); i != e; ++i) { + VarDecl *var = BlockVars[i]; + + BlockVarChecker checker(var); + bool onlyValueOfVarIsNeeded = checker.TraverseStmt(block->getBody()); + if (onlyValueOfVarIsNeeded) + VarsToChange.insert(var); + else + VarsToChange.erase(var); + } + + return true; + } + +private: + bool isImplicitStrong(QualType ty) { + if (isa<AttributedType>(ty.getTypePtr())) + return false; + return ty.getLocalQualifiers().getObjCLifetime() == Qualifiers::OCL_Strong; + } +}; + +class BlockObjCVarRewriter : public RecursiveASTVisitor<BlockObjCVarRewriter> { + llvm::DenseSet<VarDecl *> &VarsToChange; + +public: + BlockObjCVarRewriter(llvm::DenseSet<VarDecl *> &VarsToChange) + : VarsToChange(VarsToChange) { } + + bool TraverseBlockDecl(BlockDecl *block) { + RootBlockObjCVarRewriter(VarsToChange).TraverseDecl(block); + return true; + } +}; + +} // anonymous namespace + +void BlockObjCVariableTraverser::traverseBody(BodyContext &BodyCtx) { + MigrationPass &Pass = BodyCtx.getMigrationContext().Pass; + llvm::DenseSet<VarDecl *> VarsToChange; + + BlockObjCVarRewriter trans(VarsToChange); + trans.TraverseStmt(BodyCtx.getTopStmt()); + + for (llvm::DenseSet<VarDecl *>::iterator + I = VarsToChange.begin(), E = VarsToChange.end(); I != E; ++I) { + VarDecl *var = *I; + BlocksAttr *attr = var->getAttr<BlocksAttr>(); + if(!attr) + continue; + bool useWeak = canApplyWeak(Pass.Ctx, var->getType()); + SourceManager &SM = Pass.Ctx.getSourceManager(); + Transaction Trans(Pass.TA); + Pass.TA.replaceText(SM.getExpansionLoc(attr->getLocation()), + "__block", + useWeak ? "__weak" : "__unsafe_unretained"); + } +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransEmptyStatementsAndDealloc.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransEmptyStatementsAndDealloc.cpp new file mode 100644 index 0000000..d45d5d6 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransEmptyStatementsAndDealloc.cpp @@ -0,0 +1,251 @@ +//===--- TransEmptyStatements.cpp - Transformations to ARC mode -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// removeEmptyStatementsAndDealloc: +// +// Removes empty statements that are leftovers from previous transformations. +// e.g for +// +// [x retain]; +// +// removeRetainReleaseDealloc will leave an empty ";" that removeEmptyStatements +// will remove. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Basic/SourceManager.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +static bool isEmptyARCMTMacroStatement(NullStmt *S, + std::vector<SourceLocation> &MacroLocs, + ASTContext &Ctx) { + if (!S->hasLeadingEmptyMacro()) + return false; + + SourceLocation SemiLoc = S->getSemiLoc(); + if (SemiLoc.isInvalid() || SemiLoc.isMacroID()) + return false; + + if (MacroLocs.empty()) + return false; + + SourceManager &SM = Ctx.getSourceManager(); + std::vector<SourceLocation>::iterator + I = std::upper_bound(MacroLocs.begin(), MacroLocs.end(), SemiLoc, + BeforeThanCompare<SourceLocation>(SM)); + --I; + SourceLocation + AfterMacroLoc = I->getLocWithOffset(getARCMTMacroName().size()); + assert(AfterMacroLoc.isFileID()); + + if (AfterMacroLoc == SemiLoc) + return true; + + int RelOffs = 0; + if (!SM.isInSameSLocAddrSpace(AfterMacroLoc, SemiLoc, &RelOffs)) + return false; + if (RelOffs < 0) + return false; + + // We make the reasonable assumption that a semicolon after 100 characters + // means that it is not the next token after our macro. If this assumption + // fails it is not critical, we will just fail to clear out, e.g., an empty + // 'if'. + if (RelOffs - getARCMTMacroName().size() > 100) + return false; + + SourceLocation AfterMacroSemiLoc = findSemiAfterLocation(AfterMacroLoc, Ctx); + return AfterMacroSemiLoc == SemiLoc; +} + +namespace { + +/// \brief Returns true if the statement became empty due to previous +/// transformations. +class EmptyChecker : public StmtVisitor<EmptyChecker, bool> { + ASTContext &Ctx; + std::vector<SourceLocation> &MacroLocs; + +public: + EmptyChecker(ASTContext &ctx, std::vector<SourceLocation> ¯oLocs) + : Ctx(ctx), MacroLocs(macroLocs) { } + + bool VisitNullStmt(NullStmt *S) { + return isEmptyARCMTMacroStatement(S, MacroLocs, Ctx); + } + bool VisitCompoundStmt(CompoundStmt *S) { + if (S->body_empty()) + return false; // was already empty, not because of transformations. + for (auto *I : S->body()) + if (!Visit(I)) + return false; + return true; + } + bool VisitIfStmt(IfStmt *S) { + if (S->getConditionVariable()) + return false; + Expr *condE = S->getCond(); + if (!condE) + return false; + if (hasSideEffects(condE, Ctx)) + return false; + if (!S->getThen() || !Visit(S->getThen())) + return false; + return !S->getElse() || Visit(S->getElse()); + } + bool VisitWhileStmt(WhileStmt *S) { + if (S->getConditionVariable()) + return false; + Expr *condE = S->getCond(); + if (!condE) + return false; + if (hasSideEffects(condE, Ctx)) + return false; + if (!S->getBody()) + return false; + return Visit(S->getBody()); + } + bool VisitDoStmt(DoStmt *S) { + Expr *condE = S->getCond(); + if (!condE) + return false; + if (hasSideEffects(condE, Ctx)) + return false; + if (!S->getBody()) + return false; + return Visit(S->getBody()); + } + bool VisitObjCForCollectionStmt(ObjCForCollectionStmt *S) { + Expr *Exp = S->getCollection(); + if (!Exp) + return false; + if (hasSideEffects(Exp, Ctx)) + return false; + if (!S->getBody()) + return false; + return Visit(S->getBody()); + } + bool VisitObjCAutoreleasePoolStmt(ObjCAutoreleasePoolStmt *S) { + if (!S->getSubStmt()) + return false; + return Visit(S->getSubStmt()); + } +}; + +class EmptyStatementsRemover : + public RecursiveASTVisitor<EmptyStatementsRemover> { + MigrationPass &Pass; + +public: + EmptyStatementsRemover(MigrationPass &pass) : Pass(pass) { } + + bool TraverseStmtExpr(StmtExpr *E) { + CompoundStmt *S = E->getSubStmt(); + for (CompoundStmt::body_iterator + I = S->body_begin(), E = S->body_end(); I != E; ++I) { + if (I != E - 1) + check(*I); + TraverseStmt(*I); + } + return true; + } + + bool VisitCompoundStmt(CompoundStmt *S) { + for (auto *I : S->body()) + check(I); + return true; + } + + ASTContext &getContext() { return Pass.Ctx; } + +private: + void check(Stmt *S) { + if (!S) return; + if (EmptyChecker(Pass.Ctx, Pass.ARCMTMacroLocs).Visit(S)) { + Transaction Trans(Pass.TA); + Pass.TA.removeStmt(S); + } + } +}; + +} // anonymous namespace + +static bool isBodyEmpty(CompoundStmt *body, ASTContext &Ctx, + std::vector<SourceLocation> &MacroLocs) { + for (auto *I : body->body()) + if (!EmptyChecker(Ctx, MacroLocs).Visit(I)) + return false; + + return true; +} + +static void cleanupDeallocOrFinalize(MigrationPass &pass) { + ASTContext &Ctx = pass.Ctx; + TransformActions &TA = pass.TA; + DeclContext *DC = Ctx.getTranslationUnitDecl(); + Selector FinalizeSel = + Ctx.Selectors.getNullarySelector(&pass.Ctx.Idents.get("finalize")); + + typedef DeclContext::specific_decl_iterator<ObjCImplementationDecl> + impl_iterator; + for (impl_iterator I = impl_iterator(DC->decls_begin()), + E = impl_iterator(DC->decls_end()); I != E; ++I) { + ObjCMethodDecl *DeallocM = nullptr; + ObjCMethodDecl *FinalizeM = nullptr; + for (auto *MD : I->instance_methods()) { + if (!MD->hasBody()) + continue; + + if (MD->getMethodFamily() == OMF_dealloc) { + DeallocM = MD; + } else if (MD->isInstanceMethod() && MD->getSelector() == FinalizeSel) { + FinalizeM = MD; + } + } + + if (DeallocM) { + if (isBodyEmpty(DeallocM->getCompoundBody(), Ctx, pass.ARCMTMacroLocs)) { + Transaction Trans(TA); + TA.remove(DeallocM->getSourceRange()); + } + + if (FinalizeM) { + Transaction Trans(TA); + TA.remove(FinalizeM->getSourceRange()); + } + + } else if (FinalizeM) { + if (isBodyEmpty(FinalizeM->getCompoundBody(), Ctx, pass.ARCMTMacroLocs)) { + Transaction Trans(TA); + TA.remove(FinalizeM->getSourceRange()); + } else { + Transaction Trans(TA); + TA.replaceText(FinalizeM->getSelectorStartLoc(), "finalize", "dealloc"); + } + } + } +} + +void trans::removeEmptyStatementsAndDeallocFinalize(MigrationPass &pass) { + EmptyStatementsRemover(pass).TraverseDecl(pass.Ctx.getTranslationUnitDecl()); + + cleanupDeallocOrFinalize(pass); + + for (unsigned i = 0, e = pass.ARCMTMacroLocs.size(); i != e; ++i) { + Transaction Trans(pass.TA); + pass.TA.remove(pass.ARCMTMacroLocs[i]); + } +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransGCAttrs.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransGCAttrs.cpp new file mode 100644 index 0000000..2ae6b78 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransGCAttrs.cpp @@ -0,0 +1,355 @@ +//===--- TransGCAttrs.cpp - Transformations to ARC mode --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/TinyPtrVector.h" +#include "llvm/Support/SaveAndRestore.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +/// \brief Collects all the places where GC attributes __strong/__weak occur. +class GCAttrsCollector : public RecursiveASTVisitor<GCAttrsCollector> { + MigrationContext &MigrateCtx; + bool FullyMigratable; + std::vector<ObjCPropertyDecl *> &AllProps; + + typedef RecursiveASTVisitor<GCAttrsCollector> base; +public: + GCAttrsCollector(MigrationContext &ctx, + std::vector<ObjCPropertyDecl *> &AllProps) + : MigrateCtx(ctx), FullyMigratable(false), + AllProps(AllProps) { } + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitAttributedTypeLoc(AttributedTypeLoc TL) { + handleAttr(TL); + return true; + } + + bool TraverseDecl(Decl *D) { + if (!D || D->isImplicit()) + return true; + + SaveAndRestore<bool> Save(FullyMigratable, isMigratable(D)); + + if (ObjCPropertyDecl *PropD = dyn_cast<ObjCPropertyDecl>(D)) { + lookForAttribute(PropD, PropD->getTypeSourceInfo()); + AllProps.push_back(PropD); + } else if (DeclaratorDecl *DD = dyn_cast<DeclaratorDecl>(D)) { + lookForAttribute(DD, DD->getTypeSourceInfo()); + } + return base::TraverseDecl(D); + } + + void lookForAttribute(Decl *D, TypeSourceInfo *TInfo) { + if (!TInfo) + return; + TypeLoc TL = TInfo->getTypeLoc(); + while (TL) { + if (QualifiedTypeLoc QL = TL.getAs<QualifiedTypeLoc>()) { + TL = QL.getUnqualifiedLoc(); + } else if (AttributedTypeLoc Attr = TL.getAs<AttributedTypeLoc>()) { + if (handleAttr(Attr, D)) + break; + TL = Attr.getModifiedLoc(); + } else if (ArrayTypeLoc Arr = TL.getAs<ArrayTypeLoc>()) { + TL = Arr.getElementLoc(); + } else if (PointerTypeLoc PT = TL.getAs<PointerTypeLoc>()) { + TL = PT.getPointeeLoc(); + } else if (ReferenceTypeLoc RT = TL.getAs<ReferenceTypeLoc>()) + TL = RT.getPointeeLoc(); + else + break; + } + } + + bool handleAttr(AttributedTypeLoc TL, Decl *D = nullptr) { + if (TL.getAttrKind() != AttributedType::attr_objc_ownership) + return false; + + SourceLocation Loc = TL.getAttrNameLoc(); + unsigned RawLoc = Loc.getRawEncoding(); + if (MigrateCtx.AttrSet.count(RawLoc)) + return true; + + ASTContext &Ctx = MigrateCtx.Pass.Ctx; + SourceManager &SM = Ctx.getSourceManager(); + if (Loc.isMacroID()) + Loc = SM.getImmediateExpansionRange(Loc).first; + SmallString<32> Buf; + bool Invalid = false; + StringRef Spell = Lexer::getSpelling( + SM.getSpellingLoc(TL.getAttrEnumOperandLoc()), + Buf, SM, Ctx.getLangOpts(), &Invalid); + if (Invalid) + return false; + MigrationContext::GCAttrOccurrence::AttrKind Kind; + if (Spell == "strong") + Kind = MigrationContext::GCAttrOccurrence::Strong; + else if (Spell == "weak") + Kind = MigrationContext::GCAttrOccurrence::Weak; + else + return false; + + MigrateCtx.AttrSet.insert(RawLoc); + MigrateCtx.GCAttrs.push_back(MigrationContext::GCAttrOccurrence()); + MigrationContext::GCAttrOccurrence &Attr = MigrateCtx.GCAttrs.back(); + + Attr.Kind = Kind; + Attr.Loc = Loc; + Attr.ModifiedType = TL.getModifiedLoc().getType(); + Attr.Dcl = D; + Attr.FullyMigratable = FullyMigratable; + return true; + } + + bool isMigratable(Decl *D) { + if (isa<TranslationUnitDecl>(D)) + return false; + + if (isInMainFile(D)) + return true; + + if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) + return FD->hasBody(); + + if (ObjCContainerDecl *ContD = dyn_cast<ObjCContainerDecl>(D)) + return hasObjCImpl(ContD); + + if (CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(D)) { + for (const auto *MI : RD->methods()) { + if (MI->isOutOfLine()) + return true; + } + return false; + } + + return isMigratable(cast<Decl>(D->getDeclContext())); + } + + static bool hasObjCImpl(Decl *D) { + if (!D) + return false; + if (ObjCContainerDecl *ContD = dyn_cast<ObjCContainerDecl>(D)) { + if (ObjCInterfaceDecl *ID = dyn_cast<ObjCInterfaceDecl>(ContD)) + return ID->getImplementation() != nullptr; + if (ObjCCategoryDecl *CD = dyn_cast<ObjCCategoryDecl>(ContD)) + return CD->getImplementation() != nullptr; + return isa<ObjCImplDecl>(ContD); + } + return false; + } + + bool isInMainFile(Decl *D) { + if (!D) + return false; + + for (auto I : D->redecls()) + if (!isInMainFile(I->getLocation())) + return false; + + return true; + } + + bool isInMainFile(SourceLocation Loc) { + if (Loc.isInvalid()) + return false; + + SourceManager &SM = MigrateCtx.Pass.Ctx.getSourceManager(); + return SM.isInFileID(SM.getExpansionLoc(Loc), SM.getMainFileID()); + } +}; + +} // anonymous namespace + +static void errorForGCAttrsOnNonObjC(MigrationContext &MigrateCtx) { + TransformActions &TA = MigrateCtx.Pass.TA; + + for (unsigned i = 0, e = MigrateCtx.GCAttrs.size(); i != e; ++i) { + MigrationContext::GCAttrOccurrence &Attr = MigrateCtx.GCAttrs[i]; + if (Attr.FullyMigratable && Attr.Dcl) { + if (Attr.ModifiedType.isNull()) + continue; + if (!Attr.ModifiedType->isObjCRetainableType()) { + TA.reportError("GC managed memory will become unmanaged in ARC", + Attr.Loc); + } + } + } +} + +static void checkWeakGCAttrs(MigrationContext &MigrateCtx) { + TransformActions &TA = MigrateCtx.Pass.TA; + + for (unsigned i = 0, e = MigrateCtx.GCAttrs.size(); i != e; ++i) { + MigrationContext::GCAttrOccurrence &Attr = MigrateCtx.GCAttrs[i]; + if (Attr.Kind == MigrationContext::GCAttrOccurrence::Weak) { + if (Attr.ModifiedType.isNull() || + !Attr.ModifiedType->isObjCRetainableType()) + continue; + if (!canApplyWeak(MigrateCtx.Pass.Ctx, Attr.ModifiedType, + /*AllowOnUnknownClass=*/true)) { + Transaction Trans(TA); + if (!MigrateCtx.RemovedAttrSet.count(Attr.Loc.getRawEncoding())) + TA.replaceText(Attr.Loc, "__weak", "__unsafe_unretained"); + TA.clearDiagnostic(diag::err_arc_weak_no_runtime, + diag::err_arc_unsupported_weak_class, + Attr.Loc); + } + } + } +} + +typedef llvm::TinyPtrVector<ObjCPropertyDecl *> IndivPropsTy; + +static void checkAllAtProps(MigrationContext &MigrateCtx, + SourceLocation AtLoc, + IndivPropsTy &IndProps) { + if (IndProps.empty()) + return; + + for (IndivPropsTy::iterator + PI = IndProps.begin(), PE = IndProps.end(); PI != PE; ++PI) { + QualType T = (*PI)->getType(); + if (T.isNull() || !T->isObjCRetainableType()) + return; + } + + SmallVector<std::pair<AttributedTypeLoc, ObjCPropertyDecl *>, 4> ATLs; + bool hasWeak = false, hasStrong = false; + ObjCPropertyDecl::PropertyAttributeKind + Attrs = ObjCPropertyDecl::OBJC_PR_noattr; + for (IndivPropsTy::iterator + PI = IndProps.begin(), PE = IndProps.end(); PI != PE; ++PI) { + ObjCPropertyDecl *PD = *PI; + Attrs = PD->getPropertyAttributesAsWritten(); + TypeSourceInfo *TInfo = PD->getTypeSourceInfo(); + if (!TInfo) + return; + TypeLoc TL = TInfo->getTypeLoc(); + if (AttributedTypeLoc ATL = + TL.getAs<AttributedTypeLoc>()) { + ATLs.push_back(std::make_pair(ATL, PD)); + if (TInfo->getType().getObjCLifetime() == Qualifiers::OCL_Weak) { + hasWeak = true; + } else if (TInfo->getType().getObjCLifetime() == Qualifiers::OCL_Strong) + hasStrong = true; + else + return; + } + } + if (ATLs.empty()) + return; + if (hasWeak && hasStrong) + return; + + TransformActions &TA = MigrateCtx.Pass.TA; + Transaction Trans(TA); + + if (GCAttrsCollector::hasObjCImpl( + cast<Decl>(IndProps.front()->getDeclContext()))) { + if (hasWeak) + MigrateCtx.AtPropsWeak.insert(AtLoc.getRawEncoding()); + + } else { + StringRef toAttr = "strong"; + if (hasWeak) { + if (canApplyWeak(MigrateCtx.Pass.Ctx, IndProps.front()->getType(), + /*AllowOnUnkwownClass=*/true)) + toAttr = "weak"; + else + toAttr = "unsafe_unretained"; + } + if (Attrs & ObjCPropertyDecl::OBJC_PR_assign) + MigrateCtx.rewritePropertyAttribute("assign", toAttr, AtLoc); + else + MigrateCtx.addPropertyAttribute(toAttr, AtLoc); + } + + for (unsigned i = 0, e = ATLs.size(); i != e; ++i) { + SourceLocation Loc = ATLs[i].first.getAttrNameLoc(); + if (Loc.isMacroID()) + Loc = MigrateCtx.Pass.Ctx.getSourceManager() + .getImmediateExpansionRange(Loc).first; + TA.remove(Loc); + TA.clearDiagnostic(diag::err_objc_property_attr_mutually_exclusive, AtLoc); + TA.clearDiagnostic(diag::err_arc_inconsistent_property_ownership, + ATLs[i].second->getLocation()); + MigrateCtx.RemovedAttrSet.insert(Loc.getRawEncoding()); + } +} + +static void checkAllProps(MigrationContext &MigrateCtx, + std::vector<ObjCPropertyDecl *> &AllProps) { + typedef llvm::TinyPtrVector<ObjCPropertyDecl *> IndivPropsTy; + llvm::DenseMap<unsigned, IndivPropsTy> AtProps; + + for (unsigned i = 0, e = AllProps.size(); i != e; ++i) { + ObjCPropertyDecl *PD = AllProps[i]; + if (PD->getPropertyAttributesAsWritten() & + (ObjCPropertyDecl::OBJC_PR_assign | + ObjCPropertyDecl::OBJC_PR_readonly)) { + SourceLocation AtLoc = PD->getAtLoc(); + if (AtLoc.isInvalid()) + continue; + unsigned RawAt = AtLoc.getRawEncoding(); + AtProps[RawAt].push_back(PD); + } + } + + for (llvm::DenseMap<unsigned, IndivPropsTy>::iterator + I = AtProps.begin(), E = AtProps.end(); I != E; ++I) { + SourceLocation AtLoc = SourceLocation::getFromRawEncoding(I->first); + IndivPropsTy &IndProps = I->second; + checkAllAtProps(MigrateCtx, AtLoc, IndProps); + } +} + +void GCAttrsTraverser::traverseTU(MigrationContext &MigrateCtx) { + std::vector<ObjCPropertyDecl *> AllProps; + GCAttrsCollector(MigrateCtx, AllProps).TraverseDecl( + MigrateCtx.Pass.Ctx.getTranslationUnitDecl()); + + errorForGCAttrsOnNonObjC(MigrateCtx); + checkAllProps(MigrateCtx, AllProps); + checkWeakGCAttrs(MigrateCtx); +} + +void MigrationContext::dumpGCAttrs() { + llvm::errs() << "\n################\n"; + for (unsigned i = 0, e = GCAttrs.size(); i != e; ++i) { + GCAttrOccurrence &Attr = GCAttrs[i]; + llvm::errs() << "KIND: " + << (Attr.Kind == GCAttrOccurrence::Strong ? "strong" : "weak"); + llvm::errs() << "\nLOC: "; + Attr.Loc.dump(Pass.Ctx.getSourceManager()); + llvm::errs() << "\nTYPE: "; + Attr.ModifiedType.dump(); + if (Attr.Dcl) { + llvm::errs() << "DECL:\n"; + Attr.Dcl->dump(); + } else { + llvm::errs() << "DECL: NONE"; + } + llvm::errs() << "\nMIGRATABLE: " << Attr.FullyMigratable; + llvm::errs() << "\n----------------\n"; + } + llvm::errs() << "\n################\n"; +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransGCCalls.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransGCCalls.cpp new file mode 100644 index 0000000..3a236d3 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransGCCalls.cpp @@ -0,0 +1,77 @@ +//===--- TransGCCalls.cpp - Transformations to ARC mode -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/Sema/SemaDiagnostic.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class GCCollectableCallsChecker : + public RecursiveASTVisitor<GCCollectableCallsChecker> { + MigrationContext &MigrateCtx; + IdentifierInfo *NSMakeCollectableII; + IdentifierInfo *CFMakeCollectableII; + +public: + GCCollectableCallsChecker(MigrationContext &ctx) + : MigrateCtx(ctx) { + IdentifierTable &Ids = MigrateCtx.Pass.Ctx.Idents; + NSMakeCollectableII = &Ids.get("NSMakeCollectable"); + CFMakeCollectableII = &Ids.get("CFMakeCollectable"); + } + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitCallExpr(CallExpr *E) { + TransformActions &TA = MigrateCtx.Pass.TA; + + if (MigrateCtx.isGCOwnedNonObjC(E->getType())) { + TA.report(E->getLocStart(), diag::warn_arcmt_nsalloc_realloc, + E->getSourceRange()); + return true; + } + + Expr *CEE = E->getCallee()->IgnoreParenImpCasts(); + if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(CEE)) { + if (FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(DRE->getDecl())) { + if (!FD->getDeclContext()->getRedeclContext()->isFileContext()) + return true; + + if (FD->getIdentifier() == NSMakeCollectableII) { + Transaction Trans(TA); + TA.clearDiagnostic(diag::err_unavailable, + diag::err_unavailable_message, + diag::err_ovl_deleted_call, // ObjC++ + DRE->getSourceRange()); + TA.replace(DRE->getSourceRange(), "CFBridgingRelease"); + + } else if (FD->getIdentifier() == CFMakeCollectableII) { + TA.reportError("CFMakeCollectable will leak the object that it " + "receives in ARC", DRE->getLocation(), + DRE->getSourceRange()); + } + } + } + + return true; + } +}; + +} // anonymous namespace + +void GCCollectableCallsTraverser::traverseBody(BodyContext &BodyCtx) { + GCCollectableCallsChecker(BodyCtx.getMigrationContext()) + .TraverseStmt(BodyCtx.getTopStmt()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransProperties.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransProperties.cpp new file mode 100644 index 0000000..8667bc2 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransProperties.cpp @@ -0,0 +1,379 @@ +//===--- TransProperties.cpp - Transformations to ARC mode ----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// rewriteProperties: +// +// - Adds strong/weak/unsafe_unretained ownership specifier to properties that +// are missing one. +// - Migrates properties from (retain) to (strong) and (assign) to +// (unsafe_unretained/weak). +// - If a property is synthesized, adds the ownership specifier in the ivar +// backing the property. +// +// @interface Foo : NSObject { +// NSObject *x; +// } +// @property (assign) id x; +// @end +// ----> +// @interface Foo : NSObject { +// NSObject *__weak x; +// } +// @property (weak) id x; +// @end +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Sema/SemaDiagnostic.h" +#include <map> + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class PropertiesRewriter { + MigrationContext &MigrateCtx; + MigrationPass &Pass; + ObjCImplementationDecl *CurImplD; + + enum PropActionKind { + PropAction_None, + PropAction_RetainReplacedWithStrong, + PropAction_AssignRemoved, + PropAction_AssignRewritten, + PropAction_MaybeAddWeakOrUnsafe + }; + + struct PropData { + ObjCPropertyDecl *PropD; + ObjCIvarDecl *IvarD; + ObjCPropertyImplDecl *ImplD; + + PropData(ObjCPropertyDecl *propD) + : PropD(propD), IvarD(nullptr), ImplD(nullptr) {} + }; + + typedef SmallVector<PropData, 2> PropsTy; + typedef std::map<unsigned, PropsTy> AtPropDeclsTy; + AtPropDeclsTy AtProps; + llvm::DenseMap<IdentifierInfo *, PropActionKind> ActionOnProp; + +public: + explicit PropertiesRewriter(MigrationContext &MigrateCtx) + : MigrateCtx(MigrateCtx), Pass(MigrateCtx.Pass) { } + + static void collectProperties(ObjCContainerDecl *D, AtPropDeclsTy &AtProps, + AtPropDeclsTy *PrevAtProps = nullptr) { + for (auto *Prop : D->properties()) { + if (Prop->getAtLoc().isInvalid()) + continue; + unsigned RawLoc = Prop->getAtLoc().getRawEncoding(); + if (PrevAtProps) + if (PrevAtProps->find(RawLoc) != PrevAtProps->end()) + continue; + PropsTy &props = AtProps[RawLoc]; + props.push_back(Prop); + } + } + + void doTransform(ObjCImplementationDecl *D) { + CurImplD = D; + ObjCInterfaceDecl *iface = D->getClassInterface(); + if (!iface) + return; + + collectProperties(iface, AtProps); + + // Look through extensions. + for (auto *Ext : iface->visible_extensions()) + collectProperties(Ext, AtProps); + + typedef DeclContext::specific_decl_iterator<ObjCPropertyImplDecl> + prop_impl_iterator; + for (prop_impl_iterator + I = prop_impl_iterator(D->decls_begin()), + E = prop_impl_iterator(D->decls_end()); I != E; ++I) { + ObjCPropertyImplDecl *implD = *I; + if (implD->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) + continue; + ObjCPropertyDecl *propD = implD->getPropertyDecl(); + if (!propD || propD->isInvalidDecl()) + continue; + ObjCIvarDecl *ivarD = implD->getPropertyIvarDecl(); + if (!ivarD || ivarD->isInvalidDecl()) + continue; + unsigned rawAtLoc = propD->getAtLoc().getRawEncoding(); + AtPropDeclsTy::iterator findAtLoc = AtProps.find(rawAtLoc); + if (findAtLoc == AtProps.end()) + continue; + + PropsTy &props = findAtLoc->second; + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { + if (I->PropD == propD) { + I->IvarD = ivarD; + I->ImplD = implD; + break; + } + } + } + + for (AtPropDeclsTy::iterator + I = AtProps.begin(), E = AtProps.end(); I != E; ++I) { + SourceLocation atLoc = SourceLocation::getFromRawEncoding(I->first); + PropsTy &props = I->second; + if (!getPropertyType(props)->isObjCRetainableType()) + continue; + if (hasIvarWithExplicitARCOwnership(props)) + continue; + + Transaction Trans(Pass.TA); + rewriteProperty(props, atLoc); + } + } + +private: + void doPropAction(PropActionKind kind, + PropsTy &props, SourceLocation atLoc, + bool markAction = true) { + if (markAction) + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) + ActionOnProp[I->PropD->getIdentifier()] = kind; + + switch (kind) { + case PropAction_None: + return; + case PropAction_RetainReplacedWithStrong: { + StringRef toAttr = "strong"; + MigrateCtx.rewritePropertyAttribute("retain", toAttr, atLoc); + return; + } + case PropAction_AssignRemoved: + return removeAssignForDefaultStrong(props, atLoc); + case PropAction_AssignRewritten: + return rewriteAssign(props, atLoc); + case PropAction_MaybeAddWeakOrUnsafe: + return maybeAddWeakOrUnsafeUnretainedAttr(props, atLoc); + } + } + + void rewriteProperty(PropsTy &props, SourceLocation atLoc) { + ObjCPropertyDecl::PropertyAttributeKind propAttrs = getPropertyAttrs(props); + + if (propAttrs & (ObjCPropertyDecl::OBJC_PR_copy | + ObjCPropertyDecl::OBJC_PR_unsafe_unretained | + ObjCPropertyDecl::OBJC_PR_strong | + ObjCPropertyDecl::OBJC_PR_weak)) + return; + + if (propAttrs & ObjCPropertyDecl::OBJC_PR_retain) { + // strong is the default. + return doPropAction(PropAction_RetainReplacedWithStrong, props, atLoc); + } + + bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props); + + if (propAttrs & ObjCPropertyDecl::OBJC_PR_assign) { + if (HasIvarAssignedAPlusOneObject) + return doPropAction(PropAction_AssignRemoved, props, atLoc); + return doPropAction(PropAction_AssignRewritten, props, atLoc); + } + + if (HasIvarAssignedAPlusOneObject || + (Pass.isGCMigration() && !hasGCWeak(props, atLoc))) + return; // 'strong' by default. + + return doPropAction(PropAction_MaybeAddWeakOrUnsafe, props, atLoc); + } + + void removeAssignForDefaultStrong(PropsTy &props, + SourceLocation atLoc) const { + removeAttribute("retain", atLoc); + if (!removeAttribute("assign", atLoc)) + return; + + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { + if (I->ImplD) + Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, + diag::err_arc_assign_property_ownership, + diag::err_arc_inconsistent_property_ownership, + I->IvarD->getLocation()); + } + } + + void rewriteAssign(PropsTy &props, SourceLocation atLoc) const { + bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), + /*AllowOnUnknownClass=*/Pass.isGCMigration()); + const char *toWhich = + (Pass.isGCMigration() && !hasGCWeak(props, atLoc)) ? "strong" : + (canUseWeak ? "weak" : "unsafe_unretained"); + + bool rewroteAttr = rewriteAttribute("assign", toWhich, atLoc); + if (!rewroteAttr) + canUseWeak = false; + + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { + if (isUserDeclared(I->IvarD)) { + if (I->IvarD && + I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) { + const char *toWhich = + (Pass.isGCMigration() && !hasGCWeak(props, atLoc)) ? "__strong " : + (canUseWeak ? "__weak " : "__unsafe_unretained "); + Pass.TA.insert(I->IvarD->getLocation(), toWhich); + } + } + if (I->ImplD) + Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, + diag::err_arc_assign_property_ownership, + diag::err_arc_inconsistent_property_ownership, + I->IvarD->getLocation()); + } + } + + void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props, + SourceLocation atLoc) const { + bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), + /*AllowOnUnknownClass=*/Pass.isGCMigration()); + + bool addedAttr = addAttribute(canUseWeak ? "weak" : "unsafe_unretained", + atLoc); + if (!addedAttr) + canUseWeak = false; + + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { + if (isUserDeclared(I->IvarD)) { + if (I->IvarD && + I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) + Pass.TA.insert(I->IvarD->getLocation(), + canUseWeak ? "__weak " : "__unsafe_unretained "); + } + if (I->ImplD) { + Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, + diag::err_arc_assign_property_ownership, + diag::err_arc_inconsistent_property_ownership, + I->IvarD->getLocation()); + Pass.TA.clearDiagnostic( + diag::err_arc_objc_property_default_assign_on_object, + I->ImplD->getLocation()); + } + } + } + + bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const { + return MigrateCtx.removePropertyAttribute(fromAttr, atLoc); + } + + bool rewriteAttribute(StringRef fromAttr, StringRef toAttr, + SourceLocation atLoc) const { + return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc); + } + + bool addAttribute(StringRef attr, SourceLocation atLoc) const { + return MigrateCtx.addPropertyAttribute(attr, atLoc); + } + + class PlusOneAssign : public RecursiveASTVisitor<PlusOneAssign> { + ObjCIvarDecl *Ivar; + public: + PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {} + + bool VisitBinAssign(BinaryOperator *E) { + Expr *lhs = E->getLHS()->IgnoreParenImpCasts(); + if (ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(lhs)) { + if (RE->getDecl() != Ivar) + return true; + + if (isPlusOneAssign(E)) + return false; + } + + return true; + } + }; + + bool hasIvarAssignedAPlusOneObject(PropsTy &props) const { + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { + PlusOneAssign oneAssign(I->IvarD); + bool notFound = oneAssign.TraverseDecl(CurImplD); + if (!notFound) + return true; + } + + return false; + } + + bool hasIvarWithExplicitARCOwnership(PropsTy &props) const { + if (Pass.isGCMigration()) + return false; + + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { + if (isUserDeclared(I->IvarD)) { + if (isa<AttributedType>(I->IvarD->getType())) + return true; + if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime() + != Qualifiers::OCL_Strong) + return true; + } + } + + return false; + } + + // \brief Returns true if all declarations in the @property have GC __weak. + bool hasGCWeak(PropsTy &props, SourceLocation atLoc) const { + if (!Pass.isGCMigration()) + return false; + if (props.empty()) + return false; + return MigrateCtx.AtPropsWeak.count(atLoc.getRawEncoding()); + } + + bool isUserDeclared(ObjCIvarDecl *ivarD) const { + return ivarD && !ivarD->getSynthesize(); + } + + QualType getPropertyType(PropsTy &props) const { + assert(!props.empty()); + QualType ty = props[0].PropD->getType().getUnqualifiedType(); + +#ifndef NDEBUG + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) + assert(ty == I->PropD->getType().getUnqualifiedType()); +#endif + + return ty; + } + + ObjCPropertyDecl::PropertyAttributeKind + getPropertyAttrs(PropsTy &props) const { + assert(!props.empty()); + ObjCPropertyDecl::PropertyAttributeKind + attrs = props[0].PropD->getPropertyAttributesAsWritten(); + +#ifndef NDEBUG + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) + assert(attrs == I->PropD->getPropertyAttributesAsWritten()); +#endif + + return attrs; + } +}; + +} // anonymous namespace + +void PropertyRewriteTraverser::traverseObjCImplementation( + ObjCImplementationContext &ImplCtx) { + PropertiesRewriter(ImplCtx.getMigrationContext()) + .doTransform(ImplCtx.getImplementationDecl()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransProtectedScope.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransProtectedScope.cpp new file mode 100644 index 0000000..0fcbcbe --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransProtectedScope.cpp @@ -0,0 +1,202 @@ +//===--- TransProtectedScope.cpp - Transformations to ARC mode ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Adds brackets in case statements that "contain" initialization of retaining +// variable, thus emitting the "switch case is in protected scope" error. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/Sema/SemaDiagnostic.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class LocalRefsCollector : public RecursiveASTVisitor<LocalRefsCollector> { + SmallVectorImpl<DeclRefExpr *> &Refs; + +public: + LocalRefsCollector(SmallVectorImpl<DeclRefExpr *> &refs) + : Refs(refs) { } + + bool VisitDeclRefExpr(DeclRefExpr *E) { + if (ValueDecl *D = E->getDecl()) + if (D->getDeclContext()->getRedeclContext()->isFunctionOrMethod()) + Refs.push_back(E); + return true; + } +}; + +struct CaseInfo { + SwitchCase *SC; + SourceRange Range; + enum { + St_Unchecked, + St_CannotFix, + St_Fixed + } State; + + CaseInfo() : SC(nullptr), State(St_Unchecked) {} + CaseInfo(SwitchCase *S, SourceRange Range) + : SC(S), Range(Range), State(St_Unchecked) {} +}; + +class CaseCollector : public RecursiveASTVisitor<CaseCollector> { + ParentMap &PMap; + SmallVectorImpl<CaseInfo> &Cases; + +public: + CaseCollector(ParentMap &PMap, SmallVectorImpl<CaseInfo> &Cases) + : PMap(PMap), Cases(Cases) { } + + bool VisitSwitchStmt(SwitchStmt *S) { + SwitchCase *Curr = S->getSwitchCaseList(); + if (!Curr) + return true; + Stmt *Parent = getCaseParent(Curr); + Curr = Curr->getNextSwitchCase(); + // Make sure all case statements are in the same scope. + while (Curr) { + if (getCaseParent(Curr) != Parent) + return true; + Curr = Curr->getNextSwitchCase(); + } + + SourceLocation NextLoc = S->getLocEnd(); + Curr = S->getSwitchCaseList(); + // We iterate over case statements in reverse source-order. + while (Curr) { + Cases.push_back(CaseInfo(Curr,SourceRange(Curr->getLocStart(), NextLoc))); + NextLoc = Curr->getLocStart(); + Curr = Curr->getNextSwitchCase(); + } + return true; + } + + Stmt *getCaseParent(SwitchCase *S) { + Stmt *Parent = PMap.getParent(S); + while (Parent && (isa<SwitchCase>(Parent) || isa<LabelStmt>(Parent))) + Parent = PMap.getParent(Parent); + return Parent; + } +}; + +class ProtectedScopeFixer { + MigrationPass &Pass; + SourceManager &SM; + SmallVector<CaseInfo, 16> Cases; + SmallVector<DeclRefExpr *, 16> LocalRefs; + +public: + ProtectedScopeFixer(BodyContext &BodyCtx) + : Pass(BodyCtx.getMigrationContext().Pass), + SM(Pass.Ctx.getSourceManager()) { + + CaseCollector(BodyCtx.getParentMap(), Cases) + .TraverseStmt(BodyCtx.getTopStmt()); + LocalRefsCollector(LocalRefs).TraverseStmt(BodyCtx.getTopStmt()); + + SourceRange BodyRange = BodyCtx.getTopStmt()->getSourceRange(); + const CapturedDiagList &DiagList = Pass.getDiags(); + // Copy the diagnostics so we don't have to worry about invaliding iterators + // from the diagnostic list. + SmallVector<StoredDiagnostic, 16> StoredDiags; + StoredDiags.append(DiagList.begin(), DiagList.end()); + SmallVectorImpl<StoredDiagnostic>::iterator + I = StoredDiags.begin(), E = StoredDiags.end(); + while (I != E) { + if (I->getID() == diag::err_switch_into_protected_scope && + isInRange(I->getLocation(), BodyRange)) { + handleProtectedScopeError(I, E); + continue; + } + ++I; + } + } + + void handleProtectedScopeError( + SmallVectorImpl<StoredDiagnostic>::iterator &DiagI, + SmallVectorImpl<StoredDiagnostic>::iterator DiagE){ + Transaction Trans(Pass.TA); + assert(DiagI->getID() == diag::err_switch_into_protected_scope); + SourceLocation ErrLoc = DiagI->getLocation(); + bool handledAllNotes = true; + ++DiagI; + for (; DiagI != DiagE && DiagI->getLevel() == DiagnosticsEngine::Note; + ++DiagI) { + if (!handleProtectedNote(*DiagI)) + handledAllNotes = false; + } + + if (handledAllNotes) + Pass.TA.clearDiagnostic(diag::err_switch_into_protected_scope, ErrLoc); + } + + bool handleProtectedNote(const StoredDiagnostic &Diag) { + assert(Diag.getLevel() == DiagnosticsEngine::Note); + + for (unsigned i = 0; i != Cases.size(); i++) { + CaseInfo &info = Cases[i]; + if (isInRange(Diag.getLocation(), info.Range)) { + + if (info.State == CaseInfo::St_Unchecked) + tryFixing(info); + assert(info.State != CaseInfo::St_Unchecked); + + if (info.State == CaseInfo::St_Fixed) { + Pass.TA.clearDiagnostic(Diag.getID(), Diag.getLocation()); + return true; + } + return false; + } + } + + return false; + } + + void tryFixing(CaseInfo &info) { + assert(info.State == CaseInfo::St_Unchecked); + if (hasVarReferencedOutside(info)) { + info.State = CaseInfo::St_CannotFix; + return; + } + + Pass.TA.insertAfterToken(info.SC->getColonLoc(), " {"); + Pass.TA.insert(info.Range.getEnd(), "}\n"); + info.State = CaseInfo::St_Fixed; + } + + bool hasVarReferencedOutside(CaseInfo &info) { + for (unsigned i = 0, e = LocalRefs.size(); i != e; ++i) { + DeclRefExpr *DRE = LocalRefs[i]; + if (isInRange(DRE->getDecl()->getLocation(), info.Range) && + !isInRange(DRE->getLocation(), info.Range)) + return true; + } + return false; + } + + bool isInRange(SourceLocation Loc, SourceRange R) { + if (Loc.isInvalid()) + return false; + return !SM.isBeforeInTranslationUnit(Loc, R.getBegin()) && + SM.isBeforeInTranslationUnit(Loc, R.getEnd()); + } +}; + +} // anonymous namespace + +void ProtectedScopeTraverser::traverseBody(BodyContext &BodyCtx) { + ProtectedScopeFixer Fix(BodyCtx); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransRetainReleaseDealloc.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransRetainReleaseDealloc.cpp new file mode 100644 index 0000000..f81133f --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransRetainReleaseDealloc.cpp @@ -0,0 +1,455 @@ +//===--- TransRetainReleaseDealloc.cpp - Transformations to ARC mode ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// removeRetainReleaseDealloc: +// +// Removes retain/release/autorelease/dealloc messages. +// +// return [[foo retain] autorelease]; +// ----> +// return foo; +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ParentMap.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "llvm/ADT/StringSwitch.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class RetainReleaseDeallocRemover : + public RecursiveASTVisitor<RetainReleaseDeallocRemover> { + Stmt *Body; + MigrationPass &Pass; + + ExprSet Removables; + std::unique_ptr<ParentMap> StmtMap; + + Selector DelegateSel, FinalizeSel; + +public: + RetainReleaseDeallocRemover(MigrationPass &pass) + : Body(nullptr), Pass(pass) { + DelegateSel = + Pass.Ctx.Selectors.getNullarySelector(&Pass.Ctx.Idents.get("delegate")); + FinalizeSel = + Pass.Ctx.Selectors.getNullarySelector(&Pass.Ctx.Idents.get("finalize")); + } + + void transformBody(Stmt *body, Decl *ParentD) { + Body = body; + collectRemovables(body, Removables); + StmtMap.reset(new ParentMap(body)); + TraverseStmt(body); + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *E) { + switch (E->getMethodFamily()) { + default: + if (E->isInstanceMessage() && E->getSelector() == FinalizeSel) + break; + return true; + case OMF_autorelease: + if (isRemovable(E)) { + if (!isCommonUnusedAutorelease(E)) { + // An unused autorelease is badness. If we remove it the receiver + // will likely die immediately while previously it was kept alive + // by the autorelease pool. This is bad practice in general, leave it + // and emit an error to force the user to restructure their code. + Pass.TA.reportError("it is not safe to remove an unused 'autorelease' " + "message; its receiver may be destroyed immediately", + E->getLocStart(), E->getSourceRange()); + return true; + } + } + // Pass through. + case OMF_retain: + case OMF_release: + if (E->getReceiverKind() == ObjCMessageExpr::Instance) + if (Expr *rec = E->getInstanceReceiver()) { + rec = rec->IgnoreParenImpCasts(); + if (rec->getType().getObjCLifetime() == Qualifiers::OCL_ExplicitNone && + (E->getMethodFamily() != OMF_retain || isRemovable(E))) { + std::string err = "it is not safe to remove '"; + err += E->getSelector().getAsString() + "' message on " + "an __unsafe_unretained type"; + Pass.TA.reportError(err, rec->getLocStart()); + return true; + } + + if (isGlobalVar(rec) && + (E->getMethodFamily() != OMF_retain || isRemovable(E))) { + std::string err = "it is not safe to remove '"; + err += E->getSelector().getAsString() + "' message on " + "a global variable"; + Pass.TA.reportError(err, rec->getLocStart()); + return true; + } + + if (E->getMethodFamily() == OMF_release && isDelegateMessage(rec)) { + Pass.TA.reportError("it is not safe to remove 'retain' " + "message on the result of a 'delegate' message; " + "the object that was passed to 'setDelegate:' may not be " + "properly retained", rec->getLocStart()); + return true; + } + } + case OMF_dealloc: + break; + } + + switch (E->getReceiverKind()) { + default: + return true; + case ObjCMessageExpr::SuperInstance: { + Transaction Trans(Pass.TA); + clearDiagnostics(E->getSelectorLoc(0)); + if (tryRemoving(E)) + return true; + Pass.TA.replace(E->getSourceRange(), "self"); + return true; + } + case ObjCMessageExpr::Instance: + break; + } + + Expr *rec = E->getInstanceReceiver(); + if (!rec) return true; + + Transaction Trans(Pass.TA); + clearDiagnostics(E->getSelectorLoc(0)); + + ObjCMessageExpr *Msg = E; + Expr *RecContainer = Msg; + SourceRange RecRange = rec->getSourceRange(); + checkForGCDOrXPC(Msg, RecContainer, rec, RecRange); + + if (Msg->getMethodFamily() == OMF_release && + isRemovable(RecContainer) && isInAtFinally(RecContainer)) { + // Change the -release to "receiver = nil" in a finally to avoid a leak + // when an exception is thrown. + Pass.TA.replace(RecContainer->getSourceRange(), RecRange); + std::string str = " = "; + str += getNilString(Pass); + Pass.TA.insertAfterToken(RecRange.getEnd(), str); + return true; + } + + if (hasSideEffects(rec, Pass.Ctx) || !tryRemoving(RecContainer)) + Pass.TA.replace(RecContainer->getSourceRange(), RecRange); + + return true; + } + +private: + /// \brief Checks for idioms where an unused -autorelease is common. + /// + /// Returns true for this idiom which is common in property + /// setters: + /// + /// [backingValue autorelease]; + /// backingValue = [newValue retain]; // in general a +1 assign + /// + /// For these as well: + /// + /// [[var retain] autorelease]; + /// return var; + /// + bool isCommonUnusedAutorelease(ObjCMessageExpr *E) { + return isPlusOneAssignBeforeOrAfterAutorelease(E) || + isReturnedAfterAutorelease(E); + } + + bool isReturnedAfterAutorelease(ObjCMessageExpr *E) { + Expr *Rec = E->getInstanceReceiver(); + if (!Rec) + return false; + + Decl *RefD = getReferencedDecl(Rec); + if (!RefD) + return false; + + Stmt *nextStmt = getNextStmt(E); + if (!nextStmt) + return false; + + // Check for "return <variable>;". + + if (ReturnStmt *RetS = dyn_cast<ReturnStmt>(nextStmt)) + return RefD == getReferencedDecl(RetS->getRetValue()); + + return false; + } + + bool isPlusOneAssignBeforeOrAfterAutorelease(ObjCMessageExpr *E) { + Expr *Rec = E->getInstanceReceiver(); + if (!Rec) + return false; + + Decl *RefD = getReferencedDecl(Rec); + if (!RefD) + return false; + + Stmt *prevStmt, *nextStmt; + std::tie(prevStmt, nextStmt) = getPreviousAndNextStmt(E); + + return isPlusOneAssignToVar(prevStmt, RefD) || + isPlusOneAssignToVar(nextStmt, RefD); + } + + bool isPlusOneAssignToVar(Stmt *S, Decl *RefD) { + if (!S) + return false; + + // Check for "RefD = [+1 retained object];". + + if (BinaryOperator *Bop = dyn_cast<BinaryOperator>(S)) { + return (RefD == getReferencedDecl(Bop->getLHS())) && isPlusOneAssign(Bop); + } + + if (DeclStmt *DS = dyn_cast<DeclStmt>(S)) { + if (DS->isSingleDecl() && DS->getSingleDecl() == RefD) { + if (VarDecl *VD = dyn_cast<VarDecl>(RefD)) + return isPlusOne(VD->getInit()); + } + return false; + } + + return false; + } + + Stmt *getNextStmt(Expr *E) { + return getPreviousAndNextStmt(E).second; + } + + std::pair<Stmt *, Stmt *> getPreviousAndNextStmt(Expr *E) { + Stmt *prevStmt = nullptr, *nextStmt = nullptr; + if (!E) + return std::make_pair(prevStmt, nextStmt); + + Stmt *OuterS = E, *InnerS; + do { + InnerS = OuterS; + OuterS = StmtMap->getParent(InnerS); + } + while (OuterS && (isa<ParenExpr>(OuterS) || + isa<CastExpr>(OuterS) || + isa<ExprWithCleanups>(OuterS))); + + if (!OuterS) + return std::make_pair(prevStmt, nextStmt); + + Stmt::child_iterator currChildS = OuterS->child_begin(); + Stmt::child_iterator childE = OuterS->child_end(); + Stmt::child_iterator prevChildS = childE; + for (; currChildS != childE; ++currChildS) { + if (*currChildS == InnerS) + break; + prevChildS = currChildS; + } + + if (prevChildS != childE) { + prevStmt = *prevChildS; + if (prevStmt) + prevStmt = prevStmt->IgnoreImplicit(); + } + + if (currChildS == childE) + return std::make_pair(prevStmt, nextStmt); + ++currChildS; + if (currChildS == childE) + return std::make_pair(prevStmt, nextStmt); + + nextStmt = *currChildS; + if (nextStmt) + nextStmt = nextStmt->IgnoreImplicit(); + + return std::make_pair(prevStmt, nextStmt); + } + + Decl *getReferencedDecl(Expr *E) { + if (!E) + return nullptr; + + E = E->IgnoreParenCasts(); + if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(E)) { + switch (ME->getMethodFamily()) { + case OMF_copy: + case OMF_autorelease: + case OMF_release: + case OMF_retain: + return getReferencedDecl(ME->getInstanceReceiver()); + default: + return nullptr; + } + } + if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) + return DRE->getDecl(); + if (MemberExpr *ME = dyn_cast<MemberExpr>(E)) + return ME->getMemberDecl(); + if (ObjCIvarRefExpr *IRE = dyn_cast<ObjCIvarRefExpr>(E)) + return IRE->getDecl(); + + return nullptr; + } + + /// \brief Check if the retain/release is due to a GCD/XPC macro that are + /// defined as: + /// + /// #define dispatch_retain(object) ({ dispatch_object_t _o = (object); _dispatch_object_validate(_o); (void)[_o retain]; }) + /// #define dispatch_release(object) ({ dispatch_object_t _o = (object); _dispatch_object_validate(_o); [_o release]; }) + /// #define xpc_retain(object) ({ xpc_object_t _o = (object); _xpc_object_validate(_o); [_o retain]; }) + /// #define xpc_release(object) ({ xpc_object_t _o = (object); _xpc_object_validate(_o); [_o release]; }) + /// + /// and return the top container which is the StmtExpr and the macro argument + /// expression. + void checkForGCDOrXPC(ObjCMessageExpr *Msg, Expr *&RecContainer, + Expr *&Rec, SourceRange &RecRange) { + SourceLocation Loc = Msg->getExprLoc(); + if (!Loc.isMacroID()) + return; + SourceManager &SM = Pass.Ctx.getSourceManager(); + StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, + Pass.Ctx.getLangOpts()); + bool isGCDOrXPC = llvm::StringSwitch<bool>(MacroName) + .Case("dispatch_retain", true) + .Case("dispatch_release", true) + .Case("xpc_retain", true) + .Case("xpc_release", true) + .Default(false); + if (!isGCDOrXPC) + return; + + StmtExpr *StmtE = nullptr; + Stmt *S = Msg; + while (S) { + if (StmtExpr *SE = dyn_cast<StmtExpr>(S)) { + StmtE = SE; + break; + } + S = StmtMap->getParent(S); + } + + if (!StmtE) + return; + + Stmt::child_range StmtExprChild = StmtE->children(); + if (StmtExprChild.begin() == StmtExprChild.end()) + return; + auto *CompS = dyn_cast_or_null<CompoundStmt>(*StmtExprChild.begin()); + if (!CompS) + return; + + Stmt::child_range CompStmtChild = CompS->children(); + if (CompStmtChild.begin() == CompStmtChild.end()) + return; + auto *DeclS = dyn_cast_or_null<DeclStmt>(*CompStmtChild.begin()); + if (!DeclS) + return; + if (!DeclS->isSingleDecl()) + return; + VarDecl *VD = dyn_cast_or_null<VarDecl>(DeclS->getSingleDecl()); + if (!VD) + return; + Expr *Init = VD->getInit(); + if (!Init) + return; + + RecContainer = StmtE; + Rec = Init->IgnoreParenImpCasts(); + if (ExprWithCleanups *EWC = dyn_cast<ExprWithCleanups>(Rec)) + Rec = EWC->getSubExpr()->IgnoreParenImpCasts(); + RecRange = Rec->getSourceRange(); + if (SM.isMacroArgExpansion(RecRange.getBegin())) + RecRange.setBegin(SM.getImmediateSpellingLoc(RecRange.getBegin())); + if (SM.isMacroArgExpansion(RecRange.getEnd())) + RecRange.setEnd(SM.getImmediateSpellingLoc(RecRange.getEnd())); + } + + void clearDiagnostics(SourceLocation loc) const { + Pass.TA.clearDiagnostic(diag::err_arc_illegal_explicit_message, + diag::err_unavailable, + diag::err_unavailable_message, + loc); + } + + bool isDelegateMessage(Expr *E) const { + if (!E) return false; + + E = E->IgnoreParenCasts(); + + // Also look through property-getter sugar. + if (PseudoObjectExpr *pseudoOp = dyn_cast<PseudoObjectExpr>(E)) + E = pseudoOp->getResultExpr()->IgnoreImplicit(); + + if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(E)) + return (ME->isInstanceMessage() && ME->getSelector() == DelegateSel); + + return false; + } + + bool isInAtFinally(Expr *E) const { + assert(E); + Stmt *S = E; + while (S) { + if (isa<ObjCAtFinallyStmt>(S)) + return true; + S = StmtMap->getParent(S); + } + + return false; + } + + bool isRemovable(Expr *E) const { + return Removables.count(E); + } + + bool tryRemoving(Expr *E) const { + if (isRemovable(E)) { + Pass.TA.removeStmt(E); + return true; + } + + Stmt *parent = StmtMap->getParent(E); + + if (ImplicitCastExpr *castE = dyn_cast_or_null<ImplicitCastExpr>(parent)) + return tryRemoving(castE); + + if (ParenExpr *parenE = dyn_cast_or_null<ParenExpr>(parent)) + return tryRemoving(parenE); + + if (BinaryOperator * + bopE = dyn_cast_or_null<BinaryOperator>(parent)) { + if (bopE->getOpcode() == BO_Comma && bopE->getLHS() == E && + isRemovable(bopE)) { + Pass.TA.replace(bopE->getSourceRange(), bopE->getRHS()->getSourceRange()); + return true; + } + } + + return false; + } + +}; + +} // anonymous namespace + +void trans::removeRetainReleaseDeallocFinalize(MigrationPass &pass) { + BodyTransform<RetainReleaseDeallocRemover> trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransUnbridgedCasts.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransUnbridgedCasts.cpp new file mode 100644 index 0000000..7ca4955 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransUnbridgedCasts.cpp @@ -0,0 +1,469 @@ +//===--- TransUnbridgedCasts.cpp - Transformations to ARC mode ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// rewriteUnbridgedCasts: +// +// A cast of non-objc pointer to an objc one is checked. If the non-objc pointer +// is from a file-level variable, __bridge cast is used to convert it. +// For the result of a function call that we know is +1/+0, +// __bridge/CFBridgingRelease is used. +// +// NSString *str = (NSString *)kUTTypePlainText; +// str = b ? kUTTypeRTF : kUTTypePlainText; +// NSString *_uuidString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, +// _uuid); +// ----> +// NSString *str = (__bridge NSString *)kUTTypePlainText; +// str = (__bridge NSString *)(b ? kUTTypeRTF : kUTTypePlainText); +// NSString *_uuidString = (NSString *) +// CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, _uuid)); +// +// For a C pointer to ObjC, for casting 'self', __bridge is used. +// +// CFStringRef str = (CFStringRef)self; +// ----> +// CFStringRef str = (__bridge CFStringRef)self; +// +// Uses of Block_copy/Block_release macros are rewritten: +// +// c = Block_copy(b); +// Block_release(c); +// ----> +// c = [b copy]; +// <removed> +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/ParentMap.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "llvm/ADT/SmallString.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class UnbridgedCastRewriter : public RecursiveASTVisitor<UnbridgedCastRewriter>{ + MigrationPass &Pass; + IdentifierInfo *SelfII; + std::unique_ptr<ParentMap> StmtMap; + Decl *ParentD; + Stmt *Body; + mutable std::unique_ptr<ExprSet> Removables; + +public: + UnbridgedCastRewriter(MigrationPass &pass) + : Pass(pass), ParentD(nullptr), Body(nullptr) { + SelfII = &Pass.Ctx.Idents.get("self"); + } + + void transformBody(Stmt *body, Decl *ParentD) { + this->ParentD = ParentD; + Body = body; + StmtMap.reset(new ParentMap(body)); + TraverseStmt(body); + } + + bool TraverseBlockDecl(BlockDecl *D) { + // ParentMap does not enter into a BlockDecl to record its stmts, so use a + // new UnbridgedCastRewriter to handle the block. + UnbridgedCastRewriter(Pass).transformBody(D->getBody(), D); + return true; + } + + bool VisitCastExpr(CastExpr *E) { + if (E->getCastKind() != CK_CPointerToObjCPointerCast && + E->getCastKind() != CK_BitCast && + E->getCastKind() != CK_AnyPointerToBlockPointerCast) + return true; + + QualType castType = E->getType(); + Expr *castExpr = E->getSubExpr(); + QualType castExprType = castExpr->getType(); + + if (castType->isObjCRetainableType() == castExprType->isObjCRetainableType()) + return true; + + bool exprRetainable = castExprType->isObjCIndirectLifetimeType(); + bool castRetainable = castType->isObjCIndirectLifetimeType(); + if (exprRetainable == castRetainable) return true; + + if (castExpr->isNullPointerConstant(Pass.Ctx, + Expr::NPC_ValueDependentIsNull)) + return true; + + SourceLocation loc = castExpr->getExprLoc(); + if (loc.isValid() && Pass.Ctx.getSourceManager().isInSystemHeader(loc)) + return true; + + if (castType->isObjCRetainableType()) + transformNonObjCToObjCCast(E); + else + transformObjCToNonObjCCast(E); + + return true; + } + +private: + void transformNonObjCToObjCCast(CastExpr *E) { + if (!E) return; + + // Global vars are assumed that are cast as unretained. + if (isGlobalVar(E)) + if (E->getSubExpr()->getType()->isPointerType()) { + castToObjCObject(E, /*retained=*/false); + return; + } + + // If the cast is directly over the result of a Core Foundation function + // try to figure out whether it should be cast as retained or unretained. + Expr *inner = E->IgnoreParenCasts(); + if (CallExpr *callE = dyn_cast<CallExpr>(inner)) { + if (FunctionDecl *FD = callE->getDirectCallee()) { + if (FD->hasAttr<CFReturnsRetainedAttr>()) { + castToObjCObject(E, /*retained=*/true); + return; + } + if (FD->hasAttr<CFReturnsNotRetainedAttr>()) { + castToObjCObject(E, /*retained=*/false); + return; + } + if (FD->isGlobal() && + FD->getIdentifier() && + ento::cocoa::isRefType(E->getSubExpr()->getType(), "CF", + FD->getIdentifier()->getName())) { + StringRef fname = FD->getIdentifier()->getName(); + if (fname.endswith("Retain") || + fname.find("Create") != StringRef::npos || + fname.find("Copy") != StringRef::npos) { + // Do not migrate to couple of bridge transfer casts which + // cancel each other out. Leave it unchanged so error gets user + // attention instead. + if (FD->getName() == "CFRetain" && + FD->getNumParams() == 1 && + FD->getParent()->isTranslationUnit() && + FD->isExternallyVisible()) { + Expr *Arg = callE->getArg(0); + if (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(Arg)) { + const Expr *sub = ICE->getSubExpr(); + QualType T = sub->getType(); + if (T->isObjCObjectPointerType()) + return; + } + } + castToObjCObject(E, /*retained=*/true); + return; + } + + if (fname.find("Get") != StringRef::npos) { + castToObjCObject(E, /*retained=*/false); + return; + } + } + } + } + + // If returning an ivar or a member of an ivar from a +0 method, use + // a __bridge cast. + Expr *base = inner->IgnoreParenImpCasts(); + while (isa<MemberExpr>(base)) + base = cast<MemberExpr>(base)->getBase()->IgnoreParenImpCasts(); + if (isa<ObjCIvarRefExpr>(base) && + isa<ReturnStmt>(StmtMap->getParentIgnoreParenCasts(E))) { + if (ObjCMethodDecl *method = dyn_cast_or_null<ObjCMethodDecl>(ParentD)) { + if (!method->hasAttr<NSReturnsRetainedAttr>()) { + castToObjCObject(E, /*retained=*/false); + return; + } + } + } + } + + void castToObjCObject(CastExpr *E, bool retained) { + rewriteToBridgedCast(E, retained ? OBC_BridgeTransfer : OBC_Bridge); + } + + void rewriteToBridgedCast(CastExpr *E, ObjCBridgeCastKind Kind) { + Transaction Trans(Pass.TA); + rewriteToBridgedCast(E, Kind, Trans); + } + + void rewriteToBridgedCast(CastExpr *E, ObjCBridgeCastKind Kind, + Transaction &Trans) { + TransformActions &TA = Pass.TA; + + // We will remove the compiler diagnostic. + if (!TA.hasDiagnostic(diag::err_arc_mismatched_cast, + diag::err_arc_cast_requires_bridge, + E->getLocStart())) { + Trans.abort(); + return; + } + + StringRef bridge; + switch(Kind) { + case OBC_Bridge: + bridge = "__bridge "; break; + case OBC_BridgeTransfer: + bridge = "__bridge_transfer "; break; + case OBC_BridgeRetained: + bridge = "__bridge_retained "; break; + } + + TA.clearDiagnostic(diag::err_arc_mismatched_cast, + diag::err_arc_cast_requires_bridge, + E->getLocStart()); + if (Kind == OBC_Bridge || !Pass.CFBridgingFunctionsDefined()) { + if (CStyleCastExpr *CCE = dyn_cast<CStyleCastExpr>(E)) { + TA.insertAfterToken(CCE->getLParenLoc(), bridge); + } else { + SourceLocation insertLoc = E->getSubExpr()->getLocStart(); + SmallString<128> newCast; + newCast += '('; + newCast += bridge; + newCast += E->getType().getAsString(Pass.Ctx.getPrintingPolicy()); + newCast += ')'; + + if (isa<ParenExpr>(E->getSubExpr())) { + TA.insert(insertLoc, newCast.str()); + } else { + newCast += '('; + TA.insert(insertLoc, newCast.str()); + TA.insertAfterToken(E->getLocEnd(), ")"); + } + } + } else { + assert(Kind == OBC_BridgeTransfer || Kind == OBC_BridgeRetained); + SmallString<32> BridgeCall; + + Expr *WrapE = E->getSubExpr(); + SourceLocation InsertLoc = WrapE->getLocStart(); + + SourceManager &SM = Pass.Ctx.getSourceManager(); + char PrevChar = *SM.getCharacterData(InsertLoc.getLocWithOffset(-1)); + if (Lexer::isIdentifierBodyChar(PrevChar, Pass.Ctx.getLangOpts())) + BridgeCall += ' '; + + if (Kind == OBC_BridgeTransfer) + BridgeCall += "CFBridgingRelease"; + else + BridgeCall += "CFBridgingRetain"; + + if (isa<ParenExpr>(WrapE)) { + TA.insert(InsertLoc, BridgeCall); + } else { + BridgeCall += '('; + TA.insert(InsertLoc, BridgeCall); + TA.insertAfterToken(WrapE->getLocEnd(), ")"); + } + } + } + + void rewriteCastForCFRetain(CastExpr *castE, CallExpr *callE) { + Transaction Trans(Pass.TA); + Pass.TA.replace(callE->getSourceRange(), callE->getArg(0)->getSourceRange()); + rewriteToBridgedCast(castE, OBC_BridgeRetained, Trans); + } + + void getBlockMacroRanges(CastExpr *E, SourceRange &Outer, SourceRange &Inner) { + SourceManager &SM = Pass.Ctx.getSourceManager(); + SourceLocation Loc = E->getExprLoc(); + assert(Loc.isMacroID()); + SourceLocation MacroBegin, MacroEnd; + std::tie(MacroBegin, MacroEnd) = SM.getImmediateExpansionRange(Loc); + SourceRange SubRange = E->getSubExpr()->IgnoreParenImpCasts()->getSourceRange(); + SourceLocation InnerBegin = SM.getImmediateMacroCallerLoc(SubRange.getBegin()); + SourceLocation InnerEnd = SM.getImmediateMacroCallerLoc(SubRange.getEnd()); + + Outer = SourceRange(MacroBegin, MacroEnd); + Inner = SourceRange(InnerBegin, InnerEnd); + } + + void rewriteBlockCopyMacro(CastExpr *E) { + SourceRange OuterRange, InnerRange; + getBlockMacroRanges(E, OuterRange, InnerRange); + + Transaction Trans(Pass.TA); + Pass.TA.replace(OuterRange, InnerRange); + Pass.TA.insert(InnerRange.getBegin(), "["); + Pass.TA.insertAfterToken(InnerRange.getEnd(), " copy]"); + Pass.TA.clearDiagnostic(diag::err_arc_mismatched_cast, + diag::err_arc_cast_requires_bridge, + OuterRange); + } + + void removeBlockReleaseMacro(CastExpr *E) { + SourceRange OuterRange, InnerRange; + getBlockMacroRanges(E, OuterRange, InnerRange); + + Transaction Trans(Pass.TA); + Pass.TA.clearDiagnostic(diag::err_arc_mismatched_cast, + diag::err_arc_cast_requires_bridge, + OuterRange); + if (!hasSideEffects(E, Pass.Ctx)) { + if (tryRemoving(cast<Expr>(StmtMap->getParentIgnoreParenCasts(E)))) + return; + } + Pass.TA.replace(OuterRange, InnerRange); + } + + bool tryRemoving(Expr *E) const { + if (!Removables) { + Removables.reset(new ExprSet); + collectRemovables(Body, *Removables); + } + + if (Removables->count(E)) { + Pass.TA.removeStmt(E); + return true; + } + + return false; + } + + void transformObjCToNonObjCCast(CastExpr *E) { + SourceLocation CastLoc = E->getExprLoc(); + if (CastLoc.isMacroID()) { + StringRef MacroName = Lexer::getImmediateMacroName(CastLoc, + Pass.Ctx.getSourceManager(), + Pass.Ctx.getLangOpts()); + if (MacroName == "Block_copy") { + rewriteBlockCopyMacro(E); + return; + } + if (MacroName == "Block_release") { + removeBlockReleaseMacro(E); + return; + } + } + + if (isSelf(E->getSubExpr())) + return rewriteToBridgedCast(E, OBC_Bridge); + + CallExpr *callE; + if (isPassedToCFRetain(E, callE)) + return rewriteCastForCFRetain(E, callE); + + ObjCMethodFamily family = getFamilyOfMessage(E->getSubExpr()); + if (family == OMF_retain) + return rewriteToBridgedCast(E, OBC_BridgeRetained); + + if (family == OMF_autorelease || family == OMF_release) { + std::string err = "it is not safe to cast to '"; + err += E->getType().getAsString(Pass.Ctx.getPrintingPolicy()); + err += "' the result of '"; + err += family == OMF_autorelease ? "autorelease" : "release"; + err += "' message; a __bridge cast may result in a pointer to a " + "destroyed object and a __bridge_retained may leak the object"; + Pass.TA.reportError(err, E->getLocStart(), + E->getSubExpr()->getSourceRange()); + Stmt *parent = E; + do { + parent = StmtMap->getParentIgnoreParenImpCasts(parent); + } while (parent && isa<ExprWithCleanups>(parent)); + + if (ReturnStmt *retS = dyn_cast_or_null<ReturnStmt>(parent)) { + std::string note = "remove the cast and change return type of function " + "to '"; + note += E->getSubExpr()->getType().getAsString(Pass.Ctx.getPrintingPolicy()); + note += "' to have the object automatically autoreleased"; + Pass.TA.reportNote(note, retS->getLocStart()); + } + } + + Expr *subExpr = E->getSubExpr(); + + // Look through pseudo-object expressions. + if (PseudoObjectExpr *pseudo = dyn_cast<PseudoObjectExpr>(subExpr)) { + subExpr = pseudo->getResultExpr(); + assert(subExpr && "no result for pseudo-object of non-void type?"); + } + + if (ImplicitCastExpr *implCE = dyn_cast<ImplicitCastExpr>(subExpr)) { + if (implCE->getCastKind() == CK_ARCConsumeObject) + return rewriteToBridgedCast(E, OBC_BridgeRetained); + if (implCE->getCastKind() == CK_ARCReclaimReturnedObject) + return rewriteToBridgedCast(E, OBC_Bridge); + } + + bool isConsumed = false; + if (isPassedToCParamWithKnownOwnership(E, isConsumed)) + return rewriteToBridgedCast(E, isConsumed ? OBC_BridgeRetained + : OBC_Bridge); + } + + static ObjCMethodFamily getFamilyOfMessage(Expr *E) { + E = E->IgnoreParenCasts(); + if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(E)) + return ME->getMethodFamily(); + + return OMF_None; + } + + bool isPassedToCFRetain(Expr *E, CallExpr *&callE) const { + if ((callE = dyn_cast_or_null<CallExpr>( + StmtMap->getParentIgnoreParenImpCasts(E)))) + if (FunctionDecl * + FD = dyn_cast_or_null<FunctionDecl>(callE->getCalleeDecl())) + if (FD->getName() == "CFRetain" && FD->getNumParams() == 1 && + FD->getParent()->isTranslationUnit() && + FD->isExternallyVisible()) + return true; + + return false; + } + + bool isPassedToCParamWithKnownOwnership(Expr *E, bool &isConsumed) const { + if (CallExpr *callE = dyn_cast_or_null<CallExpr>( + StmtMap->getParentIgnoreParenImpCasts(E))) + if (FunctionDecl * + FD = dyn_cast_or_null<FunctionDecl>(callE->getCalleeDecl())) { + unsigned i = 0; + for (unsigned e = callE->getNumArgs(); i != e; ++i) { + Expr *arg = callE->getArg(i); + if (arg == E || arg->IgnoreParenImpCasts() == E) + break; + } + if (i < callE->getNumArgs() && i < FD->getNumParams()) { + ParmVarDecl *PD = FD->getParamDecl(i); + if (PD->hasAttr<CFConsumedAttr>()) { + isConsumed = true; + return true; + } + } + } + + return false; + } + + bool isSelf(Expr *E) const { + E = E->IgnoreParenLValueCasts(); + if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) + if (ImplicitParamDecl *IPD = dyn_cast<ImplicitParamDecl>(DRE->getDecl())) + if (IPD->getIdentifier() == SelfII) + return true; + + return false; + } +}; + +} // end anonymous namespace + +void trans::rewriteUnbridgedCasts(MigrationPass &pass) { + BodyTransform<UnbridgedCastRewriter> trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransUnusedInitDelegate.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransUnusedInitDelegate.cpp new file mode 100644 index 0000000..70370ec --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransUnusedInitDelegate.cpp @@ -0,0 +1,78 @@ +//===--- TransUnusedInitDelegate.cpp - Transformations to ARC mode --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Transformations: +//===----------------------------------------------------------------------===// +// +// rewriteUnusedInitDelegate: +// +// Rewrites an unused result of calling a delegate initialization, to assigning +// the result to self. +// e.g +// [self init]; +// ----> +// self = [self init]; +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/Sema/SemaDiagnostic.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class UnusedInitRewriter : public RecursiveASTVisitor<UnusedInitRewriter> { + Stmt *Body; + MigrationPass &Pass; + + ExprSet Removables; + +public: + UnusedInitRewriter(MigrationPass &pass) + : Body(nullptr), Pass(pass) { } + + void transformBody(Stmt *body, Decl *ParentD) { + Body = body; + collectRemovables(body, Removables); + TraverseStmt(body); + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *ME) { + if (ME->isDelegateInitCall() && + isRemovable(ME) && + Pass.TA.hasDiagnostic(diag::err_arc_unused_init_message, + ME->getExprLoc())) { + Transaction Trans(Pass.TA); + Pass.TA.clearDiagnostic(diag::err_arc_unused_init_message, + ME->getExprLoc()); + SourceRange ExprRange = ME->getSourceRange(); + Pass.TA.insert(ExprRange.getBegin(), "if (!(self = "); + std::string retStr = ")) return "; + retStr += getNilString(Pass); + Pass.TA.insertAfterToken(ExprRange.getEnd(), retStr); + } + return true; + } + +private: + bool isRemovable(Expr *E) const { + return Removables.count(E); + } +}; + +} // anonymous namespace + +void trans::rewriteUnusedInitDelegate(MigrationPass &pass) { + BodyTransform<UnusedInitRewriter> trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransZeroOutPropsInDealloc.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransZeroOutPropsInDealloc.cpp new file mode 100644 index 0000000..76ce0ec --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransZeroOutPropsInDealloc.cpp @@ -0,0 +1,227 @@ +//===--- TransZeroOutPropsInDealloc.cpp - Transformations to ARC mode -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// removeZeroOutPropsInDealloc: +// +// Removes zero'ing out "strong" @synthesized properties in a -dealloc method. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" + +using namespace clang; +using namespace arcmt; +using namespace trans; + +namespace { + +class ZeroOutInDeallocRemover : + public RecursiveASTVisitor<ZeroOutInDeallocRemover> { + typedef RecursiveASTVisitor<ZeroOutInDeallocRemover> base; + + MigrationPass &Pass; + + llvm::DenseMap<ObjCPropertyDecl*, ObjCPropertyImplDecl*> SynthesizedProperties; + ImplicitParamDecl *SelfD; + ExprSet Removables; + Selector FinalizeSel; + +public: + ZeroOutInDeallocRemover(MigrationPass &pass) : Pass(pass), SelfD(nullptr) { + FinalizeSel = + Pass.Ctx.Selectors.getNullarySelector(&Pass.Ctx.Idents.get("finalize")); + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *ME) { + ASTContext &Ctx = Pass.Ctx; + TransformActions &TA = Pass.TA; + + if (ME->getReceiverKind() != ObjCMessageExpr::Instance) + return true; + Expr *receiver = ME->getInstanceReceiver(); + if (!receiver) + return true; + + DeclRefExpr *refE = dyn_cast<DeclRefExpr>(receiver->IgnoreParenCasts()); + if (!refE || refE->getDecl() != SelfD) + return true; + + bool BackedBySynthesizeSetter = false; + for (llvm::DenseMap<ObjCPropertyDecl*, ObjCPropertyImplDecl*>::iterator + P = SynthesizedProperties.begin(), + E = SynthesizedProperties.end(); P != E; ++P) { + ObjCPropertyDecl *PropDecl = P->first; + if (PropDecl->getSetterName() == ME->getSelector()) { + BackedBySynthesizeSetter = true; + break; + } + } + if (!BackedBySynthesizeSetter) + return true; + + // Remove the setter message if RHS is null + Transaction Trans(TA); + Expr *RHS = ME->getArg(0); + bool RHSIsNull = + RHS->isNullPointerConstant(Ctx, + Expr::NPC_ValueDependentIsNull); + if (RHSIsNull && isRemovable(ME)) + TA.removeStmt(ME); + + return true; + } + + bool VisitPseudoObjectExpr(PseudoObjectExpr *POE) { + if (isZeroingPropIvar(POE) && isRemovable(POE)) { + Transaction Trans(Pass.TA); + Pass.TA.removeStmt(POE); + } + + return true; + } + + bool VisitBinaryOperator(BinaryOperator *BOE) { + if (isZeroingPropIvar(BOE) && isRemovable(BOE)) { + Transaction Trans(Pass.TA); + Pass.TA.removeStmt(BOE); + } + + return true; + } + + bool TraverseObjCMethodDecl(ObjCMethodDecl *D) { + if (D->getMethodFamily() != OMF_dealloc && + !(D->isInstanceMethod() && D->getSelector() == FinalizeSel)) + return true; + if (!D->hasBody()) + return true; + + ObjCImplDecl *IMD = dyn_cast<ObjCImplDecl>(D->getDeclContext()); + if (!IMD) + return true; + + SelfD = D->getSelfDecl(); + collectRemovables(D->getBody(), Removables); + + // For a 'dealloc' method use, find all property implementations in + // this class implementation. + for (auto *PID : IMD->property_impls()) { + if (PID->getPropertyImplementation() == + ObjCPropertyImplDecl::Synthesize) { + ObjCPropertyDecl *PD = PID->getPropertyDecl(); + ObjCMethodDecl *setterM = PD->getSetterMethodDecl(); + if (!(setterM && setterM->isDefined())) { + ObjCPropertyDecl::PropertyAttributeKind AttrKind = + PD->getPropertyAttributes(); + if (AttrKind & + (ObjCPropertyDecl::OBJC_PR_retain | + ObjCPropertyDecl::OBJC_PR_copy | + ObjCPropertyDecl::OBJC_PR_strong)) + SynthesizedProperties[PD] = PID; + } + } + } + + // Now, remove all zeroing of ivars etc. + base::TraverseObjCMethodDecl(D); + + // clear out for next method. + SynthesizedProperties.clear(); + SelfD = nullptr; + Removables.clear(); + return true; + } + + bool TraverseFunctionDecl(FunctionDecl *D) { return true; } + bool TraverseBlockDecl(BlockDecl *block) { return true; } + bool TraverseBlockExpr(BlockExpr *block) { return true; } + +private: + bool isRemovable(Expr *E) const { + return Removables.count(E); + } + + bool isZeroingPropIvar(Expr *E) { + E = E->IgnoreParens(); + if (BinaryOperator *BO = dyn_cast<BinaryOperator>(E)) + return isZeroingPropIvar(BO); + if (PseudoObjectExpr *PO = dyn_cast<PseudoObjectExpr>(E)) + return isZeroingPropIvar(PO); + return false; + } + + bool isZeroingPropIvar(BinaryOperator *BOE) { + if (BOE->getOpcode() == BO_Comma) + return isZeroingPropIvar(BOE->getLHS()) && + isZeroingPropIvar(BOE->getRHS()); + + if (BOE->getOpcode() != BO_Assign) + return false; + + Expr *LHS = BOE->getLHS(); + if (ObjCIvarRefExpr *IV = dyn_cast<ObjCIvarRefExpr>(LHS)) { + ObjCIvarDecl *IVDecl = IV->getDecl(); + if (!IVDecl->getType()->isObjCObjectPointerType()) + return false; + bool IvarBacksPropertySynthesis = false; + for (llvm::DenseMap<ObjCPropertyDecl*, ObjCPropertyImplDecl*>::iterator + P = SynthesizedProperties.begin(), + E = SynthesizedProperties.end(); P != E; ++P) { + ObjCPropertyImplDecl *PropImpDecl = P->second; + if (PropImpDecl && PropImpDecl->getPropertyIvarDecl() == IVDecl) { + IvarBacksPropertySynthesis = true; + break; + } + } + if (!IvarBacksPropertySynthesis) + return false; + } + else + return false; + + return isZero(BOE->getRHS()); + } + + bool isZeroingPropIvar(PseudoObjectExpr *PO) { + BinaryOperator *BO = dyn_cast<BinaryOperator>(PO->getSyntacticForm()); + if (!BO) return false; + if (BO->getOpcode() != BO_Assign) return false; + + ObjCPropertyRefExpr *PropRefExp = + dyn_cast<ObjCPropertyRefExpr>(BO->getLHS()->IgnoreParens()); + if (!PropRefExp) return false; + + // TODO: Using implicit property decl. + if (PropRefExp->isImplicitProperty()) + return false; + + if (ObjCPropertyDecl *PDecl = PropRefExp->getExplicitProperty()) { + if (!SynthesizedProperties.count(PDecl)) + return false; + } + + return isZero(cast<OpaqueValueExpr>(BO->getRHS())->getSourceExpr()); + } + + bool isZero(Expr *E) { + if (E->isNullPointerConstant(Pass.Ctx, Expr::NPC_ValueDependentIsNull)) + return true; + + return isZeroingPropIvar(E); + } +}; + +} // anonymous namespace + +void trans::removeZeroOutPropsInDeallocFinalize(MigrationPass &pass) { + ZeroOutInDeallocRemover trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/TransformActions.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/TransformActions.cpp new file mode 100644 index 0000000..c628b54 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/TransformActions.cpp @@ -0,0 +1,694 @@ +//===--- ARCMT.cpp - Migration to ARC mode --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/DenseSet.h" +#include <map> +using namespace clang; +using namespace arcmt; + +namespace { + +/// \brief Collects transformations and merges them before applying them with +/// with applyRewrites(). E.g. if the same source range +/// is requested to be removed twice, only one rewriter remove will be invoked. +/// Rewrites happen in "transactions"; if one rewrite in the transaction cannot +/// be done (e.g. it resides in a macro) all rewrites in the transaction are +/// aborted. +/// FIXME: "Transactional" rewrites support should be baked in the Rewriter. +class TransformActionsImpl { + CapturedDiagList &CapturedDiags; + ASTContext &Ctx; + Preprocessor &PP; + + bool IsInTransaction; + + enum ActionKind { + Act_Insert, Act_InsertAfterToken, + Act_Remove, Act_RemoveStmt, + Act_Replace, Act_ReplaceText, + Act_IncreaseIndentation, + Act_ClearDiagnostic + }; + + struct ActionData { + ActionKind Kind; + SourceLocation Loc; + SourceRange R1, R2; + StringRef Text1, Text2; + Stmt *S; + SmallVector<unsigned, 2> DiagIDs; + }; + + std::vector<ActionData> CachedActions; + + enum RangeComparison { + Range_Before, + Range_After, + Range_Contains, + Range_Contained, + Range_ExtendsBegin, + Range_ExtendsEnd + }; + + /// \brief A range to remove. It is a character range. + struct CharRange { + FullSourceLoc Begin, End; + + CharRange(CharSourceRange range, SourceManager &srcMgr, Preprocessor &PP) { + SourceLocation beginLoc = range.getBegin(), endLoc = range.getEnd(); + assert(beginLoc.isValid() && endLoc.isValid()); + if (range.isTokenRange()) { + Begin = FullSourceLoc(srcMgr.getExpansionLoc(beginLoc), srcMgr); + End = FullSourceLoc(getLocForEndOfToken(endLoc, srcMgr, PP), srcMgr); + } else { + Begin = FullSourceLoc(srcMgr.getExpansionLoc(beginLoc), srcMgr); + End = FullSourceLoc(srcMgr.getExpansionLoc(endLoc), srcMgr); + } + assert(Begin.isValid() && End.isValid()); + } + + RangeComparison compareWith(const CharRange &RHS) const { + if (End.isBeforeInTranslationUnitThan(RHS.Begin)) + return Range_Before; + if (RHS.End.isBeforeInTranslationUnitThan(Begin)) + return Range_After; + if (!Begin.isBeforeInTranslationUnitThan(RHS.Begin) && + !RHS.End.isBeforeInTranslationUnitThan(End)) + return Range_Contained; + if (Begin.isBeforeInTranslationUnitThan(RHS.Begin) && + RHS.End.isBeforeInTranslationUnitThan(End)) + return Range_Contains; + if (Begin.isBeforeInTranslationUnitThan(RHS.Begin)) + return Range_ExtendsBegin; + else + return Range_ExtendsEnd; + } + + static RangeComparison compare(SourceRange LHS, SourceRange RHS, + SourceManager &SrcMgr, Preprocessor &PP) { + return CharRange(CharSourceRange::getTokenRange(LHS), SrcMgr, PP) + .compareWith(CharRange(CharSourceRange::getTokenRange(RHS), + SrcMgr, PP)); + } + }; + + typedef SmallVector<StringRef, 2> TextsVec; + typedef std::map<FullSourceLoc, TextsVec, FullSourceLoc::BeforeThanCompare> + InsertsMap; + InsertsMap Inserts; + /// \brief A list of ranges to remove. They are always sorted and they never + /// intersect with each other. + std::list<CharRange> Removals; + + llvm::DenseSet<Stmt *> StmtRemovals; + + std::vector<std::pair<CharRange, SourceLocation> > IndentationRanges; + + /// \brief Keeps text passed to transformation methods. + llvm::StringMap<bool> UniqueText; + +public: + TransformActionsImpl(CapturedDiagList &capturedDiags, + ASTContext &ctx, Preprocessor &PP) + : CapturedDiags(capturedDiags), Ctx(ctx), PP(PP), IsInTransaction(false) { } + + ASTContext &getASTContext() { return Ctx; } + + void startTransaction(); + bool commitTransaction(); + void abortTransaction(); + + bool isInTransaction() const { return IsInTransaction; } + + void insert(SourceLocation loc, StringRef text); + void insertAfterToken(SourceLocation loc, StringRef text); + void remove(SourceRange range); + void removeStmt(Stmt *S); + void replace(SourceRange range, StringRef text); + void replace(SourceRange range, SourceRange replacementRange); + void replaceStmt(Stmt *S, StringRef text); + void replaceText(SourceLocation loc, StringRef text, + StringRef replacementText); + void increaseIndentation(SourceRange range, + SourceLocation parentIndent); + + bool clearDiagnostic(ArrayRef<unsigned> IDs, SourceRange range); + + void applyRewrites(TransformActions::RewriteReceiver &receiver); + +private: + bool canInsert(SourceLocation loc); + bool canInsertAfterToken(SourceLocation loc); + bool canRemoveRange(SourceRange range); + bool canReplaceRange(SourceRange range, SourceRange replacementRange); + bool canReplaceText(SourceLocation loc, StringRef text); + + void commitInsert(SourceLocation loc, StringRef text); + void commitInsertAfterToken(SourceLocation loc, StringRef text); + void commitRemove(SourceRange range); + void commitRemoveStmt(Stmt *S); + void commitReplace(SourceRange range, SourceRange replacementRange); + void commitReplaceText(SourceLocation loc, StringRef text, + StringRef replacementText); + void commitIncreaseIndentation(SourceRange range,SourceLocation parentIndent); + void commitClearDiagnostic(ArrayRef<unsigned> IDs, SourceRange range); + + void addRemoval(CharSourceRange range); + void addInsertion(SourceLocation loc, StringRef text); + + /// \brief Stores text passed to the transformation methods to keep the string + /// "alive". Since the vast majority of text will be the same, we also unique + /// the strings using a StringMap. + StringRef getUniqueText(StringRef text); + + /// \brief Computes the source location just past the end of the token at + /// the given source location. If the location points at a macro, the whole + /// macro expansion is skipped. + static SourceLocation getLocForEndOfToken(SourceLocation loc, + SourceManager &SM,Preprocessor &PP); +}; + +} // anonymous namespace + +void TransformActionsImpl::startTransaction() { + assert(!IsInTransaction && + "Cannot start a transaction in the middle of another one"); + IsInTransaction = true; +} + +bool TransformActionsImpl::commitTransaction() { + assert(IsInTransaction && "No transaction started"); + + if (CachedActions.empty()) { + IsInTransaction = false; + return false; + } + + // Verify that all actions are possible otherwise abort the whole transaction. + bool AllActionsPossible = true; + for (unsigned i = 0, e = CachedActions.size(); i != e; ++i) { + ActionData &act = CachedActions[i]; + switch (act.Kind) { + case Act_Insert: + if (!canInsert(act.Loc)) + AllActionsPossible = false; + break; + case Act_InsertAfterToken: + if (!canInsertAfterToken(act.Loc)) + AllActionsPossible = false; + break; + case Act_Remove: + if (!canRemoveRange(act.R1)) + AllActionsPossible = false; + break; + case Act_RemoveStmt: + assert(act.S); + if (!canRemoveRange(act.S->getSourceRange())) + AllActionsPossible = false; + break; + case Act_Replace: + if (!canReplaceRange(act.R1, act.R2)) + AllActionsPossible = false; + break; + case Act_ReplaceText: + if (!canReplaceText(act.Loc, act.Text1)) + AllActionsPossible = false; + break; + case Act_IncreaseIndentation: + // This is not important, we don't care if it will fail. + break; + case Act_ClearDiagnostic: + // We are just checking source rewrites. + break; + } + if (!AllActionsPossible) + break; + } + + if (!AllActionsPossible) { + abortTransaction(); + return true; + } + + for (unsigned i = 0, e = CachedActions.size(); i != e; ++i) { + ActionData &act = CachedActions[i]; + switch (act.Kind) { + case Act_Insert: + commitInsert(act.Loc, act.Text1); + break; + case Act_InsertAfterToken: + commitInsertAfterToken(act.Loc, act.Text1); + break; + case Act_Remove: + commitRemove(act.R1); + break; + case Act_RemoveStmt: + commitRemoveStmt(act.S); + break; + case Act_Replace: + commitReplace(act.R1, act.R2); + break; + case Act_ReplaceText: + commitReplaceText(act.Loc, act.Text1, act.Text2); + break; + case Act_IncreaseIndentation: + commitIncreaseIndentation(act.R1, act.Loc); + break; + case Act_ClearDiagnostic: + commitClearDiagnostic(act.DiagIDs, act.R1); + break; + } + } + + CachedActions.clear(); + IsInTransaction = false; + return false; +} + +void TransformActionsImpl::abortTransaction() { + assert(IsInTransaction && "No transaction started"); + CachedActions.clear(); + IsInTransaction = false; +} + +void TransformActionsImpl::insert(SourceLocation loc, StringRef text) { + assert(IsInTransaction && "Actions only allowed during a transaction"); + text = getUniqueText(text); + ActionData data; + data.Kind = Act_Insert; + data.Loc = loc; + data.Text1 = text; + CachedActions.push_back(data); +} + +void TransformActionsImpl::insertAfterToken(SourceLocation loc, StringRef text) { + assert(IsInTransaction && "Actions only allowed during a transaction"); + text = getUniqueText(text); + ActionData data; + data.Kind = Act_InsertAfterToken; + data.Loc = loc; + data.Text1 = text; + CachedActions.push_back(data); +} + +void TransformActionsImpl::remove(SourceRange range) { + assert(IsInTransaction && "Actions only allowed during a transaction"); + ActionData data; + data.Kind = Act_Remove; + data.R1 = range; + CachedActions.push_back(data); +} + +void TransformActionsImpl::removeStmt(Stmt *S) { + assert(IsInTransaction && "Actions only allowed during a transaction"); + ActionData data; + data.Kind = Act_RemoveStmt; + data.S = S->IgnoreImplicit(); // important for uniquing + CachedActions.push_back(data); +} + +void TransformActionsImpl::replace(SourceRange range, StringRef text) { + assert(IsInTransaction && "Actions only allowed during a transaction"); + text = getUniqueText(text); + remove(range); + insert(range.getBegin(), text); +} + +void TransformActionsImpl::replace(SourceRange range, + SourceRange replacementRange) { + assert(IsInTransaction && "Actions only allowed during a transaction"); + ActionData data; + data.Kind = Act_Replace; + data.R1 = range; + data.R2 = replacementRange; + CachedActions.push_back(data); +} + +void TransformActionsImpl::replaceText(SourceLocation loc, StringRef text, + StringRef replacementText) { + text = getUniqueText(text); + replacementText = getUniqueText(replacementText); + ActionData data; + data.Kind = Act_ReplaceText; + data.Loc = loc; + data.Text1 = text; + data.Text2 = replacementText; + CachedActions.push_back(data); +} + +void TransformActionsImpl::replaceStmt(Stmt *S, StringRef text) { + assert(IsInTransaction && "Actions only allowed during a transaction"); + text = getUniqueText(text); + insert(S->getLocStart(), text); + removeStmt(S); +} + +void TransformActionsImpl::increaseIndentation(SourceRange range, + SourceLocation parentIndent) { + if (range.isInvalid()) return; + assert(IsInTransaction && "Actions only allowed during a transaction"); + ActionData data; + data.Kind = Act_IncreaseIndentation; + data.R1 = range; + data.Loc = parentIndent; + CachedActions.push_back(data); +} + +bool TransformActionsImpl::clearDiagnostic(ArrayRef<unsigned> IDs, + SourceRange range) { + assert(IsInTransaction && "Actions only allowed during a transaction"); + if (!CapturedDiags.hasDiagnostic(IDs, range)) + return false; + + ActionData data; + data.Kind = Act_ClearDiagnostic; + data.R1 = range; + data.DiagIDs.append(IDs.begin(), IDs.end()); + CachedActions.push_back(data); + return true; +} + +bool TransformActionsImpl::canInsert(SourceLocation loc) { + if (loc.isInvalid()) + return false; + + SourceManager &SM = Ctx.getSourceManager(); + if (SM.isInSystemHeader(SM.getExpansionLoc(loc))) + return false; + + if (loc.isFileID()) + return true; + return PP.isAtStartOfMacroExpansion(loc); +} + +bool TransformActionsImpl::canInsertAfterToken(SourceLocation loc) { + if (loc.isInvalid()) + return false; + + SourceManager &SM = Ctx.getSourceManager(); + if (SM.isInSystemHeader(SM.getExpansionLoc(loc))) + return false; + + if (loc.isFileID()) + return true; + return PP.isAtEndOfMacroExpansion(loc); +} + +bool TransformActionsImpl::canRemoveRange(SourceRange range) { + return canInsert(range.getBegin()) && canInsertAfterToken(range.getEnd()); +} + +bool TransformActionsImpl::canReplaceRange(SourceRange range, + SourceRange replacementRange) { + return canRemoveRange(range) && canRemoveRange(replacementRange); +} + +bool TransformActionsImpl::canReplaceText(SourceLocation loc, StringRef text) { + if (!canInsert(loc)) + return false; + + SourceManager &SM = Ctx.getSourceManager(); + loc = SM.getExpansionLoc(loc); + + // Break down the source location. + std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); + + // Try to load the file buffer. + bool invalidTemp = false; + StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); + if (invalidTemp) + return false; + + return file.substr(locInfo.second).startswith(text); +} + +void TransformActionsImpl::commitInsert(SourceLocation loc, StringRef text) { + addInsertion(loc, text); +} + +void TransformActionsImpl::commitInsertAfterToken(SourceLocation loc, + StringRef text) { + addInsertion(getLocForEndOfToken(loc, Ctx.getSourceManager(), PP), text); +} + +void TransformActionsImpl::commitRemove(SourceRange range) { + addRemoval(CharSourceRange::getTokenRange(range)); +} + +void TransformActionsImpl::commitRemoveStmt(Stmt *S) { + assert(S); + if (StmtRemovals.count(S)) + return; // already removed. + + if (Expr *E = dyn_cast<Expr>(S)) { + commitRemove(E->getSourceRange()); + commitInsert(E->getSourceRange().getBegin(), getARCMTMacroName()); + } else + commitRemove(S->getSourceRange()); + + StmtRemovals.insert(S); +} + +void TransformActionsImpl::commitReplace(SourceRange range, + SourceRange replacementRange) { + RangeComparison comp = CharRange::compare(replacementRange, range, + Ctx.getSourceManager(), PP); + assert(comp == Range_Contained); + if (comp != Range_Contained) + return; // Although we asserted, be extra safe for release build. + if (range.getBegin() != replacementRange.getBegin()) + addRemoval(CharSourceRange::getCharRange(range.getBegin(), + replacementRange.getBegin())); + if (replacementRange.getEnd() != range.getEnd()) + addRemoval(CharSourceRange::getTokenRange( + getLocForEndOfToken(replacementRange.getEnd(), + Ctx.getSourceManager(), PP), + range.getEnd())); +} +void TransformActionsImpl::commitReplaceText(SourceLocation loc, + StringRef text, + StringRef replacementText) { + SourceManager &SM = Ctx.getSourceManager(); + loc = SM.getExpansionLoc(loc); + // canReplaceText already checked if loc points at text. + SourceLocation afterText = loc.getLocWithOffset(text.size()); + + addRemoval(CharSourceRange::getCharRange(loc, afterText)); + commitInsert(loc, replacementText); +} + +void TransformActionsImpl::commitIncreaseIndentation(SourceRange range, + SourceLocation parentIndent) { + SourceManager &SM = Ctx.getSourceManager(); + IndentationRanges.push_back( + std::make_pair(CharRange(CharSourceRange::getTokenRange(range), + SM, PP), + SM.getExpansionLoc(parentIndent))); +} + +void TransformActionsImpl::commitClearDiagnostic(ArrayRef<unsigned> IDs, + SourceRange range) { + CapturedDiags.clearDiagnostic(IDs, range); +} + +void TransformActionsImpl::addInsertion(SourceLocation loc, StringRef text) { + SourceManager &SM = Ctx.getSourceManager(); + loc = SM.getExpansionLoc(loc); + for (const CharRange &I : llvm::reverse(Removals)) { + if (!SM.isBeforeInTranslationUnit(loc, I.End)) + break; + if (I.Begin.isBeforeInTranslationUnitThan(loc)) + return; + } + + Inserts[FullSourceLoc(loc, SM)].push_back(text); +} + +void TransformActionsImpl::addRemoval(CharSourceRange range) { + CharRange newRange(range, Ctx.getSourceManager(), PP); + if (newRange.Begin == newRange.End) + return; + + Inserts.erase(Inserts.upper_bound(newRange.Begin), + Inserts.lower_bound(newRange.End)); + + std::list<CharRange>::iterator I = Removals.end(); + while (I != Removals.begin()) { + std::list<CharRange>::iterator RI = I; + --RI; + RangeComparison comp = newRange.compareWith(*RI); + switch (comp) { + case Range_Before: + --I; + break; + case Range_After: + Removals.insert(I, newRange); + return; + case Range_Contained: + return; + case Range_Contains: + RI->End = newRange.End; + case Range_ExtendsBegin: + newRange.End = RI->End; + Removals.erase(RI); + break; + case Range_ExtendsEnd: + RI->End = newRange.End; + return; + } + } + + Removals.insert(Removals.begin(), newRange); +} + +void TransformActionsImpl::applyRewrites( + TransformActions::RewriteReceiver &receiver) { + for (InsertsMap::iterator I = Inserts.begin(), E = Inserts.end(); I!=E; ++I) { + SourceLocation loc = I->first; + for (TextsVec::iterator + TI = I->second.begin(), TE = I->second.end(); TI != TE; ++TI) { + receiver.insert(loc, *TI); + } + } + + for (std::vector<std::pair<CharRange, SourceLocation> >::iterator + I = IndentationRanges.begin(), E = IndentationRanges.end(); I!=E; ++I) { + CharSourceRange range = CharSourceRange::getCharRange(I->first.Begin, + I->first.End); + receiver.increaseIndentation(range, I->second); + } + + for (std::list<CharRange>::iterator + I = Removals.begin(), E = Removals.end(); I != E; ++I) { + CharSourceRange range = CharSourceRange::getCharRange(I->Begin, I->End); + receiver.remove(range); + } +} + +/// \brief Stores text passed to the transformation methods to keep the string +/// "alive". Since the vast majority of text will be the same, we also unique +/// the strings using a StringMap. +StringRef TransformActionsImpl::getUniqueText(StringRef text) { + return UniqueText.insert(std::make_pair(text, false)).first->first(); +} + +/// \brief Computes the source location just past the end of the token at +/// the given source location. If the location points at a macro, the whole +/// macro expansion is skipped. +SourceLocation TransformActionsImpl::getLocForEndOfToken(SourceLocation loc, + SourceManager &SM, + Preprocessor &PP) { + if (loc.isMacroID()) + loc = SM.getExpansionRange(loc).second; + return PP.getLocForEndOfToken(loc); +} + +TransformActions::RewriteReceiver::~RewriteReceiver() { } + +TransformActions::TransformActions(DiagnosticsEngine &diag, + CapturedDiagList &capturedDiags, + ASTContext &ctx, Preprocessor &PP) + : Diags(diag), CapturedDiags(capturedDiags) { + Impl = new TransformActionsImpl(capturedDiags, ctx, PP); +} + +TransformActions::~TransformActions() { + delete static_cast<TransformActionsImpl*>(Impl); +} + +void TransformActions::startTransaction() { + static_cast<TransformActionsImpl*>(Impl)->startTransaction(); +} + +bool TransformActions::commitTransaction() { + return static_cast<TransformActionsImpl*>(Impl)->commitTransaction(); +} + +void TransformActions::abortTransaction() { + static_cast<TransformActionsImpl*>(Impl)->abortTransaction(); +} + + +void TransformActions::insert(SourceLocation loc, StringRef text) { + static_cast<TransformActionsImpl*>(Impl)->insert(loc, text); +} + +void TransformActions::insertAfterToken(SourceLocation loc, + StringRef text) { + static_cast<TransformActionsImpl*>(Impl)->insertAfterToken(loc, text); +} + +void TransformActions::remove(SourceRange range) { + static_cast<TransformActionsImpl*>(Impl)->remove(range); +} + +void TransformActions::removeStmt(Stmt *S) { + static_cast<TransformActionsImpl*>(Impl)->removeStmt(S); +} + +void TransformActions::replace(SourceRange range, StringRef text) { + static_cast<TransformActionsImpl*>(Impl)->replace(range, text); +} + +void TransformActions::replace(SourceRange range, + SourceRange replacementRange) { + static_cast<TransformActionsImpl*>(Impl)->replace(range, replacementRange); +} + +void TransformActions::replaceStmt(Stmt *S, StringRef text) { + static_cast<TransformActionsImpl*>(Impl)->replaceStmt(S, text); +} + +void TransformActions::replaceText(SourceLocation loc, StringRef text, + StringRef replacementText) { + static_cast<TransformActionsImpl*>(Impl)->replaceText(loc, text, + replacementText); +} + +void TransformActions::increaseIndentation(SourceRange range, + SourceLocation parentIndent) { + static_cast<TransformActionsImpl*>(Impl)->increaseIndentation(range, + parentIndent); +} + +bool TransformActions::clearDiagnostic(ArrayRef<unsigned> IDs, + SourceRange range) { + return static_cast<TransformActionsImpl*>(Impl)->clearDiagnostic(IDs, range); +} + +void TransformActions::applyRewrites(RewriteReceiver &receiver) { + static_cast<TransformActionsImpl*>(Impl)->applyRewrites(receiver); +} + +DiagnosticBuilder TransformActions::report(SourceLocation loc, unsigned diagId, + SourceRange range) { + assert(!static_cast<TransformActionsImpl *>(Impl)->isInTransaction() && + "Errors should be emitted out of a transaction"); + return Diags.Report(loc, diagId) << range; +} + +void TransformActions::reportError(StringRef message, SourceLocation loc, + SourceRange range) { + report(loc, diag::err_mt_message, range) << message; +} + +void TransformActions::reportWarning(StringRef message, SourceLocation loc, + SourceRange range) { + report(loc, diag::warn_mt_message, range) << message; +} + +void TransformActions::reportNote(StringRef message, SourceLocation loc, + SourceRange range) { + report(loc, diag::note_mt_message, range) << message; +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/Transforms.cpp b/contrib/llvm/tools/clang/lib/ARCMigrate/Transforms.cpp new file mode 100644 index 0000000..3fd36ff --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/Transforms.cpp @@ -0,0 +1,599 @@ +//===--- Transforms.cpp - Transformations to ARC mode ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Transforms.h" +#include "Internals.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Sema/Sema.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringSwitch.h" +#include <map> + +using namespace clang; +using namespace arcmt; +using namespace trans; + +ASTTraverser::~ASTTraverser() { } + +bool MigrationPass::CFBridgingFunctionsDefined() { + if (!EnableCFBridgeFns.hasValue()) + EnableCFBridgeFns = SemaRef.isKnownName("CFBridgingRetain") && + SemaRef.isKnownName("CFBridgingRelease"); + return *EnableCFBridgeFns; +} + +//===----------------------------------------------------------------------===// +// Helpers. +//===----------------------------------------------------------------------===// + +bool trans::canApplyWeak(ASTContext &Ctx, QualType type, + bool AllowOnUnknownClass) { + if (!Ctx.getLangOpts().ObjCWeakRuntime) + return false; + + QualType T = type; + if (T.isNull()) + return false; + + // iOS is always safe to use 'weak'. + if (Ctx.getTargetInfo().getTriple().isiOS() || + Ctx.getTargetInfo().getTriple().isWatchOS()) + AllowOnUnknownClass = true; + + while (const PointerType *ptr = T->getAs<PointerType>()) + T = ptr->getPointeeType(); + if (const ObjCObjectPointerType *ObjT = T->getAs<ObjCObjectPointerType>()) { + ObjCInterfaceDecl *Class = ObjT->getInterfaceDecl(); + if (!AllowOnUnknownClass && (!Class || Class->getName() == "NSObject")) + return false; // id/NSObject is not safe for weak. + if (!AllowOnUnknownClass && !Class->hasDefinition()) + return false; // forward classes are not verifiable, therefore not safe. + if (Class && Class->isArcWeakrefUnavailable()) + return false; + } + + return true; +} + +bool trans::isPlusOneAssign(const BinaryOperator *E) { + if (E->getOpcode() != BO_Assign) + return false; + + return isPlusOne(E->getRHS()); +} + +bool trans::isPlusOne(const Expr *E) { + if (!E) + return false; + if (const ExprWithCleanups *EWC = dyn_cast<ExprWithCleanups>(E)) + E = EWC->getSubExpr(); + + if (const ObjCMessageExpr * + ME = dyn_cast<ObjCMessageExpr>(E->IgnoreParenCasts())) + if (ME->getMethodFamily() == OMF_retain) + return true; + + if (const CallExpr * + callE = dyn_cast<CallExpr>(E->IgnoreParenCasts())) { + if (const FunctionDecl *FD = callE->getDirectCallee()) { + if (FD->hasAttr<CFReturnsRetainedAttr>()) + return true; + + if (FD->isGlobal() && + FD->getIdentifier() && + FD->getParent()->isTranslationUnit() && + FD->isExternallyVisible() && + ento::cocoa::isRefType(callE->getType(), "CF", + FD->getIdentifier()->getName())) { + StringRef fname = FD->getIdentifier()->getName(); + if (fname.endswith("Retain") || + fname.find("Create") != StringRef::npos || + fname.find("Copy") != StringRef::npos) { + return true; + } + } + } + } + + const ImplicitCastExpr *implCE = dyn_cast<ImplicitCastExpr>(E); + while (implCE && implCE->getCastKind() == CK_BitCast) + implCE = dyn_cast<ImplicitCastExpr>(implCE->getSubExpr()); + + return implCE && implCE->getCastKind() == CK_ARCConsumeObject; +} + +/// \brief 'Loc' is the end of a statement range. This returns the location +/// immediately after the semicolon following the statement. +/// If no semicolon is found or the location is inside a macro, the returned +/// source location will be invalid. +SourceLocation trans::findLocationAfterSemi(SourceLocation loc, + ASTContext &Ctx, bool IsDecl) { + SourceLocation SemiLoc = findSemiAfterLocation(loc, Ctx, IsDecl); + if (SemiLoc.isInvalid()) + return SourceLocation(); + return SemiLoc.getLocWithOffset(1); +} + +/// \brief \arg Loc is the end of a statement range. This returns the location +/// of the semicolon following the statement. +/// If no semicolon is found or the location is inside a macro, the returned +/// source location will be invalid. +SourceLocation trans::findSemiAfterLocation(SourceLocation loc, + ASTContext &Ctx, + bool IsDecl) { + SourceManager &SM = Ctx.getSourceManager(); + if (loc.isMacroID()) { + if (!Lexer::isAtEndOfMacroExpansion(loc, SM, Ctx.getLangOpts(), &loc)) + return SourceLocation(); + } + loc = Lexer::getLocForEndOfToken(loc, /*Offset=*/0, SM, Ctx.getLangOpts()); + + // Break down the source location. + std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); + + // Try to load the file buffer. + bool invalidTemp = false; + StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); + if (invalidTemp) + return SourceLocation(); + + const char *tokenBegin = file.data() + locInfo.second; + + // Lex from the start of the given location. + Lexer lexer(SM.getLocForStartOfFile(locInfo.first), + Ctx.getLangOpts(), + file.begin(), tokenBegin, file.end()); + Token tok; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::semi)) { + if (!IsDecl) + return SourceLocation(); + // Declaration may be followed with other tokens; such as an __attribute, + // before ending with a semicolon. + return findSemiAfterLocation(tok.getLocation(), Ctx, /*IsDecl*/true); + } + + return tok.getLocation(); +} + +bool trans::hasSideEffects(Expr *E, ASTContext &Ctx) { + if (!E || !E->HasSideEffects(Ctx)) + return false; + + E = E->IgnoreParenCasts(); + ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(E); + if (!ME) + return true; + switch (ME->getMethodFamily()) { + case OMF_autorelease: + case OMF_dealloc: + case OMF_release: + case OMF_retain: + switch (ME->getReceiverKind()) { + case ObjCMessageExpr::SuperInstance: + return false; + case ObjCMessageExpr::Instance: + return hasSideEffects(ME->getInstanceReceiver(), Ctx); + default: + break; + } + break; + default: + break; + } + + return true; +} + +bool trans::isGlobalVar(Expr *E) { + E = E->IgnoreParenCasts(); + if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) + return DRE->getDecl()->getDeclContext()->isFileContext() && + DRE->getDecl()->isExternallyVisible(); + if (ConditionalOperator *condOp = dyn_cast<ConditionalOperator>(E)) + return isGlobalVar(condOp->getTrueExpr()) && + isGlobalVar(condOp->getFalseExpr()); + + return false; +} + +StringRef trans::getNilString(MigrationPass &Pass) { + return Pass.SemaRef.PP.isMacroDefined("nil") ? "nil" : "0"; +} + +namespace { + +class ReferenceClear : public RecursiveASTVisitor<ReferenceClear> { + ExprSet &Refs; +public: + ReferenceClear(ExprSet &refs) : Refs(refs) { } + bool VisitDeclRefExpr(DeclRefExpr *E) { Refs.erase(E); return true; } +}; + +class ReferenceCollector : public RecursiveASTVisitor<ReferenceCollector> { + ValueDecl *Dcl; + ExprSet &Refs; + +public: + ReferenceCollector(ValueDecl *D, ExprSet &refs) + : Dcl(D), Refs(refs) { } + + bool VisitDeclRefExpr(DeclRefExpr *E) { + if (E->getDecl() == Dcl) + Refs.insert(E); + return true; + } +}; + +class RemovablesCollector : public RecursiveASTVisitor<RemovablesCollector> { + ExprSet &Removables; + +public: + RemovablesCollector(ExprSet &removables) + : Removables(removables) { } + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool TraverseStmtExpr(StmtExpr *E) { + CompoundStmt *S = E->getSubStmt(); + for (CompoundStmt::body_iterator + I = S->body_begin(), E = S->body_end(); I != E; ++I) { + if (I != E - 1) + mark(*I); + TraverseStmt(*I); + } + return true; + } + + bool VisitCompoundStmt(CompoundStmt *S) { + for (auto *I : S->body()) + mark(I); + return true; + } + + bool VisitIfStmt(IfStmt *S) { + mark(S->getThen()); + mark(S->getElse()); + return true; + } + + bool VisitWhileStmt(WhileStmt *S) { + mark(S->getBody()); + return true; + } + + bool VisitDoStmt(DoStmt *S) { + mark(S->getBody()); + return true; + } + + bool VisitForStmt(ForStmt *S) { + mark(S->getInit()); + mark(S->getInc()); + mark(S->getBody()); + return true; + } + +private: + void mark(Stmt *S) { + if (!S) return; + + while (LabelStmt *Label = dyn_cast<LabelStmt>(S)) + S = Label->getSubStmt(); + S = S->IgnoreImplicit(); + if (Expr *E = dyn_cast<Expr>(S)) + Removables.insert(E); + } +}; + +} // end anonymous namespace + +void trans::clearRefsIn(Stmt *S, ExprSet &refs) { + ReferenceClear(refs).TraverseStmt(S); +} + +void trans::collectRefs(ValueDecl *D, Stmt *S, ExprSet &refs) { + ReferenceCollector(D, refs).TraverseStmt(S); +} + +void trans::collectRemovables(Stmt *S, ExprSet &exprs) { + RemovablesCollector(exprs).TraverseStmt(S); +} + +//===----------------------------------------------------------------------===// +// MigrationContext +//===----------------------------------------------------------------------===// + +namespace { + +class ASTTransform : public RecursiveASTVisitor<ASTTransform> { + MigrationContext &MigrateCtx; + typedef RecursiveASTVisitor<ASTTransform> base; + +public: + ASTTransform(MigrationContext &MigrateCtx) : MigrateCtx(MigrateCtx) { } + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool TraverseObjCImplementationDecl(ObjCImplementationDecl *D) { + ObjCImplementationContext ImplCtx(MigrateCtx, D); + for (MigrationContext::traverser_iterator + I = MigrateCtx.traversers_begin(), + E = MigrateCtx.traversers_end(); I != E; ++I) + (*I)->traverseObjCImplementation(ImplCtx); + + return base::TraverseObjCImplementationDecl(D); + } + + bool TraverseStmt(Stmt *rootS) { + if (!rootS) + return true; + + BodyContext BodyCtx(MigrateCtx, rootS); + for (MigrationContext::traverser_iterator + I = MigrateCtx.traversers_begin(), + E = MigrateCtx.traversers_end(); I != E; ++I) + (*I)->traverseBody(BodyCtx); + + return true; + } +}; + +} + +MigrationContext::~MigrationContext() { + for (traverser_iterator + I = traversers_begin(), E = traversers_end(); I != E; ++I) + delete *I; +} + +bool MigrationContext::isGCOwnedNonObjC(QualType T) { + while (!T.isNull()) { + if (const AttributedType *AttrT = T->getAs<AttributedType>()) { + if (AttrT->getAttrKind() == AttributedType::attr_objc_ownership) + return !AttrT->getModifiedType()->isObjCRetainableType(); + } + + if (T->isArrayType()) + T = Pass.Ctx.getBaseElementType(T); + else if (const PointerType *PT = T->getAs<PointerType>()) + T = PT->getPointeeType(); + else if (const ReferenceType *RT = T->getAs<ReferenceType>()) + T = RT->getPointeeType(); + else + break; + } + + return false; +} + +bool MigrationContext::rewritePropertyAttribute(StringRef fromAttr, + StringRef toAttr, + SourceLocation atLoc) { + if (atLoc.isMacroID()) + return false; + + SourceManager &SM = Pass.Ctx.getSourceManager(); + + // Break down the source location. + std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(atLoc); + + // Try to load the file buffer. + bool invalidTemp = false; + StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); + if (invalidTemp) + return false; + + const char *tokenBegin = file.data() + locInfo.second; + + // Lex from the start of the given location. + Lexer lexer(SM.getLocForStartOfFile(locInfo.first), + Pass.Ctx.getLangOpts(), + file.begin(), tokenBegin, file.end()); + Token tok; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::at)) return false; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::raw_identifier)) return false; + if (tok.getRawIdentifier() != "property") + return false; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::l_paren)) return false; + + Token BeforeTok = tok; + Token AfterTok; + AfterTok.startToken(); + SourceLocation AttrLoc; + + lexer.LexFromRawLexer(tok); + if (tok.is(tok::r_paren)) + return false; + + while (1) { + if (tok.isNot(tok::raw_identifier)) return false; + if (tok.getRawIdentifier() == fromAttr) { + if (!toAttr.empty()) { + Pass.TA.replaceText(tok.getLocation(), fromAttr, toAttr); + return true; + } + // We want to remove the attribute. + AttrLoc = tok.getLocation(); + } + + do { + lexer.LexFromRawLexer(tok); + if (AttrLoc.isValid() && AfterTok.is(tok::unknown)) + AfterTok = tok; + } while (tok.isNot(tok::comma) && tok.isNot(tok::r_paren)); + if (tok.is(tok::r_paren)) + break; + if (AttrLoc.isInvalid()) + BeforeTok = tok; + lexer.LexFromRawLexer(tok); + } + + if (toAttr.empty() && AttrLoc.isValid() && AfterTok.isNot(tok::unknown)) { + // We want to remove the attribute. + if (BeforeTok.is(tok::l_paren) && AfterTok.is(tok::r_paren)) { + Pass.TA.remove(SourceRange(BeforeTok.getLocation(), + AfterTok.getLocation())); + } else if (BeforeTok.is(tok::l_paren) && AfterTok.is(tok::comma)) { + Pass.TA.remove(SourceRange(AttrLoc, AfterTok.getLocation())); + } else { + Pass.TA.remove(SourceRange(BeforeTok.getLocation(), AttrLoc)); + } + + return true; + } + + return false; +} + +bool MigrationContext::addPropertyAttribute(StringRef attr, + SourceLocation atLoc) { + if (atLoc.isMacroID()) + return false; + + SourceManager &SM = Pass.Ctx.getSourceManager(); + + // Break down the source location. + std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(atLoc); + + // Try to load the file buffer. + bool invalidTemp = false; + StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); + if (invalidTemp) + return false; + + const char *tokenBegin = file.data() + locInfo.second; + + // Lex from the start of the given location. + Lexer lexer(SM.getLocForStartOfFile(locInfo.first), + Pass.Ctx.getLangOpts(), + file.begin(), tokenBegin, file.end()); + Token tok; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::at)) return false; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::raw_identifier)) return false; + if (tok.getRawIdentifier() != "property") + return false; + lexer.LexFromRawLexer(tok); + + if (tok.isNot(tok::l_paren)) { + Pass.TA.insert(tok.getLocation(), std::string("(") + attr.str() + ") "); + return true; + } + + lexer.LexFromRawLexer(tok); + if (tok.is(tok::r_paren)) { + Pass.TA.insert(tok.getLocation(), attr); + return true; + } + + if (tok.isNot(tok::raw_identifier)) return false; + + Pass.TA.insert(tok.getLocation(), std::string(attr) + ", "); + return true; +} + +void MigrationContext::traverse(TranslationUnitDecl *TU) { + for (traverser_iterator + I = traversers_begin(), E = traversers_end(); I != E; ++I) + (*I)->traverseTU(*this); + + ASTTransform(*this).TraverseDecl(TU); +} + +static void GCRewriteFinalize(MigrationPass &pass) { + ASTContext &Ctx = pass.Ctx; + TransformActions &TA = pass.TA; + DeclContext *DC = Ctx.getTranslationUnitDecl(); + Selector FinalizeSel = + Ctx.Selectors.getNullarySelector(&pass.Ctx.Idents.get("finalize")); + + typedef DeclContext::specific_decl_iterator<ObjCImplementationDecl> + impl_iterator; + for (impl_iterator I = impl_iterator(DC->decls_begin()), + E = impl_iterator(DC->decls_end()); I != E; ++I) { + for (const auto *MD : I->instance_methods()) { + if (!MD->hasBody()) + continue; + + if (MD->isInstanceMethod() && MD->getSelector() == FinalizeSel) { + const ObjCMethodDecl *FinalizeM = MD; + Transaction Trans(TA); + TA.insert(FinalizeM->getSourceRange().getBegin(), + "#if !__has_feature(objc_arc)\n"); + CharSourceRange::getTokenRange(FinalizeM->getSourceRange()); + const SourceManager &SM = pass.Ctx.getSourceManager(); + const LangOptions &LangOpts = pass.Ctx.getLangOpts(); + bool Invalid; + std::string str = "\n#endif\n"; + str += Lexer::getSourceText( + CharSourceRange::getTokenRange(FinalizeM->getSourceRange()), + SM, LangOpts, &Invalid); + TA.insertAfterToken(FinalizeM->getSourceRange().getEnd(), str); + + break; + } + } + } +} + +//===----------------------------------------------------------------------===// +// getAllTransformations. +//===----------------------------------------------------------------------===// + +static void traverseAST(MigrationPass &pass) { + MigrationContext MigrateCtx(pass); + + if (pass.isGCMigration()) { + MigrateCtx.addTraverser(new GCCollectableCallsTraverser); + MigrateCtx.addTraverser(new GCAttrsTraverser()); + } + MigrateCtx.addTraverser(new PropertyRewriteTraverser()); + MigrateCtx.addTraverser(new BlockObjCVariableTraverser()); + MigrateCtx.addTraverser(new ProtectedScopeTraverser()); + + MigrateCtx.traverse(pass.Ctx.getTranslationUnitDecl()); +} + +static void independentTransforms(MigrationPass &pass) { + rewriteAutoreleasePool(pass); + removeRetainReleaseDeallocFinalize(pass); + rewriteUnusedInitDelegate(pass); + removeZeroOutPropsInDeallocFinalize(pass); + makeAssignARCSafe(pass); + rewriteUnbridgedCasts(pass); + checkAPIUses(pass); + traverseAST(pass); +} + +std::vector<TransformFn> arcmt::getAllTransformations( + LangOptions::GCMode OrigGCMode, + bool NoFinalizeRemoval) { + std::vector<TransformFn> transforms; + + if (OrigGCMode == LangOptions::GCOnly && NoFinalizeRemoval) + transforms.push_back(GCRewriteFinalize); + transforms.push_back(independentTransforms); + // This depends on previous transformations removing various expressions. + transforms.push_back(removeEmptyStatementsAndDeallocFinalize); + + return transforms; +} diff --git a/contrib/llvm/tools/clang/lib/ARCMigrate/Transforms.h b/contrib/llvm/tools/clang/lib/ARCMigrate/Transforms.h new file mode 100644 index 0000000..7e3dd34 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/ARCMigrate/Transforms.h @@ -0,0 +1,225 @@ +//===-- Transforms.h - Transformations to ARC mode --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_ARCMIGRATE_TRANSFORMS_H +#define LLVM_CLANG_LIB_ARCMIGRATE_TRANSFORMS_H + +#include "clang/AST/ParentMap.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/Support/SaveAndRestore.h" + +namespace clang { + class Decl; + class Stmt; + class BlockDecl; + class ObjCMethodDecl; + class FunctionDecl; + +namespace arcmt { + class MigrationPass; + +namespace trans { + + class MigrationContext; + +//===----------------------------------------------------------------------===// +// Transformations. +//===----------------------------------------------------------------------===// + +void rewriteAutoreleasePool(MigrationPass &pass); +void rewriteUnbridgedCasts(MigrationPass &pass); +void makeAssignARCSafe(MigrationPass &pass); +void removeRetainReleaseDeallocFinalize(MigrationPass &pass); +void removeZeroOutPropsInDeallocFinalize(MigrationPass &pass); +void rewriteUnusedInitDelegate(MigrationPass &pass); +void checkAPIUses(MigrationPass &pass); + +void removeEmptyStatementsAndDeallocFinalize(MigrationPass &pass); + +class BodyContext { + MigrationContext &MigrateCtx; + ParentMap PMap; + Stmt *TopStmt; + +public: + BodyContext(MigrationContext &MigrateCtx, Stmt *S) + : MigrateCtx(MigrateCtx), PMap(S), TopStmt(S) {} + + MigrationContext &getMigrationContext() { return MigrateCtx; } + ParentMap &getParentMap() { return PMap; } + Stmt *getTopStmt() { return TopStmt; } +}; + +class ObjCImplementationContext { + MigrationContext &MigrateCtx; + ObjCImplementationDecl *ImpD; + +public: + ObjCImplementationContext(MigrationContext &MigrateCtx, + ObjCImplementationDecl *D) + : MigrateCtx(MigrateCtx), ImpD(D) {} + + MigrationContext &getMigrationContext() { return MigrateCtx; } + ObjCImplementationDecl *getImplementationDecl() { return ImpD; } +}; + +class ASTTraverser { +public: + virtual ~ASTTraverser(); + virtual void traverseTU(MigrationContext &MigrateCtx) { } + virtual void traverseBody(BodyContext &BodyCtx) { } + virtual void traverseObjCImplementation(ObjCImplementationContext &ImplCtx) {} +}; + +class MigrationContext { + std::vector<ASTTraverser *> Traversers; + +public: + MigrationPass &Pass; + + struct GCAttrOccurrence { + enum AttrKind { Weak, Strong } Kind; + SourceLocation Loc; + QualType ModifiedType; + Decl *Dcl; + /// \brief true if the attribute is owned, e.g. it is in a body and not just + /// in an interface. + bool FullyMigratable; + }; + std::vector<GCAttrOccurrence> GCAttrs; + llvm::DenseSet<unsigned> AttrSet; + llvm::DenseSet<unsigned> RemovedAttrSet; + + /// \brief Set of raw '@' locations for 'assign' properties group that contain + /// GC __weak. + llvm::DenseSet<unsigned> AtPropsWeak; + + explicit MigrationContext(MigrationPass &pass) : Pass(pass) {} + ~MigrationContext(); + + typedef std::vector<ASTTraverser *>::iterator traverser_iterator; + traverser_iterator traversers_begin() { return Traversers.begin(); } + traverser_iterator traversers_end() { return Traversers.end(); } + + void addTraverser(ASTTraverser *traverser) { + Traversers.push_back(traverser); + } + + bool isGCOwnedNonObjC(QualType T); + bool removePropertyAttribute(StringRef fromAttr, SourceLocation atLoc) { + return rewritePropertyAttribute(fromAttr, StringRef(), atLoc); + } + bool rewritePropertyAttribute(StringRef fromAttr, StringRef toAttr, + SourceLocation atLoc); + bool addPropertyAttribute(StringRef attr, SourceLocation atLoc); + + void traverse(TranslationUnitDecl *TU); + + void dumpGCAttrs(); +}; + +class PropertyRewriteTraverser : public ASTTraverser { +public: + void traverseObjCImplementation(ObjCImplementationContext &ImplCtx) override; +}; + +class BlockObjCVariableTraverser : public ASTTraverser { +public: + void traverseBody(BodyContext &BodyCtx) override; +}; + +class ProtectedScopeTraverser : public ASTTraverser { +public: + void traverseBody(BodyContext &BodyCtx) override; +}; + +// GC transformations + +class GCAttrsTraverser : public ASTTraverser { +public: + void traverseTU(MigrationContext &MigrateCtx) override; +}; + +class GCCollectableCallsTraverser : public ASTTraverser { +public: + void traverseBody(BodyContext &BodyCtx) override; +}; + +//===----------------------------------------------------------------------===// +// Helpers. +//===----------------------------------------------------------------------===// + +/// \brief Determine whether we can add weak to the given type. +bool canApplyWeak(ASTContext &Ctx, QualType type, + bool AllowOnUnknownClass = false); + +bool isPlusOneAssign(const BinaryOperator *E); +bool isPlusOne(const Expr *E); + +/// \brief 'Loc' is the end of a statement range. This returns the location +/// immediately after the semicolon following the statement. +/// If no semicolon is found or the location is inside a macro, the returned +/// source location will be invalid. +SourceLocation findLocationAfterSemi(SourceLocation loc, ASTContext &Ctx, + bool IsDecl = false); + +/// \brief 'Loc' is the end of a statement range. This returns the location +/// of the semicolon following the statement. +/// If no semicolon is found or the location is inside a macro, the returned +/// source location will be invalid. +SourceLocation findSemiAfterLocation(SourceLocation loc, ASTContext &Ctx, + bool IsDecl = false); + +bool hasSideEffects(Expr *E, ASTContext &Ctx); +bool isGlobalVar(Expr *E); +/// \brief Returns "nil" or "0" if 'nil' macro is not actually defined. +StringRef getNilString(MigrationPass &Pass); + +template <typename BODY_TRANS> +class BodyTransform : public RecursiveASTVisitor<BodyTransform<BODY_TRANS> > { + MigrationPass &Pass; + Decl *ParentD; + + typedef RecursiveASTVisitor<BodyTransform<BODY_TRANS> > base; +public: + BodyTransform(MigrationPass &pass) : Pass(pass), ParentD(nullptr) { } + + bool TraverseStmt(Stmt *rootS) { + if (rootS) + BODY_TRANS(Pass).transformBody(rootS, ParentD); + return true; + } + + bool TraverseObjCMethodDecl(ObjCMethodDecl *D) { + SaveAndRestore<Decl *> SetParent(ParentD, D); + return base::TraverseObjCMethodDecl(D); + } +}; + +typedef llvm::DenseSet<Expr *> ExprSet; + +void clearRefsIn(Stmt *S, ExprSet &refs); +template <typename iterator> +void clearRefsIn(iterator begin, iterator end, ExprSet &refs) { + for (; begin != end; ++begin) + clearRefsIn(*begin, refs); +} + +void collectRefs(ValueDecl *D, Stmt *S, ExprSet &refs); + +void collectRemovables(Stmt *S, ExprSet &exprs); + +} // end namespace trans + +} // end namespace arcmt + +} // end namespace clang + +#endif |