From: John McCall Date: Wed, 15 Jun 2011 23:25:17 +0000 (+0000) Subject: The ARC Migration Tool. All the credit goes to Argyrios and Fariborz X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=8f0e8d22960d56f8390f4971e2c0f2f0a0884602;p=clang The ARC Migration Tool. All the credit goes to Argyrios and Fariborz for this. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@133104 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/clang.xcodeproj/project.pbxproj b/clang.xcodeproj/project.pbxproj index 1612d122f5..fda6d6f0fa 100644 --- a/clang.xcodeproj/project.pbxproj +++ b/clang.xcodeproj/project.pbxproj @@ -373,6 +373,12 @@ 90FD6D90103C3D80005F5B73 /* TypeXML.def */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = TypeXML.def; path = clang/Frontend/TypeXML.def; sourceTree = ""; }; 90FD6D91103C3D80005F5B73 /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Utils.h; path = clang/Frontend/Utils.h; sourceTree = ""; }; 90FD6DB5103D977E005F5B73 /* index-test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "index-test.cpp"; path = "tools/index-test/index-test.cpp"; sourceTree = ""; }; + BB20603B131EDDBF003C3343 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + BB20603C131EDDBF003C3343 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + BB206041131EDDDA003C3343 /* ARRMT.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARRMT.h; sourceTree = ""; }; + BB206043131EDE03003C3343 /* arrmt-test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "arrmt-test.cpp"; sourceTree = ""; }; + BB206044131EDE03003C3343 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + BB206045131EDE03003C3343 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; BB5C372812A5057500259F53 /* DumpXML.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DumpXML.cpp; path = /Volumes/Data/llvm/tools/clang/lib/AST/DumpXML.cpp; sourceTree = ""; }; BBA5AB141309C2FA000B38F1 /* AdjustedReturnValueChecker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AdjustedReturnValueChecker.cpp; sourceTree = ""; }; BBA5AB151309C2FA000B38F1 /* AnalyzerStatsChecker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AnalyzerStatsChecker.cpp; sourceTree = ""; }; @@ -777,6 +783,7 @@ 08FB7795FE84155DC02AAC07 /* Libraries */ = { isa = PBXGroup; children = ( + BB20603A131EDDBF003C3343 /* ARRMigrate */, BBA5AB121309C2FA000B38F1 /* StaticAnalyzer */, 57EB5660121B034300ECA335 /* Serialization */, BFE2F67911DA95590007EDC0 /* Rewrite */, @@ -1105,6 +1112,38 @@ name = "index-test"; sourceTree = ""; }; + BB20603A131EDDBF003C3343 /* ARRMigrate */ = { + isa = PBXGroup; + children = ( + BB20603B131EDDBF003C3343 /* CMakeLists.txt */, + BB20603C131EDDBF003C3343 /* Makefile */, + BDDF60E91337BF40009F1764 /* Transforms.cpp */, + ); + name = ARRMigrate; + path = lib/ARRMigrate; + sourceTree = ""; + }; + BB206040131EDDDA003C3343 /* ARRMigrate */ = { + isa = PBXGroup; + children = ( + BB206041131EDDDA003C3343 /* ARRMT.h */, + ); + name = ARRMigrate; + path = clang/ARRMigrate; + sourceTree = ""; + }; + BB206042131EDE03003C3343 /* arrmt-test */ = { + isa = PBXGroup; + children = ( + BD8A47E7133D32660066FE40 /* ARRMT.cpp */, + BB206043131EDE03003C3343 /* arrmt-test.cpp */, + BB206044131EDE03003C3343 /* CMakeLists.txt */, + BB206045131EDE03003C3343 /* Makefile */, + ); + name = "arrmt-test"; + path = "tools/arrmt-test"; + sourceTree = ""; + }; BBA5AB121309C2FA000B38F1 /* StaticAnalyzer */ = { isa = PBXGroup; children = ( @@ -1575,6 +1614,7 @@ DED7D72E0A524295003AD0FB /* include */ = { isa = PBXGroup; children = ( + BB206040131EDDDA003C3343 /* ARRMigrate */, DED7D7300A524295003AD0FB /* Basic */, DED7D7390A524295003AD0FB /* Lex */, DE1F21F20A7D84E800FBF588 /* Parse */, @@ -1724,6 +1764,7 @@ DEDFE61F0F7B3AE10035BD10 /* Tools */ = { isa = PBXGroup; children = ( + BB206042131EDE03003C3343 /* arrmt-test */, 90F9EFA8104ABDC400D09A15 /* c-index-test */, 9012911E104812DA0083456D /* CIndex */, 90FD6DB4103D9763005F5B73 /* index-test */, diff --git a/include/clang/ARCMigrate/ARCMT.h b/include/clang/ARCMigrate/ARCMT.h new file mode 100644 index 0000000000..013f46b7d7 --- /dev/null +++ b/include/clang/ARCMigrate/ARCMT.h @@ -0,0 +1,83 @@ +//===-- ARCMT.h - ARC Migration Rewriter ------------------------*- 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_ARCMIGRATE_ARCMT_H +#define LLVM_CLANG_ARCMIGRATE_ARCMT_H + +#include "clang/ARCMigrate/FileRemapper.h" +#include "clang/Frontend/CompilerInvocation.h" + +namespace clang { + class ASTContext; + class DiagnosticClient; + +namespace arcmt { + class MigrationPass; + +/// \brief Creates an AST with the provided CompilerInvocation but with these +/// changes: +/// -if a PCH/PTH is set, the original header is used instead +/// -Automatic Reference Counting mode is enabled +/// +/// It then checks the AST and produces errors/warning for ARC migration issues +/// that the user needs to handle manually. +/// +/// \returns false if no error is produced, true otherwise. +bool checkForManualIssues(CompilerInvocation &CI, + llvm::StringRef Filename, InputKind Kind, + DiagnosticClient *DiagClient); + +/// \brief Works similar to checkForManualIssues but instead of checking, it +/// applies automatic modifications to source files to conform to ARC. +/// +/// \returns false if no error is produced, true otherwise. +bool applyTransformations(CompilerInvocation &origCI, + llvm::StringRef Filename, InputKind Kind, + DiagnosticClient *DiagClient); + +/// \brief Like applyTransformations but no source file is modified, compilation +/// happens using in-memory buffers. +bool applyTransformationsInMemory(CompilerInvocation &origCI, + llvm::StringRef Filename, InputKind Kind, + DiagnosticClient *DiagClient); + +typedef void (*TransformFn)(MigrationPass &pass); + +std::vector getAllTransformations(); + +class MigrationProcess { + CompilerInvocation OrigCI; + DiagnosticClient *DiagClient; + FileRemapper Remapper; + +public: + MigrationProcess(const CompilerInvocation &CI, DiagnosticClient *diagClient) + : OrigCI(CI), DiagClient(diagClient) { } + + class RewriteListener { + public: + virtual ~RewriteListener(); + + virtual void start(ASTContext &Ctx) { } + virtual void finish() { } + + virtual void insert(SourceLocation loc, llvm::StringRef text) { } + virtual void remove(CharSourceRange range) { } + }; + + bool applyTransform(TransformFn trans, RewriteListener *listener = 0); + + FileRemapper &getRemapper() { return Remapper; } +}; + +} // end namespace arcmt + +} // end namespace clang + +#endif diff --git a/include/clang/ARCMigrate/FileRemapper.h b/include/clang/ARCMigrate/FileRemapper.h new file mode 100644 index 0000000000..809f6a5f71 --- /dev/null +++ b/include/clang/ARCMigrate/FileRemapper.h @@ -0,0 +1,76 @@ +//===-- FileRemapper.h - File Remapping Helper ------------------*- 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_ARCMIGRATE_FILEREMAPPER_H +#define LLVM_CLANG_ARCMIGRATE_FILEREMAPPER_H + +#include "llvm/ADT/OwningPtr.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" + +namespace llvm { + class MemoryBuffer; +} + +namespace clang { + class FileManager; + class FileEntry; + class Diagnostic; + class CompilerInvocation; + +namespace arcmt { + +class FileRemapper { + // FIXME: Reuse the same FileManager for multiple ASTContexts. + llvm::OwningPtr FileMgr; + + typedef llvm::PointerUnion Target; + typedef llvm::DenseMap MappingsTy; + MappingsTy FromToMappings; + + llvm::DenseMap ToFromMappings; + +public: + FileRemapper(); + ~FileRemapper(); + + bool initFromDisk(llvm::StringRef outputDir, Diagnostic &Diag, + bool ignoreIfFilesChanged); + bool flushToDisk(llvm::StringRef outputDir, Diagnostic &Diag); + + bool overwriteOriginal(Diagnostic &Diag, + llvm::StringRef outputDir = llvm::StringRef()); + + void remap(llvm::StringRef filePath, llvm::MemoryBuffer *memBuf); + void remap(llvm::StringRef filePath, llvm::StringRef newPath); + + void applyMappings(CompilerInvocation &CI) const; + + void transferMappingsAndClear(CompilerInvocation &CI); + + void clear(llvm::StringRef outputDir = llvm::StringRef()); + +private: + void remap(const FileEntry *file, llvm::MemoryBuffer *memBuf); + void remap(const FileEntry *file, const FileEntry *newfile); + + const FileEntry *getOriginalFile(llvm::StringRef filePath); + void resetTarget(Target &targ); + + bool report(const std::string &err, Diagnostic &Diag); + + std::string getRemapInfoFile(llvm::StringRef outputDir); +}; + +} // end namespace arcmt + +} // end namespace clang + +#endif diff --git a/lib/ARCMigrate/ARCMT.cpp b/lib/ARCMigrate/ARCMT.cpp new file mode 100644 index 0000000000..bb66da6a71 --- /dev/null +++ b/lib/ARCMigrate/ARCMT.cpp @@ -0,0 +1,484 @@ +//===--- 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/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/Utils.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/Rewrite/Rewriter.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "clang/Basic/DiagnosticCategories.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/ADT/Triple.h" + +using namespace clang; +using namespace arcmt; +using llvm::StringRef; + +bool CapturedDiagList::clearDiagnostic(llvm::ArrayRef 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++; + while (I != List.end() && I->getLevel() == Diagnostic::Note) + ++I; + // Clear the diagnostic and any notes following it. + List.erase(eraseS, I); + continue; + } + + ++I; + } + + return cleared; +} + +bool CapturedDiagList::hasDiagnostic(llvm::ArrayRef IDs, + SourceRange range) { + if (range.isInvalid()) + return false; + + ListTy::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(Diagnostic &Diags) { + for (ListTy::iterator I = List.begin(), E = List.end(); I != E; ++I) + Diags.Report(*I); +} + +namespace { + +class CaptureDiagnosticClient : public DiagnosticClient { + Diagnostic &Diags; + CapturedDiagList &CapturedDiags; +public: + CaptureDiagnosticClient(Diagnostic &diags, + CapturedDiagList &capturedDiags) + : Diags(diags), CapturedDiags(capturedDiags) { } + + virtual void HandleDiagnostic(Diagnostic::Level level, + const DiagnosticInfo &Info) { + if (arcmt::isARCDiagnostic(Info.getID(), Diags) || + level >= Diagnostic::Error || level == Diagnostic::Note) { + CapturedDiags.push_back(StoredDiagnostic(level, Info)); + return; + } + + // Non-ARC warnings are ignored. + Diags.setLastDiagnosticIgnored(); + } +}; + +} // end anonymous namespace + +CompilerInvocation *createInvocationForMigration(CompilerInvocation &origCI) { + llvm::OwningPtr CInvok; + CInvok.reset(new CompilerInvocation(origCI)); + CInvok->getPreprocessorOpts().ImplicitPCHInclude = std::string(); + CInvok->getPreprocessorOpts().ImplicitPTHInclude = std::string(); + std::string define = getARCMTMacroName(); + define += '='; + CInvok->getPreprocessorOpts().addMacroDef(define); + CInvok->getLangOpts().ObjCAutoRefCount = true; + CInvok->getDiagnosticOpts().ErrorLimit = 0; + + // FIXME: Hackety hack! Try to find out if there is an ARC runtime. + bool hasARCRuntime = false; + llvm::SmallVector args; + args.push_back("-x"); + args.push_back("objective-c"); + args.push_back("-fobjc-arc"); + + llvm::Triple triple(CInvok->getTargetOpts().Triple); + if (triple.getOS() == llvm::Triple::IOS || + triple.getOS() == llvm::Triple::MacOSX) { + unsigned Major, Minor, Micro; + triple.getOSVersion(Major, Minor, Micro); + llvm::SmallString<100> flag; + if (triple.getOS() == llvm::Triple::IOS) + flag += "-miphoneos-version-min="; + else + flag += "-mmacosx-version-min="; + llvm::raw_svector_ostream(flag) << Major << '.' << Minor << '.' << Micro; + args.push_back(flag.str()); + } + + args.push_back(origCI.getFrontendOpts().Inputs[0].second.c_str()); + // Also push all defines to deal with the iOS simulator hack. + for (unsigned i = 0, e = origCI.getPreprocessorOpts().Macros.size(); + i != e; ++i) { + std::string &def = origCI.getPreprocessorOpts().Macros[i].first; + bool isUndef = origCI.getPreprocessorOpts().Macros[i].second; + if (!isUndef) { + std::string newdef = "-D"; + newdef += def; + args.push_back(newdef); + } + } + + llvm::SmallVector cargs; + for (unsigned i = 0, e = args.size(); i != e; ++i) + cargs.push_back(args[i].c_str()); + + llvm::OwningPtr checkCI; + checkCI.reset(clang::createInvocationFromCommandLine(cargs)); + if (checkCI) + hasARCRuntime = !checkCI->getLangOpts().ObjCNoAutoRefCountRuntime; + + CInvok->getLangOpts().ObjCNoAutoRefCountRuntime = !hasARCRuntime; + + return CInvok.take(); +} + +//===----------------------------------------------------------------------===// +// checkForManualIssues. +//===----------------------------------------------------------------------===// + +bool arcmt::checkForManualIssues(CompilerInvocation &origCI, + llvm::StringRef Filename, InputKind Kind, + DiagnosticClient *DiagClient) { + if (!origCI.getLangOpts().ObjC1) + return false; + + std::vector transforms = arcmt::getAllTransformations(); + assert(!transforms.empty()); + + llvm::OwningPtr CInvok; + CInvok.reset(createInvocationForMigration(origCI)); + CInvok->getFrontendOpts().Inputs.clear(); + CInvok->getFrontendOpts().Inputs.push_back(std::make_pair(Kind, Filename)); + + CapturedDiagList capturedDiags; + + assert(DiagClient); + llvm::IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + llvm::IntrusiveRefCntPtr Diags( + new Diagnostic(DiagID, DiagClient, /*ShouldOwnClient=*/false)); + + // Filter of all diagnostics. + CaptureDiagnosticClient errRec(*Diags, capturedDiags); + Diags->setClient(&errRec, /*ShouldOwnClient=*/false); + + llvm::OwningPtr Unit( + ASTUnit::LoadFromCompilerInvocationAction(CInvok.take(), Diags)); + if (!Unit) + return true; + + // Don't filter diagnostics anymore. + Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); + + ASTContext &Ctx = Unit->getASTContext(); + + if (Diags->hasFatalErrorOccurred()) { + Diags->Reset(); + DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); + capturedDiags.reportDiagnostics(*Diags); + DiagClient->EndSourceFile(); + return true; + } + + // After parsing of source files ended, we want to reuse the + // diagnostics objects to emit further diagnostics. + // We call BeginSourceFile because DiagnosticClient requires that + // diagnostics with source range information are emitted only in between + // BeginSourceFile() and EndSourceFile(). + DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); + + // No macros will be added since we are just checking and we won't modify + // source code. + std::vector ARCMTMacroLocs; + + TransformActions testAct(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); + MigrationPass pass(Ctx, Unit->getSema(), testAct, ARCMTMacroLocs); + + for (unsigned i=0, e = transforms.size(); i != e; ++i) + transforms[i](pass); + + capturedDiags.reportDiagnostics(*Diags); + + DiagClient->EndSourceFile(); + + return Diags->getClient()->getNumErrors() > 0; +} + +//===----------------------------------------------------------------------===// +// applyTransformations. +//===----------------------------------------------------------------------===// + +bool arcmt::applyTransformations(CompilerInvocation &origCI, + llvm::StringRef Filename, InputKind Kind, + DiagnosticClient *DiagClient) { + if (!origCI.getLangOpts().ObjC1) + return false; + + // Make sure checking is successful first. + CompilerInvocation CInvokForCheck(origCI); + if (arcmt::checkForManualIssues(CInvokForCheck, Filename, Kind, DiagClient)) + return true; + + CompilerInvocation CInvok(origCI); + CInvok.getFrontendOpts().Inputs.clear(); + CInvok.getFrontendOpts().Inputs.push_back(std::make_pair(Kind, Filename)); + + MigrationProcess migration(CInvok, DiagClient); + + std::vector transforms = arcmt::getAllTransformations(); + assert(!transforms.empty()); + + for (unsigned i=0, e = transforms.size(); i != e; ++i) { + bool err = migration.applyTransform(transforms[i]); + if (err) return true; + } + + origCI.getLangOpts().ObjCAutoRefCount = true; + + llvm::IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + llvm::IntrusiveRefCntPtr Diags( + new Diagnostic(DiagID, DiagClient, /*ShouldOwnClient=*/false)); + return migration.getRemapper().overwriteOriginal(*Diags); +} + +//===----------------------------------------------------------------------===// +// applyTransformationsInMemory. +//===----------------------------------------------------------------------===// + +bool arcmt::applyTransformationsInMemory(CompilerInvocation &origCI, + llvm::StringRef Filename, InputKind Kind, + DiagnosticClient *DiagClient) { + if (!origCI.getLangOpts().ObjC1) + return false; + + // Make sure checking is successful first. + CompilerInvocation CInvokForCheck(origCI); + if (arcmt::checkForManualIssues(CInvokForCheck, Filename, Kind, DiagClient)) + return true; + + CompilerInvocation CInvok(origCI); + CInvok.getFrontendOpts().Inputs.clear(); + CInvok.getFrontendOpts().Inputs.push_back(std::make_pair(Kind, Filename)); + + MigrationProcess migration(CInvok, DiagClient); + + std::vector transforms = arcmt::getAllTransformations(); + assert(!transforms.empty()); + + for (unsigned i=0, e = transforms.size(); i != e; ++i) { + bool err = migration.applyTransform(transforms[i]); + if (err) return true; + } + + origCI.getLangOpts().ObjCAutoRefCount = true; + migration.getRemapper().transferMappingsAndClear(origCI); + + return false; +} + +//===----------------------------------------------------------------------===// +// CollectTransformActions. +//===----------------------------------------------------------------------===// + +namespace { + +class ARCMTMacroTrackerPPCallbacks : public PPCallbacks { + std::vector &ARCMTMacroLocs; + +public: + ARCMTMacroTrackerPPCallbacks(std::vector &ARCMTMacroLocs) + : ARCMTMacroLocs(ARCMTMacroLocs) { } + + virtual void MacroExpands(const Token &MacroNameTok, const MacroInfo *MI) { + if (MacroNameTok.getIdentifierInfo()->getName() == getARCMTMacroName()) + ARCMTMacroLocs.push_back(MacroNameTok.getLocation()); + } +}; + +class ARCMTMacroTrackerAction : public ASTFrontendAction { + std::vector &ARCMTMacroLocs; + +public: + ARCMTMacroTrackerAction(std::vector &ARCMTMacroLocs) + : ARCMTMacroLocs(ARCMTMacroLocs) { } + + virtual ASTConsumer *CreateASTConsumer(CompilerInstance &CI, + llvm::StringRef InFile) { + CI.getPreprocessor().addPPCallbacks( + new ARCMTMacroTrackerPPCallbacks(ARCMTMacroLocs)); + return new ASTConsumer(); + } +}; + +class RewritesApplicator : public TransformActions::RewriteReceiver { + Rewriter &rewriter; + ASTContext &Ctx; + MigrationProcess::RewriteListener *Listener; + +public: + RewritesApplicator(Rewriter &rewriter, ASTContext &ctx, + MigrationProcess::RewriteListener *listener) + : rewriter(rewriter), Ctx(ctx), Listener(listener) { + if (Listener) + Listener->start(ctx); + } + ~RewritesApplicator() { + if (Listener) + Listener->finish(); + } + + virtual void insert(SourceLocation loc, llvm::StringRef text) { + bool err = rewriter.InsertText(loc, text, /*InsertAfter=*/true, + /*indentNewLines=*/true); + if (!err && Listener) + Listener->insert(loc, text); + } + + virtual void remove(CharSourceRange range) { + Rewriter::RewriteOptions removeOpts; + removeOpts.IncludeInsertsAtBeginOfRange = false; + removeOpts.IncludeInsertsAtEndOfRange = false; + removeOpts.RemoveLineIfEmpty = true; + + bool err = rewriter.RemoveText(range, removeOpts); + if (!err && Listener) + Listener->remove(range); + } + + virtual void increaseIndentation(CharSourceRange range, + SourceLocation parentIndent) { + rewriter.IncreaseIndentation(range, parentIndent); + } +}; + +} // end anonymous namespace. + +/// \brief Anchor for VTable. +MigrationProcess::RewriteListener::~RewriteListener() { } + +bool MigrationProcess::applyTransform(TransformFn trans, + RewriteListener *listener) { + llvm::OwningPtr CInvok; + CInvok.reset(createInvocationForMigration(OrigCI)); + CInvok->getDiagnosticOpts().IgnoreWarnings = true; + + Remapper.applyMappings(*CInvok); + + CapturedDiagList capturedDiags; + std::vector ARCMTMacroLocs; + + assert(DiagClient); + llvm::IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + llvm::IntrusiveRefCntPtr Diags( + new Diagnostic(DiagID, DiagClient, /*ShouldOwnClient=*/false)); + + // Filter of all diagnostics. + CaptureDiagnosticClient errRec(*Diags, capturedDiags); + Diags->setClient(&errRec, /*ShouldOwnClient=*/false); + + llvm::OwningPtr ASTAction; + ASTAction.reset(new ARCMTMacroTrackerAction(ARCMTMacroLocs)); + + llvm::OwningPtr Unit( + ASTUnit::LoadFromCompilerInvocationAction(CInvok.take(), Diags, + ASTAction.get())); + if (!Unit) + return true; + Unit->setOwnsRemappedFileBuffers(false); // FileRemapper manages that. + + // Don't filter diagnostics anymore. + Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); + + ASTContext &Ctx = Unit->getASTContext(); + + if (Diags->hasFatalErrorOccurred()) { + Diags->Reset(); + DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); + capturedDiags.reportDiagnostics(*Diags); + DiagClient->EndSourceFile(); + return true; + } + + // After parsing of source files ended, we want to reuse the + // diagnostics objects to emit further diagnostics. + // We call BeginSourceFile because DiagnosticClient requires that + // diagnostics with source range information are emitted only in between + // BeginSourceFile() and EndSourceFile(). + DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); + + Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOptions()); + TransformActions TA(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); + MigrationPass pass(Ctx, Unit->getSema(), TA, ARCMTMacroLocs); + + trans(pass); + + { + RewritesApplicator applicator(rewriter, Ctx, listener); + TA.applyRewrites(applicator); + } + + DiagClient->EndSourceFile(); + + 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"; + llvm::SmallString<512> newText; + llvm::raw_svector_ostream vecOS(newText); + buf.write(vecOS); + vecOS.flush(); + llvm::MemoryBuffer *memBuf = llvm::MemoryBuffer::getMemBufferCopy( + llvm::StringRef(newText.data(), newText.size()), newFname); + llvm::SmallString<64> filePath(file->getName()); + Unit->getFileManager().FixupRelativePath(filePath); + Remapper.remap(filePath.str(), memBuf); + } + + return false; +} + +//===----------------------------------------------------------------------===// +// isARCDiagnostic. +//===----------------------------------------------------------------------===// + +bool arcmt::isARCDiagnostic(unsigned diagID, Diagnostic &Diag) { + return Diag.getDiagnosticIDs()->getCategoryNumberForDiag(diagID) == + diag::DiagCat_Automatic_Reference_Counting_Issue; +} diff --git a/lib/ARCMigrate/CMakeLists.txt b/lib/ARCMigrate/CMakeLists.txt new file mode 100644 index 0000000000..3645a22356 --- /dev/null +++ b/lib/ARCMigrate/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_USED_LIBS clangBasic clangAST clangParse clangFrontend clangRewrite) + +add_clang_library(clangARCMigrate + ARCMT.cpp + FileRemapper.cpp + TransformActions.cpp + Transforms.cpp + ) + +add_dependencies(clangARCMigrate + ClangAttrClasses + ClangAttrList + ClangDeclNodes + ClangStmtNodes) diff --git a/lib/ARCMigrate/FileRemapper.cpp b/lib/ARCMigrate/FileRemapper.cpp new file mode 100644 index 0000000000..ae5d3a3c93 --- /dev/null +++ b/lib/ARCMigrate/FileRemapper.cpp @@ -0,0 +1,290 @@ +//===--- 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/Frontend/CompilerInvocation.h" +#include "clang/Basic/FileManager.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace clang; +using namespace arcmt; + +FileRemapper::FileRemapper() { + FileMgr.reset(new FileManager(FileSystemOptions())); +} + +FileRemapper::~FileRemapper() { + clear(); +} + +void FileRemapper::clear(llvm::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); + bool existed; + llvm::sys::fs::remove(infoFile, existed); + } +} + +std::string FileRemapper::getRemapInfoFile(llvm::StringRef outputDir) { + assert(!outputDir.empty()); + llvm::sys::Path dir(outputDir); + llvm::sys::Path infoFile = dir; + infoFile.appendComponent("remap"); + return infoFile.str(); +} + +bool FileRemapper::initFromDisk(llvm::StringRef outputDir, Diagnostic &Diag, + bool ignoreIfFilesChanged) { + assert(FromToMappings.empty() && + "initFromDisk should be called before any remap calls"); + std::string infoFile = getRemapInfoFile(outputDir); + bool fileExists = false; + llvm::sys::fs::exists(infoFile, fileExists); + if (!fileExists) + return false; + + std::vector > pairs; + + std::ifstream fin(infoFile.c_str()); + if (!fin.good()) + return report(std::string("Error opening file: ") + infoFile, Diag); + + while (true) { + std::string fromFilename, toFilename; + uint64_t timeModified; + + fin >> fromFilename >> timeModified >> toFilename; + if (fin.eof()) + break; + if (!fin.good()) { + if (ignoreIfFilesChanged) + return false; + return report(std::string("Error in format of file: ") + infoFile, Diag); + } + + const FileEntry *origFE = FileMgr->getFile(fromFilename); + if (!origFE) { + if (ignoreIfFilesChanged) + continue; + return report(std::string("File does not exist: ") + fromFilename, Diag); + } + const FileEntry *newFE = FileMgr->getFile(toFilename); + if (!newFE) { + if (ignoreIfFilesChanged) + continue; + return report(std::string("File does not exist: ") + toFilename, Diag); + } + + if ((uint64_t)origFE->getModificationTime() != timeModified) { + if (ignoreIfFilesChanged) + continue; + return report(std::string("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(llvm::StringRef outputDir, Diagnostic &Diag) { + using namespace llvm::sys; + + bool existed; + if (fs::create_directory(outputDir, existed) != llvm::errc::success) + return report(std::string("Could not create directory: ") + outputDir.str(), + Diag); + + std::string errMsg; + std::string infoFile = getRemapInfoFile(outputDir); + llvm::raw_fd_ostream infoOut(infoFile.c_str(), errMsg, + llvm::raw_fd_ostream::F_Binary); + if (!errMsg.empty() || infoOut.has_error()) + return report(errMsg, Diag); + + for (MappingsTy::iterator + I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { + + const FileEntry *origFE = I->first; + infoOut << origFE->getName() << '\n'; + infoOut << (uint64_t)origFE->getModificationTime() << '\n'; + + if (const FileEntry *FE = I->second.dyn_cast()) { + infoOut << FE->getName() << '\n'; + } else { + + llvm::SmallString<64> tempPath; + tempPath = path::filename(origFE->getName()); + tempPath += "-%%%%%%%%"; + tempPath += path::extension(origFE->getName()); + int fd; + if (fs::unique_file(tempPath.str(), fd, tempPath) != llvm::errc::success) + return report(std::string("Could not create file: ") + tempPath.c_str(), + Diag); + + llvm::raw_fd_ostream newOut(fd, /*shouldClose=*/true); + llvm::MemoryBuffer *mem = I->second.get(); + 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(Diagnostic &Diag, + llvm::StringRef outputDir) { + using namespace llvm::sys; + + for (MappingsTy::iterator + I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { + const FileEntry *origFE = I->first; + if (const FileEntry *newFE = I->second.dyn_cast()) { + if (fs::copy_file(newFE->getName(), origFE->getName(), + fs::copy_option::overwrite_if_exists) != llvm::errc::success) { + std::string err = "Could not copy file '"; + llvm::raw_string_ostream os(err); + os << "Could not copy file '" << newFE->getName() << "' to file '" + << origFE->getName() << "'"; + os.flush(); + return report(err, Diag); + } + } else { + + bool fileExists = false; + fs::exists(origFE->getName(), fileExists); + if (!fileExists) + return report(std::string("File does not exist: ") + origFE->getName(), + Diag); + + std::string errMsg; + llvm::raw_fd_ostream Out(origFE->getName(), errMsg, + llvm::raw_fd_ostream::F_Binary); + if (!errMsg.empty() || Out.has_error()) + return report(errMsg, Diag); + + llvm::MemoryBuffer *mem = I->second.get(); + Out.write(mem->getBufferStart(), mem->getBufferSize()); + Out.close(); + } + } + + clear(outputDir); + return false; +} + +void FileRemapper::applyMappings(CompilerInvocation &CI) const { + PreprocessorOptions &PPOpts = CI.getPreprocessorOpts(); + for (MappingsTy::const_iterator + I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { + if (const FileEntry *FE = I->second.dyn_cast()) { + PPOpts.addRemappedFile(I->first->getName(), FE->getName()); + } else { + llvm::MemoryBuffer *mem = I->second.get(); + PPOpts.addRemappedFile(I->first->getName(), mem); + } + } + + PPOpts.RetainRemappedFileBuffers = true; +} + +void FileRemapper::transferMappingsAndClear(CompilerInvocation &CI) { + PreprocessorOptions &PPOpts = CI.getPreprocessorOpts(); + for (MappingsTy::iterator + I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { + if (const FileEntry *FE = I->second.dyn_cast()) { + PPOpts.addRemappedFile(I->first->getName(), FE->getName()); + } else { + llvm::MemoryBuffer *mem = I->second.get(); + PPOpts.addRemappedFile(I->first->getName(), mem); + } + I->second = Target(); + } + + PPOpts.RetainRemappedFileBuffers = false; + clear(); +} + +void FileRemapper::remap(llvm::StringRef filePath, llvm::MemoryBuffer *memBuf) { + remap(getOriginalFile(filePath), memBuf); +} + +void FileRemapper::remap(llvm::StringRef filePath, llvm::StringRef newPath) { + const FileEntry *file = getOriginalFile(filePath); + const FileEntry *newfile = FileMgr->getFile(newPath); + remap(file, newfile); +} + +void FileRemapper::remap(const FileEntry *file, llvm::MemoryBuffer *memBuf) { + assert(file); + Target &targ = FromToMappings[file]; + resetTarget(targ); + targ = memBuf; +} + +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(llvm::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::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()) { + delete oldmem; + } else { + const FileEntry *toFE = targ.get(); + llvm::DenseMap::iterator + I = ToFromMappings.find(toFE); + if (I != ToFromMappings.end()) + ToFromMappings.erase(I); + } +} + +bool FileRemapper::report(const std::string &err, Diagnostic &Diag) { + unsigned ID = Diag.getDiagnosticIDs()->getCustomDiagID(DiagnosticIDs::Error, + err); + Diag.Report(ID); + return true; +} diff --git a/lib/ARCMigrate/Internals.h b/lib/ARCMigrate/Internals.h new file mode 100644 index 0000000000..fdc0aad176 --- /dev/null +++ b/lib/ARCMigrate/Internals.h @@ -0,0 +1,146 @@ +//===-- 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 "llvm/ADT/ArrayRef.h" + +namespace clang { + class Sema; + class Stmt; + +namespace arcmt { + +class CapturedDiagList { + typedef std::list ListTy; + ListTy List; + +public: + void push_back(const StoredDiagnostic &diag) { List.push_back(diag); } + + bool clearDiagnostic(llvm::ArrayRef IDs, SourceRange range); + bool hasDiagnostic(llvm::ArrayRef IDs, SourceRange range); + + void reportDiagnostics(Diagnostic &diags); +}; + +class TransformActions { + Diagnostic &Diags; + CapturedDiagList &CapturedDiags; + void *Impl; // TransformActionsImpl. + +public: + TransformActions(Diagnostic &diag, CapturedDiagList &capturedDiags, + ASTContext &ctx, Preprocessor &PP); + ~TransformActions(); + + void startTransaction(); + bool commitTransaction(); + void abortTransaction(); + + void insert(SourceLocation loc, llvm::StringRef text); + void insertAfterToken(SourceLocation loc, llvm::StringRef text); + void remove(SourceRange range); + void removeStmt(Stmt *S); + void replace(SourceRange range, llvm::StringRef text); + void replace(SourceRange range, SourceRange replacementRange); + void replaceStmt(Stmt *S, llvm::StringRef text); + void replaceText(SourceLocation loc, llvm::StringRef text, + llvm::StringRef replacementText); + void increaseIndentation(SourceRange range, + SourceLocation parentIndent); + + bool clearDiagnostic(llvm::ArrayRef IDs, SourceRange range); + bool clearAllDiagnostics(SourceRange range) { + return clearDiagnostic(llvm::ArrayRef(), 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); + } + + void reportError(llvm::StringRef error, SourceLocation loc, + SourceRange range = SourceRange()); + void reportNote(llvm::StringRef note, SourceLocation loc, + SourceRange range = SourceRange()); + + class RewriteReceiver { + public: + virtual ~RewriteReceiver(); + + virtual void insert(SourceLocation loc, llvm::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; + Sema &SemaRef; + TransformActions &TA; + std::vector &ARCMTMacroLocs; + + MigrationPass(ASTContext &Ctx, Sema &sema, TransformActions &TA, + std::vector &ARCMTMacroLocs) + : Ctx(Ctx), SemaRef(sema), TA(TA), ARCMTMacroLocs(ARCMTMacroLocs) { } +}; + +bool isARCDiagnostic(unsigned diagID, Diagnostic &Diag); + +static inline llvm::StringRef getARCMTMacroName() { + return "__IMPL_ARCMT_REMOVED_EXPR__"; +} + +} // end namespace arcmt + +} // end namespace clang + +#endif diff --git a/lib/ARCMigrate/Makefile b/lib/ARCMigrate/Makefile new file mode 100644 index 0000000000..5232c5e5af --- /dev/null +++ b/lib/ARCMigrate/Makefile @@ -0,0 +1,18 @@ +##===- clang/lib/ARCMigrate/Makefile --------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +# +# This implements code transformation to ARC mode. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../.. +LIBRARYNAME := clangARCMigrate + +include $(CLANG_LEVEL)/Makefile + diff --git a/lib/ARCMigrate/TransformActions.cpp b/lib/ARCMigrate/TransformActions.cpp new file mode 100644 index 0000000000..cd8a80e86b --- /dev/null +++ b/lib/ARCMigrate/TransformActions.cpp @@ -0,0 +1,699 @@ +//===--- 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/Expr.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/DenseSet.h" +#include + +using namespace clang; +using namespace arcmt; +using llvm::StringRef; + +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; + llvm::StringRef Text1, Text2; + Stmt *S; + llvm::SmallVector DiagIDs; + }; + + std::vector 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.getInstantiationLoc(beginLoc), srcMgr); + End = FullSourceLoc(getLocForEndOfToken(endLoc, srcMgr, PP), srcMgr); + } else { + Begin = FullSourceLoc(srcMgr.getInstantiationLoc(beginLoc), srcMgr); + End = FullSourceLoc(srcMgr.getInstantiationLoc(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 llvm::SmallVector TextsVec; + typedef std::map + InsertsMap; + InsertsMap Inserts; + /// \brief A list of ranges to remove. They are always sorted and they never + /// intersect with each other. + std::list Removals; + + llvm::DenseSet StmtRemovals; + + std::vector > IndentationRanges; + + /// \brief Keeps text passed to transformation methods. + llvm::StringMap UniqueText; + +public: + TransformActionsImpl(CapturedDiagList &capturedDiags, + ASTContext &ctx, Preprocessor &PP) + : CapturedDiags(capturedDiags), Ctx(ctx), PP(PP), IsInTransaction(false) { } + + void startTransaction(); + bool commitTransaction(); + void abortTransaction(); + + bool isInTransaction() const { return IsInTransaction; } + + void insert(SourceLocation loc, llvm::StringRef text); + void insertAfterToken(SourceLocation loc, llvm::StringRef text); + void remove(SourceRange range); + void removeStmt(Stmt *S); + void replace(SourceRange range, llvm::StringRef text); + void replace(SourceRange range, SourceRange replacementRange); + void replaceStmt(Stmt *S, llvm::StringRef text); + void replaceText(SourceLocation loc, llvm::StringRef text, + llvm::StringRef replacementText); + void increaseIndentation(SourceRange range, + SourceLocation parentIndent); + + bool clearDiagnostic(llvm::ArrayRef 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, llvm::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, llvm::StringRef text, + llvm::StringRef replacementText); + void commitIncreaseIndentation(SourceRange range,SourceLocation parentIndent); + void commitClearDiagnostic(llvm::ArrayRef 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 instantiation 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; + 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(llvm::ArrayRef 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.getInstantiationLoc(loc))) + return false; + + if (loc.isFileID()) + return true; + return SM.isAtStartOfMacroInstantiation(loc); +} + +bool TransformActionsImpl::canInsertAfterToken(SourceLocation loc) { + if (loc.isInvalid()) + return false; + + SourceManager &SM = Ctx.getSourceManager(); + if (SM.isInSystemHeader(SM.getInstantiationLoc(loc))) + return false; + + if (loc.isFileID()) + return true; + return SM.isAtEndOfMacroInstantiation(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.getInstantiationLoc(loc); + + // Break down the source location. + std::pair locInfo = SM.getDecomposedLoc(loc); + + // Try to load the file buffer. + bool invalidTemp = false; + llvm::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(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.getInstantiationLoc(loc); + // canReplaceText already checked if loc points at text. + SourceLocation afterText = loc.getFileLocWithOffset(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.getInstantiationLoc(parentIndent))); +} + +void TransformActionsImpl::commitClearDiagnostic(llvm::ArrayRef IDs, + SourceRange range) { + CapturedDiags.clearDiagnostic(IDs, range); +} + +void TransformActionsImpl::addInsertion(SourceLocation loc, StringRef text) { + SourceManager &SM = Ctx.getSourceManager(); + loc = SM.getInstantiationLoc(loc); + for (std::list::reverse_iterator + I = Removals.rbegin(), E = Removals.rend(); I != E; ++I) { + 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::iterator I = Removals.end(); + while (I != Removals.begin()) { + std::list::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 >::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::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) { + llvm::StringMapEntry &entry = UniqueText.GetOrCreateValue(text); + return entry.getKey(); +} + +/// \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 instantiation is skipped. +SourceLocation TransformActionsImpl::getLocForEndOfToken(SourceLocation loc, + SourceManager &SM, + Preprocessor &PP) { + if (loc.isMacroID()) + loc = SM.getInstantiationRange(loc).second; + return PP.getLocForEndOfToken(loc); +} + +TransformActions::RewriteReceiver::~RewriteReceiver() { } + +TransformActions::TransformActions(Diagnostic &diag, + CapturedDiagList &capturedDiags, + ASTContext &ctx, Preprocessor &PP) + : Diags(diag), CapturedDiags(capturedDiags) { + Impl = new TransformActionsImpl(capturedDiags, ctx, PP); +} + +TransformActions::~TransformActions() { + delete static_cast(Impl); +} + +void TransformActions::startTransaction() { + static_cast(Impl)->startTransaction(); +} + +bool TransformActions::commitTransaction() { + return static_cast(Impl)->commitTransaction(); +} + +void TransformActions::abortTransaction() { + static_cast(Impl)->abortTransaction(); +} + + +void TransformActions::insert(SourceLocation loc, llvm::StringRef text) { + static_cast(Impl)->insert(loc, text); +} + +void TransformActions::insertAfterToken(SourceLocation loc, + llvm::StringRef text) { + static_cast(Impl)->insertAfterToken(loc, text); +} + +void TransformActions::remove(SourceRange range) { + static_cast(Impl)->remove(range); +} + +void TransformActions::removeStmt(Stmt *S) { + static_cast(Impl)->removeStmt(S); +} + +void TransformActions::replace(SourceRange range, llvm::StringRef text) { + static_cast(Impl)->replace(range, text); +} + +void TransformActions::replace(SourceRange range, + SourceRange replacementRange) { + static_cast(Impl)->replace(range, replacementRange); +} + +void TransformActions::replaceStmt(Stmt *S, llvm::StringRef text) { + static_cast(Impl)->replaceStmt(S, text); +} + +void TransformActions::replaceText(SourceLocation loc, llvm::StringRef text, + llvm::StringRef replacementText) { + static_cast(Impl)->replaceText(loc, text, + replacementText); +} + +void TransformActions::increaseIndentation(SourceRange range, + SourceLocation parentIndent) { + static_cast(Impl)->increaseIndentation(range, + parentIndent); +} + +bool TransformActions::clearDiagnostic(llvm::ArrayRef IDs, + SourceRange range) { + return static_cast(Impl)->clearDiagnostic(IDs, range); +} + +void TransformActions::applyRewrites(RewriteReceiver &receiver) { + static_cast(Impl)->applyRewrites(receiver); +} + +void TransformActions::reportError(llvm::StringRef error, SourceLocation loc, + SourceRange range) { + assert(!static_cast(Impl)->isInTransaction() && + "Errors should be emitted out of a transaction"); + // FIXME: Use a custom category name to distinguish rewriter errors. + std::string rewriteErr = "[rewriter] "; + rewriteErr += error; + unsigned diagID + = Diags.getDiagnosticIDs()->getCustomDiagID(DiagnosticIDs::Error, + rewriteErr); + Diags.Report(loc, diagID) << range; +} + +void TransformActions::reportNote(llvm::StringRef note, SourceLocation loc, + SourceRange range) { + assert(!static_cast(Impl)->isInTransaction() && + "Errors should be emitted out of a transaction"); + // FIXME: Use a custom category name to distinguish rewriter errors. + std::string rewriteNote = "[rewriter] "; + rewriteNote += note; + unsigned diagID + = Diags.getDiagnosticIDs()->getCustomDiagID(DiagnosticIDs::Note, + rewriteNote); + Diags.Report(loc, diagID) << range; +} diff --git a/lib/ARCMigrate/Transforms.cpp b/lib/ARCMigrate/Transforms.cpp new file mode 100644 index 0000000000..ab3bc1dbe7 --- /dev/null +++ b/lib/ARCMigrate/Transforms.cpp @@ -0,0 +1,2094 @@ +//===--- Tranforms.cpp - Tranformations 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: +//===----------------------------------------------------------------------===// +// +// castNonObjCToObjC: +// +// A cast of non-objc pointer to an objc one is checked. If the non-objc pointer +// is from a file-level variable, objc_unretainedObject function is used to +// convert it. +// +// NSString *str = (NSString *)kUTTypePlainText; +// str = b ? kUTTypeRTF : kUTTypePlainText; +// ----> +// NSString *str = objc_unretainedObject(kUTTypePlainText); +// str = objc_unretainedObject(b ? kUTTypeRTF : kUTTypePlainText); +// +// For a C pointer to ObjC, objc_unretainedPointer is used. +// +// void *vp = str; // NSString* +// ----> +// void *vp = (void*)objc_unretainedPointer(str); +// +//===----------------------------------------------------------------------===// +// +// rewriteAllocCopyWithZone: +// +// Calls to +allocWithZone/-copyWithZone/-mutableCopyWithZone are changed to +// +alloc/-copy/-mutableCopy if we can safely remove the given parameter. +// +// Foo *foo1 = [[Foo allocWithZone:[self zone]] init]; +// ----> +// Foo *foo1 = [[Foo alloc] init]; +// +//===----------------------------------------------------------------------===// +// +// 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. +// +//===----------------------------------------------------------------------===// +// +// makeAssignARCSafe: +// +// Add '__strong' where appropriate. +// +// for (id x in collection) { +// x = 0; +// } +// ----> +// for (__strong id x in collection) { +// x = 0; +// } +// +//===----------------------------------------------------------------------===// +// +// removeRetainReleaseDealloc: +// +// Removes retain/release/autorelease/dealloc messages. +// +// return [[foo retain] autorelease]; +// ----> +// return foo; +// +//===----------------------------------------------------------------------===// +// +// removeEmptyStatements: +// +// Removes empty statements that are leftovers from previous transformations. +// e.g for +// +// [x retain]; +// +// removeRetainReleaseDealloc will leave an empty ";" that removeEmptyStatements +// will remove. +// +//===----------------------------------------------------------------------===// +// +// changeIvarsOfAssignProperties: +// +// If a property is synthesized with 'assign' attribute and the user didn't +// set a lifetime attribute, change the property to 'weak' or add +// __unsafe_unretained if the ARC runtime is not available. +// +// @interface Foo : NSObject { +// NSObject *x; +// } +// @property (assign) id x; +// @end +// ----> +// @interface Foo : NSObject { +// NSObject *__weak x; +// } +// @property (weak) id x; +// @end +// +//===----------------------------------------------------------------------===// +// +// rewriteUnusedDelegateInit: +// +// Rewrites an unused result of calling a delegate initialization, to assigning +// the result to self. +// e.g +// [self init]; +// ----> +// self = [self init]; +// +//===----------------------------------------------------------------------===// +// +// rewriteBlockObjCVariable: +// +// Adding __block to an obj-c variable could be either because the 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]; }); +// +//===----------------------------------------------------------------------===// +// +// removeZeroOutIvarsInDealloc: +// +// Removes zero'ing out "strong" @synthesized properties in a -dealloc method. +// +//===----------------------------------------------------------------------===// + +#include "Internals.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/AST/ParentMap.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Lex/Lexer.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/ADT/DenseSet.h" +#include + +using namespace clang; +using namespace arcmt; +using llvm::StringRef; + +//===----------------------------------------------------------------------===// +// Transformations. +//===----------------------------------------------------------------------===// + +namespace { + +class RemovablesCollector : public RecursiveASTVisitor { + llvm::DenseSet &Removables; + +public: + RemovablesCollector(llvm::DenseSet &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 (CompoundStmt::body_iterator + I = S->body_begin(), E = S->body_end(); I != E; ++I) + 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; + + if (LabelStmt *Label = dyn_cast(S)) + return mark(Label->getSubStmt()); + if (ImplicitCastExpr *CE = dyn_cast(S)) + return mark(CE->getSubExpr()); + if (ExprWithCleanups *EWC = dyn_cast(S)) + return mark(EWC->getSubExpr()); + if (Expr *E = dyn_cast(S)) + Removables.insert(E); + } +}; + +} // end anonymous namespace. + +static bool HasSideEffects(Expr *E, ASTContext &Ctx) { + if (!E || !E->HasSideEffects(Ctx)) + return false; + + E = E->IgnoreParenCasts(); + ObjCMessageExpr *ME = dyn_cast(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; +} + +static void removeDeallocMethod(MigrationPass &pass) { + ASTContext &Ctx = pass.Ctx; + TransformActions &TA = pass.TA; + DeclContext *DC = Ctx.getTranslationUnitDecl(); + ObjCMethodDecl *DeallocMethodDecl = 0; + IdentifierInfo *II = &Ctx.Idents.get("dealloc"); + + for (DeclContext::decl_iterator I = DC->decls_begin(), E = DC->decls_end(); + I != E; ++I) { + Decl *D = *I; + if (ObjCImplementationDecl *IMD = + dyn_cast(D)) { + DeallocMethodDecl = 0; + for (ObjCImplementationDecl::instmeth_iterator I = + IMD->instmeth_begin(), E = IMD->instmeth_end(); + I != E; ++I) { + ObjCMethodDecl *OMD = *I; + if (OMD->isInstanceMethod() && + OMD->getSelector() == Ctx.Selectors.getSelector(0, &II)) { + DeallocMethodDecl = OMD; + break; + } + } + if (DeallocMethodDecl && + DeallocMethodDecl->getCompoundBody()->body_empty()) { + Transaction Trans(TA); + TA.remove(DeallocMethodDecl->getSourceRange()); + } + } + } +} + +namespace { + +class ReferenceClear : public RecursiveASTVisitor { + llvm::DenseSet &Refs; +public: + ReferenceClear(llvm::DenseSet &refs) : Refs(refs) { } + bool VisitDeclRefExpr(DeclRefExpr *E) { Refs.erase(E); return true; } + bool VisitBlockDeclRefExpr(BlockDeclRefExpr *E) { Refs.erase(E); return true; } + void clearRefsIn(Stmt *S) { TraverseStmt(S); } + template + void clearRefsIn(iterator begin, iterator end) { + for (; begin != end; ++begin) + TraverseStmt(*begin); + } +}; + +class ReferenceCollector : public RecursiveASTVisitor { + ValueDecl *Dcl; + llvm::DenseSet &Refs; + +public: + ReferenceCollector(llvm::DenseSet &refs) + : Dcl(0), Refs(refs) { } + + void lookFor(ValueDecl *D, Stmt *S) { + Dcl = D; + TraverseStmt(S); + } + + bool VisitDeclRefExpr(DeclRefExpr *E) { + if (E->getDecl() == Dcl) + Refs.insert(E); + return true; + } + + bool VisitBlockDeclRefExpr(BlockDeclRefExpr *E) { + if (E->getDecl() == Dcl) + Refs.insert(E); + return true; + } +}; + +class ReleaseCollector : public RecursiveASTVisitor { + Decl *Dcl; + llvm::SmallVectorImpl &Releases; + +public: + ReleaseCollector(Decl *D, llvm::SmallVectorImpl &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(instance)) { + if (DE->getDecl() == Dcl) + Releases.push_back(E); + } + return true; + } +}; + +template +class BodyTransform : public RecursiveASTVisitor > { + MigrationPass &Pass; + +public: + BodyTransform(MigrationPass &pass) : Pass(pass) { } + + void handleBody(Decl *D) { + Stmt *body = D->getBody(); + if (body) { + BODY_TRANS(D, Pass).transformBody(body); + } + } + + bool TraverseBlockDecl(BlockDecl *D) { + handleBody(D); + return true; + } + bool TraverseObjCMethodDecl(ObjCMethodDecl *D) { + if (D->isThisDeclarationADefinition()) + handleBody(D); + return true; + } + bool TraverseFunctionDecl(FunctionDecl *D) { + if (D->isThisDeclarationADefinition()) + handleBody(D); + return true; + } +}; + +} // anonymous namespace + +//===----------------------------------------------------------------------===// +// makeAssignARCSafe +//===----------------------------------------------------------------------===// + +namespace { + +class ARCAssignChecker : public RecursiveASTVisitor { + MigrationPass &Pass; + llvm::DenseSet ModifiedVars; + +public: + ARCAssignChecker(MigrationPass &pass) : Pass(pass) { } + + bool VisitBinaryOperator(BinaryOperator *Exp) { + Expr *E = Exp->getLHS(); + SourceLocation OrigLoc = E->getExprLoc(); + SourceLocation Loc = OrigLoc; + DeclRefExpr *declRef = dyn_cast(E->IgnoreParenCasts()); + if (declRef && isa(declRef->getDecl())) { + ASTContext &Ctx = Pass.Ctx; + Expr::isModifiableLvalueResult IsLV = E->isModifiableLvalue(Ctx, &Loc); + if (IsLV != Expr::MLV_ConstQualified) + return true; + VarDecl *var = cast(declRef->getDecl()); + if (var->getType().getLocalQualifiers().getObjCLifetime() + == Qualifiers::OCL_ExplicitNone && + (var->getTypeSourceInfo() && + !var->getTypeSourceInfo()->getType().isConstQualified())) { + 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 + +static void makeAssignARCSafe(MigrationPass &pass) { + ARCAssignChecker assignCheck(pass); + assignCheck.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// castNonObjCToObjC +//===----------------------------------------------------------------------===// + +namespace { + +class NonObjCToObjCCaster : public RecursiveASTVisitor { + MigrationPass &Pass; +public: + NonObjCToObjCCaster(MigrationPass &pass) : Pass(pass) { } + + bool VisitCastExpr(CastExpr *E) { + if (E->getCastKind() != CK_AnyPointerToObjCPointerCast + && E->getCastKind() != CK_BitCast) + return true; + + QualType castType = E->getType(); + Expr *castExpr = E->getSubExpr(); + QualType castExprType = castExpr->getType(); + + if (castType->isObjCObjectPointerType() && + castExprType->isObjCObjectPointerType()) + return true; + if (!castType->isObjCObjectPointerType() && + !castExprType->isObjCObjectPointerType()) + 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->isObjCObjectPointerType()) + 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(inner)) { + if (FunctionDecl *FD = callE->getDirectCallee()) { + if (FD->getAttr()) { + castToObjCObject(E, /*retained=*/true); + return; + } + if (FD->getAttr()) { + 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) { + castToObjCObject(E, /*retained=*/true); + return; + } + + if (fname.find("Get") != StringRef::npos) { + castToObjCObject(E, /*retained=*/false); + return; + } + } + } + } + } + + void castToObjCObject(CastExpr *E, bool retained) { + 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())) + return; + + Transaction Trans(TA); + TA.clearDiagnostic(diag::err_arc_mismatched_cast, + diag::err_arc_cast_requires_bridge, + E->getLocStart()); + if (CStyleCastExpr *CCE = dyn_cast(E)) { + TA.insertAfterToken(CCE->getLParenLoc(), retained ? "__bridge_transfer " + : "__bridge "); + } else { + SourceLocation insertLoc = E->getSubExpr()->getLocStart(); + llvm::SmallString<128> newCast; + newCast += '('; + newCast += retained ? "__bridge_transfer " : "__bridge "; + newCast += E->getType().getAsString(Pass.Ctx.PrintingPolicy); + newCast += ')'; + + if (isa(E->getSubExpr())) { + TA.insert(insertLoc, newCast.str()); + } else { + newCast += '('; + TA.insert(insertLoc, newCast.str()); + TA.insertAfterToken(E->getLocEnd(), ")"); + } + } + } + + void transformObjCToNonObjCCast(CastExpr *E) { + // FIXME: Handle these casts. + return; +#if 0 + 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())) + return; + + Transaction Trans(TA); + TA.clearDiagnostic(diag::err_arc_mismatched_cast, + diag::err_arc_cast_requires_bridge, + E->getLocStart()); + + assert(!E->getType()->isObjCObjectPointerType()); + + bool shouldCast = !isa(E) && + !E->getType()->getPointeeType().isConstQualified(); + SourceLocation loc = E->getSubExpr()->getLocStart(); + if (isa(E->getSubExpr())) { + TA.insert(loc, shouldCast ? "(void*)objc_unretainedPointer" + : "objc_unretainedPointer"); + } else { + TA.insert(loc, shouldCast ? "(void*)objc_unretainedPointer(" + : "objc_unretainedPointer("); + TA.insertAfterToken(E->getLocEnd(), ")"); + } +#endif + } + + static bool isGlobalVar(Expr *E) { + E = E->IgnoreParenCasts(); + if (DeclRefExpr *DRE = dyn_cast(E)) + return DRE->getDecl()->getDeclContext()->isFileContext(); + if (ConditionalOperator *condOp = dyn_cast(E)) + return isGlobalVar(condOp->getTrueExpr()) && + isGlobalVar(condOp->getFalseExpr()); + + return false; + } +}; + +} // end anonymous namespace + +static void castNonObjCToObjC(MigrationPass &pass) { + NonObjCToObjCCaster trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// rewriteAllocCopyWithZone +//===----------------------------------------------------------------------===// + +namespace { + +class AllocCopyWithZoneRewriter : + public RecursiveASTVisitor { + Decl *Dcl; + Stmt *Body; + MigrationPass &Pass; + + Selector allocWithZoneSel; + Selector copyWithZoneSel; + Selector mutableCopyWithZoneSel; + Selector zoneSel; + IdentifierInfo *NSZoneII; + + std::vector NSZoneVars; + std::vector Removals; + +public: + AllocCopyWithZoneRewriter(Decl *D, MigrationPass &pass) + : Dcl(D), Body(0), Pass(pass) { + SelectorTable &sels = pass.Ctx.Selectors; + IdentifierTable &ids = pass.Ctx.Idents; + allocWithZoneSel = sels.getUnarySelector(&ids.get("allocWithZone")); + copyWithZoneSel = sels.getUnarySelector(&ids.get("copyWithZone")); + mutableCopyWithZoneSel = sels.getUnarySelector( + &ids.get("mutableCopyWithZone")); + zoneSel = sels.getNullarySelector(&ids.get("zone")); + NSZoneII = &ids.get("_NSZone"); + } + + void transformBody(Stmt *body) { + Body = body; + // Don't change allocWithZone/copyWithZone messages inside + // custom implementations of such methods, it can lead to infinite loops. + if (ObjCMethodDecl *MD = dyn_cast(Dcl)) { + Selector sel = MD->getSelector(); + if (sel == allocWithZoneSel || + sel == copyWithZoneSel || + sel == mutableCopyWithZoneSel || + sel == zoneSel) + return; + } + + TraverseStmt(body); + } + + ~AllocCopyWithZoneRewriter() { + for (std::vector::reverse_iterator + I = NSZoneVars.rbegin(), E = NSZoneVars.rend(); I != E; ++I) { + DeclStmt *DS = *I; + DeclGroupRef group = DS->getDeclGroup(); + std::vector varRemovals = Removals; + + bool areAllVarsUnused = true; + for (std::reverse_iterator + DI(group.end()), DE(group.begin()); DI != DE; ++DI) { + VarDecl *VD = cast(*DI); + if (isNSZoneVarUsed(VD, varRemovals)) { + areAllVarsUnused = false; + break; + } + varRemovals.push_back(VD->getInit()); + } + + if (areAllVarsUnused) { + Transaction Trans(Pass.TA); + clearUnavailableDiags(DS); + Pass.TA.removeStmt(DS); + Removals.swap(varRemovals); + } + } + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *E) { + if (!isAllocCopyWithZoneCall(E)) + return true; + Expr *arg = E->getArg(0); + if (paramToAllocWithZoneHasSideEffects(arg)) + return true; + + Pass.TA.startTransaction(); + + clearUnavailableDiags(arg); + Pass.TA.clearDiagnostic(diag::err_unavailable_message, + E->getReceiverRange().getBegin()); + + Pass.TA.remove(SourceRange(E->getSelectorLoc(), arg->getLocEnd())); + StringRef rewrite; + if (E->getSelector() == allocWithZoneSel) + rewrite = "alloc"; + else if (E->getSelector() == copyWithZoneSel) + rewrite = "copy"; + else { + assert(E->getSelector() == mutableCopyWithZoneSel); + rewrite = "mutableCopy"; + } + Pass.TA.insert(E->getSelectorLoc(), rewrite); + + bool failed = Pass.TA.commitTransaction(); + if (!failed) + Removals.push_back(arg); + + return true; + } + + bool VisitDeclStmt(DeclStmt *DS) { + DeclGroupRef group = DS->getDeclGroup(); + if (group.begin() == group.end()) + return true; + for (DeclGroupRef::iterator + DI = group.begin(), DE = group.end(); DI != DE; ++DI) + if (!isRemovableNSZoneVar(*DI)) + return true; + + NSZoneVars.push_back(DS); + return true; + } + +private: + bool isRemovableNSZoneVar(Decl *D) { + if (VarDecl *VD = dyn_cast(D)) { + if (isNSZone(VD->getType())) + return !paramToAllocWithZoneHasSideEffects(VD->getInit()); + } + return false; + } + + bool isNSZone(RecordDecl *RD) { + return RD && RD->getIdentifier() == NSZoneII; + } + + bool isNSZone(QualType Ty) { + QualType pointee = Ty->getPointeeType(); + if (pointee.isNull()) + return false; + if (const RecordType *recT = pointee->getAsStructureType()) + return isNSZone(recT->getDecl()); + return false; + } + + bool isNSZoneVarUsed(VarDecl *D, std::vector &removals) { + llvm::DenseSet refs; + + ReferenceCollector refColl(refs); + refColl.lookFor(D, Body); + + ReferenceClear refClear(refs); + refClear.clearRefsIn(removals.begin(), removals.end()); + + return !refs.empty(); + } + + bool isAllocCopyWithZoneCall(ObjCMessageExpr *E) { + if (E->getNumArgs() == 1 && + E->getSelector() == allocWithZoneSel && + (E->isClassMessage() || + Pass.TA.hasDiagnostic(diag::err_unavailable_message, + E->getReceiverRange().getBegin()))) + return true; + + return E->isInstanceMessage() && + E->getNumArgs() == 1 && + (E->getSelector() == copyWithZoneSel || + E->getSelector() == mutableCopyWithZoneSel); + } + + bool isZoneCall(ObjCMessageExpr *E) { + return E->isInstanceMessage() && + E->getNumArgs() == 0 && + E->getSelector() == zoneSel; + } + + bool paramToAllocWithZoneHasSideEffects(Expr *E) { + if (!HasSideEffects(E, Pass.Ctx)) + return false; + E = E->IgnoreParenCasts(); + ObjCMessageExpr *ME = dyn_cast(E); + if (!ME) + return true; + if (!isZoneCall(ME)) + return true; + return HasSideEffects(ME->getInstanceReceiver(), Pass.Ctx); + } + + void clearUnavailableDiags(Stmt *S) { + if (S) + Pass.TA.clearDiagnostic(diag::err_unavailable, + diag::err_unavailable_message, + S->getSourceRange()); + } +}; + +} // end anonymous namespace + +static void rewriteAllocCopyWithZone(MigrationPass &pass) { + BodyTransform trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// rewriteAutoreleasePool +//===----------------------------------------------------------------------===// + +/// \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. +static SourceLocation findLocationAfterSemi(ASTContext &Ctx, + SourceLocation loc) { + SourceManager &SM = Ctx.getSourceManager(); + if (loc.isMacroID()) { + if (!SM.isAtEndOfMacroInstantiation(loc)) + return SourceLocation(); + loc = SM.getInstantiationRange(loc).second; + } + loc = Lexer::getLocForEndOfToken(loc, /*Offset=*/0, SM, Ctx.getLangOptions()); + + // Break down the source location. + std::pair locInfo = SM.getDecomposedLoc(loc); + + // Try to load the file buffer. + bool invalidTemp = false; + llvm::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.getLangOptions(), + file.begin(), tokenBegin, file.end()); + Token tok; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::semi)) + return SourceLocation(); + + return tok.getLocation().getFileLocWithOffset(1); +} + +namespace { + +class AutoreleasePoolRewriter + : public RecursiveASTVisitor { +public: + AutoreleasePoolRewriter(Decl *D, MigrationPass &pass) + : Dcl(D), Body(0), Pass(pass) { + PoolII = &pass.Ctx.Idents.get("NSAutoreleasePool"); + DrainSel = pass.Ctx.Selectors.getNullarySelector( + &pass.Ctx.Idents.get("drain")); + } + + void transformBody(Stmt *body) { + Body = body; + TraverseStmt(body); + } + + ~AutoreleasePoolRewriter() { + llvm::SmallVector VarsToHandle; + + for (std::map::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. + + ReferenceClear refClear(info.Refs); + refClear.clearRefsIn(info.Dcl); + for (llvm::SmallVectorImpl::iterator + scpI = info.Scopes.begin(), + scpE = info.Scopes.end(); scpI != scpE; ++scpI) { + PoolScope &scope = *scpI; + refClear.clearRefsIn(*scope.Begin); + refClear.clearRefsIn(*scope.End); + refClear.clearRefsIn(scope.Releases.begin(), scope.Releases.end()); + } + + // 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 (llvm::SmallVectorImpl::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(Pass.Ctx, + (*retI)->getLocEnd()); + 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 (llvm::SmallVectorImpl::iterator + scpI = info.Scopes.begin(), + scpE = info.Scopes.end(); scpI != scpE; ++scpI) { + PoolScope &scope = *scpI; + for (llvm::SmallVectorImpl::iterator + relI = scope.Releases.begin(), + relE = scope.Releases.end(); relI != relE; ++relI) { + clearUnavailableDiags(*relI); + Pass.TA.removeStmt(*relI); + } + } + } + } + + bool VisitCompoundStmt(CompoundStmt *S) { + llvm::SmallVector 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(child)) { + if (DclS->isSingleDecl()) { + if (VarDecl *VD = dyn_cast(DclS->getSingleDecl())) { + if (isNSAutoreleasePool(VD->getType())) { + PoolVarInfo &info = PoolVars[VD]; + info.Dcl = DclS; + ReferenceCollector refColl(info.Refs); + refColl.lookFor(VD, S); + // 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(child)) { + if (DeclRefExpr *dref = dyn_cast(bop->getLHS())) { + if (VarDecl *VD = dyn_cast(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; + llvm::SmallVector Releases; + + PoolScope() : PoolVar(0), CompoundParent(0), 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{ + 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 VisitBlockDeclRefExpr(BlockDeclRefExpr *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) { + 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(*SI)) + if ((retS->getRetValue() == 0 || + isa(retS->getRetValue()->IgnoreParenCasts())) && + findLocationAfterSemi(Pass.Ctx, retS->getLocEnd()).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(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(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(S); + if (!ME) return false; + if (ME->getReceiverKind() == ObjCMessageExpr::Instance) { + Expr *rec = getEssential(ME->getInstanceReceiver()); + if (DeclRefExpr *dref = dyn_cast(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()) + return isNSAutoreleasePool(interT->getDecl()); + return false; + } + + static Expr *getEssential(Expr *E) { + return cast(getEssential((Stmt*)E)); + } + static Stmt *getEssential(Stmt *S) { + if (ExprWithCleanups *EWC = dyn_cast(S)) + S = EWC->getSubExpr(); + if (Expr *E = dyn_cast(S)) + S = E->IgnoreParenCasts(); + return S; + } + + Decl *Dcl; + Stmt *Body; + MigrationPass &Pass; + + IdentifierInfo *PoolII; + Selector DrainSel; + + struct PoolVarInfo { + DeclStmt *Dcl; + llvm::DenseSet Refs; + llvm::SmallVector Scopes; + + PoolVarInfo() : Dcl(0) { } + }; + + std::map PoolVars; +}; + +} // anonymous namespace + +static void rewriteAutoreleasePool(MigrationPass &pass) { + BodyTransform trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// removeRetainReleaseDealloc +//===----------------------------------------------------------------------===// + +namespace { + +class RetainReleaseDeallocRemover : + public RecursiveASTVisitor { + Decl *Dcl; + Stmt *Body; + MigrationPass &Pass; + + llvm::DenseSet Removables; + llvm::OwningPtr StmtMap; + +public: + RetainReleaseDeallocRemover(Decl *D, MigrationPass &pass) + : Dcl(D), Body(0), Pass(pass) { } + + void transformBody(Stmt *body) { + Body = body; + RemovablesCollector(Removables).TraverseStmt(body); + StmtMap.reset(new ParentMap(body)); + TraverseStmt(body); + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *E) { + switch (E->getMethodFamily()) { + default: + return true; + case OMF_retain: + case OMF_release: + case OMF_autorelease: + if (E->getReceiverKind() == ObjCMessageExpr::Instance) + if (Expr *rec = E->getInstanceReceiver()) { + rec = rec->IgnoreParenImpCasts(); + if (rec->getType().getObjCLifetime() == Qualifiers::OCL_ExplicitNone){ + 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; + } + } + case OMF_dealloc: + break; + } + + switch (E->getReceiverKind()) { + default: + return true; + case ObjCMessageExpr::SuperInstance: { + Transaction Trans(Pass.TA); + Pass.TA.clearDiagnostic(diag::err_arc_illegal_explicit_message, + diag::err_unavailable, + diag::err_unavailable_message, + E->getSuperLoc()); + 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); + Pass.TA.clearDiagnostic(diag::err_arc_illegal_explicit_message, + diag::err_unavailable, + diag::err_unavailable_message, + rec->getExprLoc()); + if (!HasSideEffects(E, Pass.Ctx)) { + if (tryRemoving(E)) + return true; + } + Pass.TA.replace(E->getSourceRange(), rec->getSourceRange()); + + return true; + } + +private: + bool isRemovable(Expr *E) const { + return Removables.count(E); + } + + bool tryRemoving(Expr *E) const { + if (isRemovable(E)) { + Pass.TA.removeStmt(E); + return true; + } + + if (ParenExpr *parenE = dyn_cast_or_null(StmtMap->getParent(E))) + return tryRemoving(parenE); + + if (BinaryOperator * + bopE = dyn_cast_or_null(StmtMap->getParent(E))) { + if (bopE->getOpcode() == BO_Comma && bopE->getLHS() == E && + isRemovable(bopE)) { + Pass.TA.replace(bopE->getSourceRange(), bopE->getRHS()->getSourceRange()); + return true; + } + } + + return false; + } + +}; + +} // anonymous namespace + +static void removeRetainReleaseDealloc(MigrationPass &pass) { + BodyTransform trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// removeEmptyStatements +//===----------------------------------------------------------------------===// + +namespace { + +class EmptyStatementsRemover : + public RecursiveASTVisitor { + MigrationPass &Pass; + llvm::DenseSet MacroLocs; + +public: + EmptyStatementsRemover(MigrationPass &pass) : Pass(pass) { + for (unsigned i = 0, e = Pass.ARCMTMacroLocs.size(); i != e; ++i) + MacroLocs.insert(Pass.ARCMTMacroLocs[i].getRawEncoding()); + } + + 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 (CompoundStmt::body_iterator + I = S->body_begin(), E = S->body_end(); I != E; ++I) + check(*I); + return true; + } + + bool isMacroLoc(SourceLocation loc) { + if (loc.isInvalid()) return false; + return MacroLocs.count(loc.getRawEncoding()); + } + + ASTContext &getContext() { return Pass.Ctx; } + +private: + /// \brief Returns true if the statement became empty due to previous + /// transformations. + class EmptyChecker : public StmtVisitor { + EmptyStatementsRemover &Trans; + + public: + EmptyChecker(EmptyStatementsRemover &trans) : Trans(trans) { } + + bool VisitNullStmt(NullStmt *S) { + return Trans.isMacroLoc(S->getLeadingEmptyMacroLoc()); + } + bool VisitCompoundStmt(CompoundStmt *S) { + if (S->body_empty()) + return false; // was already empty, not because of transformations. + for (CompoundStmt::body_iterator + I = S->body_begin(), E = S->body_end(); I != E; ++I) + 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, Trans.getContext())) + return false; + if (!S->getThen() || !Visit(S->getThen())) + return false; + if (S->getElse() && !Visit(S->getElse())) + return false; + return true; + } + bool VisitWhileStmt(WhileStmt *S) { + if (S->getConditionVariable()) + return false; + Expr *condE = S->getCond(); + if (!condE) + return false; + if (HasSideEffects(condE, Trans.getContext())) + 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, Trans.getContext())) + 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, Trans.getContext())) + return false; + if (!S->getBody()) + return false; + return Visit(S->getBody()); + } + bool VisitObjCAutoreleasePoolStmt(ObjCAutoreleasePoolStmt *S) { + if (!S->getSubStmt()) + return false; + return Visit(S->getSubStmt()); + } + }; + + void check(Stmt *S) { + if (!S) return; + if (EmptyChecker(*this).Visit(S)) { + Transaction Trans(Pass.TA); + Pass.TA.removeStmt(S); + } + } +}; + +} // anonymous namespace + +static void removeEmptyStatements(MigrationPass &pass) { + EmptyStatementsRemover(pass).TraverseDecl(pass.Ctx.getTranslationUnitDecl()); + + for (unsigned i = 0, e = pass.ARCMTMacroLocs.size(); i != e; ++i) { + Transaction Trans(pass.TA); + pass.TA.remove(pass.ARCMTMacroLocs[i]); + } +} + +//===----------------------------------------------------------------------===// +// changeIvarsOfAssignProperties. +//===----------------------------------------------------------------------===// + +namespace { + +class AssignPropertiesTrans { + MigrationPass &Pass; + struct PropData { + ObjCPropertyDecl *PropD; + ObjCIvarDecl *IvarD; + bool ShouldChangeToWeak; + SourceLocation ArcPropAssignErrorLoc; + }; + + typedef llvm::SmallVector PropsTy; + typedef llvm::DenseMap PropsMapTy; + PropsMapTy PropsMap; + +public: + AssignPropertiesTrans(MigrationPass &pass) : Pass(pass) { } + + void doTransform(ObjCImplementationDecl *D) { + SourceManager &SM = Pass.Ctx.getSourceManager(); + + ObjCInterfaceDecl *IFace = D->getClassInterface(); + for (ObjCInterfaceDecl::prop_iterator + I = IFace->prop_begin(), E = IFace->prop_end(); I != E; ++I) { + ObjCPropertyDecl *propD = *I; + unsigned loc = SM.getInstantiationLoc(propD->getAtLoc()).getRawEncoding(); + PropsTy &props = PropsMap[loc]; + props.push_back(PropData()); + props.back().PropD = propD; + props.back().IvarD = 0; + props.back().ShouldChangeToWeak = false; + } + + typedef DeclContext::specific_decl_iterator + prop_impl_iterator; + for (prop_impl_iterator + I = prop_impl_iterator(D->decls_begin()), + E = prop_impl_iterator(D->decls_end()); I != E; ++I) { + VisitObjCPropertyImplDecl(*I); + } + + for (PropsMapTy::iterator + I = PropsMap.begin(), E = PropsMap.end(); I != E; ++I) { + SourceLocation atLoc = SourceLocation::getFromRawEncoding(I->first); + PropsTy &props = I->second; + if (shouldApplyWeakToAllProp(props)) { + if (changeAssignToWeak(atLoc)) { + // Couldn't add the 'weak' property attribute, + // try adding __unsafe_unretained. + applyUnsafeUnretained(props); + } else { + for (PropsTy::iterator + PI = props.begin(), PE = props.end(); PI != PE; ++PI) { + applyWeak(*PI); + } + } + } else { + // We should not add 'weak' attribute since not all properties need it. + // So just add __unsafe_unretained to the ivars. + applyUnsafeUnretained(props); + } + } + } + + bool shouldApplyWeakToAllProp(PropsTy &props) { + for (PropsTy::iterator + PI = props.begin(), PE = props.end(); PI != PE; ++PI) { + if (!PI->ShouldChangeToWeak) + return false; + } + return true; + } + + void applyWeak(PropData &prop) { + assert(!Pass.Ctx.getLangOptions().ObjCNoAutoRefCountRuntime); + + Transaction Trans(Pass.TA); + Pass.TA.insert(prop.IvarD->getLocation(), "__weak "); + Pass.TA.clearDiagnostic(diag::err_arc_assign_property_lifetime, + prop.ArcPropAssignErrorLoc); + } + + void applyUnsafeUnretained(PropsTy &props) { + for (PropsTy::iterator + PI = props.begin(), PE = props.end(); PI != PE; ++PI) { + if (PI->ShouldChangeToWeak) { + Transaction Trans(Pass.TA); + Pass.TA.insert(PI->IvarD->getLocation(), "__unsafe_unretained "); + Pass.TA.clearDiagnostic(diag::err_arc_assign_property_lifetime, + PI->ArcPropAssignErrorLoc); + } + } + } + + bool VisitObjCPropertyImplDecl(ObjCPropertyImplDecl *D) { + SourceManager &SM = Pass.Ctx.getSourceManager(); + + if (D->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) + return true; + ObjCPropertyDecl *propD = D->getPropertyDecl(); + if (!propD || propD->isInvalidDecl()) + return true; + ObjCIvarDecl *ivarD = D->getPropertyIvarDecl(); + if (!ivarD || ivarD->isInvalidDecl()) + return true; + if (!(propD->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_assign)) + return true; + if (isa(ivarD->getType().getTypePtr())) + return true; + if (ivarD->getType().getLocalQualifiers().getObjCLifetime() + != Qualifiers::OCL_Strong) + return true; + if (!Pass.TA.hasDiagnostic( + diag::err_arc_assign_property_lifetime, D->getLocation())) + return true; + + // There is a "error: existing ivar for assign property must be + // __unsafe_unretained"; fix it. + + if (Pass.Ctx.getLangOptions().ObjCNoAutoRefCountRuntime) { + // We will just add __unsafe_unretained to the ivar. + Transaction Trans(Pass.TA); + Pass.TA.insert(ivarD->getLocation(), "__unsafe_unretained "); + Pass.TA.clearDiagnostic( + diag::err_arc_assign_property_lifetime, D->getLocation()); + } else { + // Mark that we want the ivar to become weak. + unsigned loc = SM.getInstantiationLoc(propD->getAtLoc()).getRawEncoding(); + PropsTy &props = PropsMap[loc]; + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { + if (I->PropD == propD) { + I->IvarD = ivarD; + I->ShouldChangeToWeak = true; + I->ArcPropAssignErrorLoc = D->getLocation(); + } + } + } + + return true; + } + +private: + bool changeAssignToWeak(SourceLocation atLoc) { + SourceManager &SM = Pass.Ctx.getSourceManager(); + + // Break down the source location. + std::pair locInfo = SM.getDecomposedLoc(atLoc); + + // Try to load the file buffer. + bool invalidTemp = false; + llvm::StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); + if (invalidTemp) + return true; + + const char *tokenBegin = file.data() + locInfo.second; + + // Lex from the start of the given location. + Lexer lexer(SM.getLocForStartOfFile(locInfo.first), + Pass.Ctx.getLangOptions(), + file.begin(), tokenBegin, file.end()); + Token tok; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::at)) return true; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::raw_identifier)) return true; + if (llvm::StringRef(tok.getRawIdentifierData(), tok.getLength()) + != "property") + return true; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::l_paren)) return true; + + SourceLocation LParen = tok.getLocation(); + SourceLocation assignLoc; + bool isEmpty = false; + + lexer.LexFromRawLexer(tok); + if (tok.is(tok::r_paren)) { + isEmpty = true; + } else { + while (1) { + if (tok.isNot(tok::raw_identifier)) return true; + llvm::StringRef ident(tok.getRawIdentifierData(), tok.getLength()); + if (ident == "assign") + assignLoc = tok.getLocation(); + + do { + lexer.LexFromRawLexer(tok); + } while (tok.isNot(tok::comma) && tok.isNot(tok::r_paren)); + if (tok.is(tok::r_paren)) + break; + lexer.LexFromRawLexer(tok); + } + } + + Transaction Trans(Pass.TA); + if (assignLoc.isValid()) + Pass.TA.replaceText(assignLoc, "assign", "weak"); + else + Pass.TA.insertAfterToken(LParen, isEmpty ? "weak" : "weak, "); + return false; + } +}; + +class PropertiesChecker : public RecursiveASTVisitor { + MigrationPass &Pass; + +public: + PropertiesChecker(MigrationPass &pass) : Pass(pass) { } + + bool TraverseObjCImplementationDecl(ObjCImplementationDecl *D) { + AssignPropertiesTrans(Pass).doTransform(D); + return true; + } +}; + +} // anonymous namespace + +static void changeIvarsOfAssignProperties(MigrationPass &pass) { + PropertiesChecker(pass).TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// rewriteUnusedDelegateInit +//===----------------------------------------------------------------------===// + +namespace { + +class UnusedInitRewriter : public RecursiveASTVisitor { + Decl *Dcl; + Stmt *Body; + MigrationPass &Pass; + + llvm::DenseSet Removables; + +public: + UnusedInitRewriter(Decl *D, MigrationPass &pass) + : Dcl(D), Body(0), Pass(pass) { } + + void transformBody(Stmt *body) { + Body = body; + RemovablesCollector(Removables).TraverseStmt(body); + 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()); + Pass.TA.insert(ME->getExprLoc(), "self = "); + } + return true; + } + +private: + bool isRemovable(Expr *E) const { + return Removables.count(E); + } +}; + +} // anonymous namespace + +static void rewriteUnusedDelegateInit(MigrationPass &pass) { + BodyTransform trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// rewriteBlockObjCVariable +//===----------------------------------------------------------------------===// + +namespace { + +class RootBlockObjCVarRewriter : + public RecursiveASTVisitor { + MigrationPass &Pass; + llvm::DenseSet CheckedVars; + + class BlockVarChecker : public RecursiveASTVisitor { + VarDecl *Var; + + typedef RecursiveASTVisitor base; + public: + BlockVarChecker(VarDecl *var) : Var(var) { } + + bool TraverseImplicitCastExpr(ImplicitCastExpr *castE) { + if (BlockDeclRefExpr * + ref = dyn_cast(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().getLangOptions().CPlusPlus) + return true; // Binding to const C++ reference. + } + } + + return base::TraverseImplicitCastExpr(castE); + } + + bool VisitBlockDeclRefExpr(BlockDeclRefExpr *E) { + if (E->getDecl() == Var) + return false; // The reference of the variable, and not just its value, + // is needed. + return true; + } + }; + +public: + RootBlockObjCVarRewriter(MigrationPass &pass) : Pass(pass) { } + + bool VisitBlockDecl(BlockDecl *block) { + llvm::SmallVector BlockVars; + + for (BlockDecl::capture_iterator + I = block->capture_begin(), E = block->capture_end(); I != E; ++I) { + VarDecl *var = I->getVariable(); + if (I->isByRef() && + !isAlreadyChecked(var) && + var->getType()->isObjCObjectPointerType() && + isImplicitStrong(var->getType())) { + BlockVars.push_back(var); + } + } + + for (unsigned i = 0, e = BlockVars.size(); i != e; ++i) { + VarDecl *var = BlockVars[i]; + CheckedVars.insert(var); + + BlockVarChecker checker(var); + bool onlyValueOfVarIsNeeded = checker.TraverseStmt(block->getBody()); + if (onlyValueOfVarIsNeeded) { + BlocksAttr *attr = var->getAttr(); + if(!attr) + continue; + bool hasARCRuntime = !Pass.Ctx.getLangOptions().ObjCNoAutoRefCountRuntime; + SourceManager &SM = Pass.Ctx.getSourceManager(); + Transaction Trans(Pass.TA); + Pass.TA.replaceText(SM.getInstantiationLoc(attr->getLocation()), + "__block", + hasARCRuntime ? "__weak" : "__unsafe_unretained"); + } + + } + + return true; + } + +private: + bool isAlreadyChecked(VarDecl *VD) { + return CheckedVars.count(VD); + } + + bool isImplicitStrong(QualType ty) { + if (isa(ty.getTypePtr())) + return false; + return ty.getLocalQualifiers().getObjCLifetime() == Qualifiers::OCL_Strong; + } +}; + +class BlockObjCVarRewriter : public RecursiveASTVisitor { + MigrationPass &Pass; + +public: + BlockObjCVarRewriter(MigrationPass &pass) : Pass(pass) { } + + bool TraverseBlockDecl(BlockDecl *block) { + RootBlockObjCVarRewriter(Pass).TraverseDecl(block); + return true; + } +}; + +} // anonymous namespace + +static void rewriteBlockObjCVariable(MigrationPass &pass) { + BlockObjCVarRewriter trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// removeZeroOutIvarsInDealloc +//===----------------------------------------------------------------------===// + +namespace { + +class ZeroOutInDeallocRemover : + public RecursiveASTVisitor { + typedef RecursiveASTVisitor base; + + MigrationPass &Pass; + + llvm::DenseMap SynthesizedProperties; + ImplicitParamDecl *SelfD; + llvm::DenseSet Removables; + +public: + ZeroOutInDeallocRemover(MigrationPass &pass) : Pass(pass), SelfD(0) { } + + 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(receiver->IgnoreParenCasts()); + if (!refE || refE->getDecl() != SelfD) + return true; + + bool BackedBySynthesizeSetter = false; + for (llvm::DenseMap::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 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) + return true; + if (!D->hasBody()) + return true; + + ObjCImplDecl *IMD = dyn_cast(D->getDeclContext()); + if (!IMD) + return true; + + SelfD = D->getSelfDecl(); + RemovablesCollector(Removables).TraverseStmt(D->getBody()); + + // For a 'dealloc' method use, find all property implementations in + // this class implementation. + for (ObjCImplDecl::propimpl_iterator + I = IMD->propimpl_begin(), EI = IMD->propimpl_end(); I != EI; ++I) { + ObjCPropertyImplDecl *PID = *I; + 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 = 0; + 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) { + BinaryOperator *BOE = dyn_cast_or_null(E); + if (!BOE) return false; + + if (BOE->getOpcode() == BO_Comma) + return isZeroingPropIvar(BOE->getLHS()) && + isZeroingPropIvar(BOE->getRHS()); + + if (BOE->getOpcode() != BO_Assign) + return false; + + ASTContext &Ctx = Pass.Ctx; + + Expr *LHS = BOE->getLHS(); + if (ObjCIvarRefExpr *IV = dyn_cast(LHS)) { + ObjCIvarDecl *IVDecl = IV->getDecl(); + if (!IVDecl->getType()->isObjCObjectPointerType()) + return false; + bool IvarBacksPropertySynthesis = false; + for (llvm::DenseMap::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 if (ObjCPropertyRefExpr *PropRefExp = dyn_cast(LHS)) { + // TODO: Using implicit property decl. + if (PropRefExp->isImplicitProperty()) + return false; + if (ObjCPropertyDecl *PDecl = PropRefExp->getExplicitProperty()) { + if (!SynthesizedProperties.count(PDecl)) + return false; + } + } + else + return false; + + Expr *RHS = BOE->getRHS(); + bool RHSIsNull = RHS->isNullPointerConstant(Ctx, + Expr::NPC_ValueDependentIsNull); + if (RHSIsNull) + return true; + + return isZeroingPropIvar(RHS); + } +}; + +} // anonymous namespace + +static void removeZeroOutIvarsInDealloc(MigrationPass &pass) { + ZeroOutInDeallocRemover trans(pass); + trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); +} + +//===----------------------------------------------------------------------===// +// getAllTransformations. +//===----------------------------------------------------------------------===// + +static void independentTransforms(MigrationPass &pass) { + rewriteAutoreleasePool(pass); + changeIvarsOfAssignProperties(pass); + removeRetainReleaseDealloc(pass); + rewriteUnusedDelegateInit(pass); + removeZeroOutIvarsInDealloc(pass); + makeAssignARCSafe(pass); + castNonObjCToObjC(pass); + rewriteBlockObjCVariable(pass); + rewriteAllocCopyWithZone(pass); +} + +std::vector arcmt::getAllTransformations() { + std::vector transforms; + + // This must come first since rewriteAutoreleasePool depends on -release + // calls being present to determine the @autorelease ending scope. + transforms.push_back(independentTransforms); + + transforms.push_back(removeEmptyStatements); + transforms.push_back(removeDeallocMethod); + + return transforms; +} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b4574344bc..7b46c6040a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(Sema) add_subdirectory(CodeGen) add_subdirectory(Analysis) add_subdirectory(Rewrite) +add_subdirectory(ARCMigrate) add_subdirectory(Driver) add_subdirectory(Serialization) add_subdirectory(Frontend) diff --git a/lib/Driver/Tools.cpp b/lib/Driver/Tools.cpp index 1947827438..85f4c1fb21 100644 --- a/lib/Driver/Tools.cpp +++ b/lib/Driver/Tools.cpp @@ -1389,6 +1389,26 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Args.AddLastArg(CmdArgs, options::OPT_working_directory); + if (!Args.hasArg(options::OPT_fno_objc_arc)) { + if (const Arg *A = Args.getLastArg(options::OPT_ccc_arrmt_check, + options::OPT_ccc_arrmt_modify, + options::OPT_ccc_arrmt_modify_in_memory)) { + switch (A->getOption().getID()) { + default: + llvm_unreachable("missed a case"); + case options::OPT_ccc_arrmt_check: + CmdArgs.push_back("-arcmt-check"); + break; + case options::OPT_ccc_arrmt_modify: + CmdArgs.push_back("-arcmt-modify"); + break; + case options::OPT_ccc_arrmt_modify_in_memory: + CmdArgs.push_back("-arcmt-modify-in-memory"); + break; + } + } + } + // Add preprocessing options like -I, -D, etc. if we are using the // preprocessor. // diff --git a/lib/Frontend/CMakeLists.txt b/lib/Frontend/CMakeLists.txt index 7dcbebff39..5565b7b4a8 100644 --- a/lib/Frontend/CMakeLists.txt +++ b/lib/Frontend/CMakeLists.txt @@ -1,4 +1,5 @@ set( LLVM_USED_LIBS + clangARCMigrate clangAST clangBasic clangDriver diff --git a/lib/Frontend/CompilerInstance.cpp b/lib/Frontend/CompilerInstance.cpp index 38fcfe3c47..b05c24a985 100644 --- a/lib/Frontend/CompilerInstance.cpp +++ b/lib/Frontend/CompilerInstance.cpp @@ -26,6 +26,7 @@ #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/VerifyDiagnosticsClient.h" #include "clang/Frontend/Utils.h" +#include "clang/ARCMigrate/ARCMT.h" #include "clang/Serialization/ASTReader.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "llvm/Support/FileSystem.h" @@ -596,6 +597,34 @@ bool CompilerInstance::ExecuteAction(FrontendAction &Act) { if (hasSourceManager()) getSourceManager().clearIDTables(); + switch (getFrontendOpts().ARCMTAction) { + default: + break; + + case FrontendOptions::ARCMT_Check: + if (arcmt::checkForManualIssues(getInvocation(), InFile, + getFrontendOpts().Inputs[i].first, + getDiagnostics().getClient())) + continue; + // We only want to see warnings reported from arcmt::checkForManualIssues. + getDiagnostics().setIgnoreAllWarnings(true); + break; + + case FrontendOptions::ARCMT_Modify: + if (arcmt::applyTransformations(getInvocation(), InFile, + getFrontendOpts().Inputs[i].first, + getDiagnostics().getClient())) + continue; + break; + + case FrontendOptions::ARCMT_ModifyInMemory: + if (arcmt::applyTransformationsInMemory(getInvocation(), InFile, + getFrontendOpts().Inputs[i].first, + getDiagnostics().getClient())) + continue; + break; + } + if (Act.BeginSourceFile(*this, InFile, getFrontendOpts().Inputs[i].first)) { Act.Execute(); Act.EndSourceFile(); diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 1324926f94..2c158fb1e4 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -415,6 +415,19 @@ static void FrontendOptsToArgs(const FrontendOptions &Opts, Res.push_back("-version"); if (Opts.FixWhatYouCan) Res.push_back("-fix-what-you-can"); + switch (Opts.ARCMTAction) { + case FrontendOptions::ARCMT_None: + break; + case FrontendOptions::ARCMT_Check: + Res.push_back("-arcmt-check"); + break; + case FrontendOptions::ARCMT_Modify: + Res.push_back("-arcmt-modify"); + break; + case FrontendOptions::ARCMT_ModifyInMemory: + Res.push_back("-arcmt-modify-in-memory"); + break; + } bool NeedLang = false; for (unsigned i = 0, e = Opts.Inputs.size(); i != e; ++i) @@ -1227,6 +1240,25 @@ static InputKind ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args, Opts.FixWhatYouCan = Args.hasArg(OPT_fix_what_you_can); Opts.Modules = Args.getAllArgValues(OPT_import_module); + Opts.ARCMTAction = FrontendOptions::ARCMT_None; + if (const Arg *A = Args.getLastArg(OPT_arcmt_check, + OPT_arcmt_modify, + OPT_arcmt_modify_in_memory)) { + switch (A->getOption().getID()) { + default: + llvm_unreachable("missed a case"); + case OPT_arcmt_check: + Opts.ARCMTAction = FrontendOptions::ARCMT_Check; + break; + case OPT_arcmt_modify: + Opts.ARCMTAction = FrontendOptions::ARCMT_Modify; + break; + case OPT_arcmt_modify_in_memory: + Opts.ARCMTAction = FrontendOptions::ARCMT_ModifyInMemory; + break; + } + } + InputKind DashX = IK_None; if (const Arg *A = Args.getLastArg(OPT_x)) { DashX = llvm::StringSwitch(A->getValue(Args)) diff --git a/lib/Makefile b/lib/Makefile index 924819c818..74df7abcef 100755 --- a/lib/Makefile +++ b/lib/Makefile @@ -9,8 +9,8 @@ CLANG_LEVEL := .. PARALLEL_DIRS = Headers Basic Lex Parse AST Sema CodeGen Analysis \ - StaticAnalyzer Rewrite Serialization Frontend FrontendTool \ - Index Driver + StaticAnalyzer Rewrite ARCMigrate Serialization Frontend \ + FrontendTool Index Driver include $(CLANG_LEVEL)/Makefile diff --git a/test/ARCMT/Common.h b/test/ARCMT/Common.h new file mode 100644 index 0000000000..1ddfef942d --- /dev/null +++ b/test/ARCMT/Common.h @@ -0,0 +1,72 @@ +#if __has_feature(objc_arr) +#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode"))) +#else +#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE +#endif + +typedef struct _NSZone NSZone; +typedef int BOOL; +typedef unsigned NSUInteger; +typedef int int32_t; +typedef unsigned char uint8_t; +typedef int32_t UChar32; +typedef unsigned char UChar; + +@protocol NSObject +- (BOOL)isEqual:(id)object; +- (id)retain NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (NSUInteger)retainCount NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (oneway void)release NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (id)autorelease NS_AUTOMATED_REFCOUNT_UNAVAILABLE; + +- (NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +@end + +@protocol NSCopying +- (id)copyWithZone:(NSZone *)zone; +@end + +@protocol NSMutableCopying +- (id)mutableCopyWithZone:(NSZone *)zone; +@end + +@interface NSObject {} +- (id)init; + ++ (id)new; ++ (id)allocWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; ++ (id)alloc; +- (void)dealloc; + +- (void)finalize; + +- (id)copy; +- (id)mutableCopy; + ++ (id)copyWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; ++ (id)mutableCopyWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +@end + +extern void NSRecycleZone(NSZone *zone); + +NS_AUTOMATED_REFCOUNT_UNAVAILABLE +@interface NSAutoreleasePool : NSObject { +@private + void *_token; + void *_reserved3; + void *_reserved2; + void *_reserved; +} + ++ (void)addObject:(id)anObject; + +- (void)addObject:(id)anObject; + +- (void)drain; + +@end + +typedef const void* objc_objectptr_t; +extern __attribute__((ns_returns_retained)) id objc_retainedObject(objc_objectptr_t __attribute__((cf_consumed)) pointer); +extern __attribute__((ns_returns_not_retained)) id objc_unretainedObject(objc_objectptr_t pointer); +extern objc_objectptr_t objc_unretainedPointer(id object); diff --git a/test/ARCMT/alloc-with-zone-check.m b/test/ARCMT/alloc-with-zone-check.m new file mode 100644 index 0000000000..a987fa8b84 --- /dev/null +++ b/test/ARCMT/alloc-with-zone-check.m @@ -0,0 +1,80 @@ +// RUN: arcmt-test -check-only -verify --args %s + +#if __has_feature(objc_arr) +#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode"))) +#else +#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE +#endif + +typedef struct _NSZone NSZone; +typedef int BOOL; +typedef unsigned NSUInteger; + +@protocol NSObject +- (BOOL)isEqual:(id)object; +- (id)retain NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (NSUInteger)retainCount NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (oneway void)release NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (id)autorelease NS_AUTOMATED_REFCOUNT_UNAVAILABLE; + +- (NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; // expected-note {{marked unavailable here}} +@end + +@protocol NSCopying +- (id)copyWithZone:(NSZone *)zone; +@end + +@protocol NSMutableCopying +- (id)mutableCopyWithZone:(NSZone *)zone; +@end + +@interface NSObject {} +- (id)init; + ++ (id)allocWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; // expected-note 2 {{marked unavailable here}} ++ (id)alloc; +- (void)dealloc; + +- (void)finalize; + +- (id)copy; +- (id)mutableCopy; + ++ (id)copyWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; ++ (id)mutableCopyWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +@end + +extern void NSRecycleZone(NSZone *zone); + +id IhaveSideEffect(); + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(id)test:(id)obj; +@end + +@implementation Foo + +@synthesize bar; + +-(id)test:(id)obj { + Foo *foo1 = [[Foo allocWithZone:[self zone]] init]; + Foo *foo2 = [[Foo allocWithZone:[super zone]] init]; + Foo *foo3 = [[Foo allocWithZone:[IhaveSideEffect() zone]] init]; // expected-error {{not available}} + NSRecycleZone([self zone]); // expected-error {{not available}} + + foo1 = [foo1 copyWithZone:[self zone]]; + foo2 = [foo1 copyWithZone:[super zone]]; + foo3 = [foo1 copyWithZone:[IhaveSideEffect() zone]]; + foo1 = [foo1 mutableCopyWithZone:[self zone]]; + + return foo1; +} + ++(id)allocWithZone:(NSZone *)zone { + return [super allocWithZone:zone]; // expected-error {{not available in automatic reference counting mode}} +} + +@end diff --git a/test/ARCMT/alloc-with-zone.m b/test/ARCMT/alloc-with-zone.m new file mode 100644 index 0000000000..b8b78d1ed4 --- /dev/null +++ b/test/ARCMT/alloc-with-zone.m @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(id)test:(NSZone *)z; +@end + +@implementation Foo + +@synthesize bar; + ++(id)class_test:(NSZone *)z { + return [self allocWithZone:z]; +} + +-(id)test:(NSZone *)z { + NSZone *z2 = [self zone], *z3 = z2; + NSZone *z4 = z3; + + Foo *foo1 = [[Foo allocWithZone:[self zone]] init]; + Foo *foo2 = [[Foo allocWithZone:[super zone]] init]; + Foo *foo3 = [[Foo allocWithZone:z] init]; + + Foo *foo4 = [[Foo allocWithZone:z2] init]; + Foo *foo5 = [[Foo allocWithZone:z3] init]; + Foo *foo6 = [[Foo allocWithZone:z4] init]; + + foo1 = [foo1 copyWithZone:[self zone]]; + foo2 = [foo1 copyWithZone:[super zone]]; + foo3 = [foo1 copyWithZone:z]; + foo1 = [foo1 mutableCopyWithZone:[self zone]]; + + return foo1; +} + +@end diff --git a/test/ARCMT/alloc-with-zone.m.result b/test/ARCMT/alloc-with-zone.m.result new file mode 100644 index 0000000000..52daed619e --- /dev/null +++ b/test/ARCMT/alloc-with-zone.m.result @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(id)test:(NSZone *)z; +@end + +@implementation Foo + +@synthesize bar; + ++(id)class_test:(NSZone *)z { + return [self alloc]; +} + +-(id)test:(NSZone *)z { + + Foo *foo1 = [[Foo alloc] init]; + Foo *foo2 = [[Foo alloc] init]; + Foo *foo3 = [[Foo alloc] init]; + + Foo *foo4 = [[Foo alloc] init]; + Foo *foo5 = [[Foo alloc] init]; + Foo *foo6 = [[Foo alloc] init]; + + foo1 = [foo1 copy]; + foo2 = [foo1 copy]; + foo3 = [foo1 copy]; + foo1 = [foo1 mutableCopy]; + + return foo1; +} + +@end diff --git a/test/ARCMT/assign-prop-no-arc-runtime.m b/test/ARCMT/assign-prop-no-arc-runtime.m new file mode 100644 index 0000000000..07a5b40b6f --- /dev/null +++ b/test/ARCMT/assign-prop-no-arc-runtime.m @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -fobjc-no-arc-runtime -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s -D__IPHONE_OS_VERSION_MIN_REQUIRED=40300 > %t +// RUN: diff %t %s.result +// RUN: arcmt-test --args -arch x86_64 %s -miphoneos-version-min=4.3 > %t +// RUN: diff %t %s.result +// RUN: arcmt-test --args -arch x86_64 %s -mmacosx-version-min=10.6 > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface Foo : NSObject { + NSObject *x; +} +@property (readonly,assign) id x; +@end + +@implementation Foo +@synthesize x; +@end diff --git a/test/ARCMT/assign-prop-no-arc-runtime.m.result b/test/ARCMT/assign-prop-no-arc-runtime.m.result new file mode 100644 index 0000000000..69909f3416 --- /dev/null +++ b/test/ARCMT/assign-prop-no-arc-runtime.m.result @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -fobjc-no-arc-runtime -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s -D__IPHONE_OS_VERSION_MIN_REQUIRED=40300 > %t +// RUN: diff %t %s.result +// RUN: arcmt-test --args -arch x86_64 %s -miphoneos-version-min=4.3 > %t +// RUN: diff %t %s.result +// RUN: arcmt-test --args -arch x86_64 %s -mmacosx-version-min=10.6 > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface Foo : NSObject { + NSObject *__unsafe_unretained x; +} +@property (readonly,assign) id x; +@end + +@implementation Foo +@synthesize x; +@end diff --git a/test/ARCMT/assign-prop-with-arc-runtime.m b/test/ARCMT/assign-prop-with-arc-runtime.m new file mode 100644 index 0000000000..86f5fbb616 --- /dev/null +++ b/test/ARCMT/assign-prop-with-arc-runtime.m @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s -D__IPHONE_OS_VERSION_MIN_REQUIRED=50000 > %t +// RUN: diff %t %s.result +// RUN: arcmt-test --args -arch x86_64 %s -miphoneos-version-min=5.0 > %t +// RUN: diff %t %s.result +// RUN: arcmt-test --args -arch x86_64 %s -mmacosx-version-min=10.7 > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface Foo : NSObject { + NSObject *x, *w, *q1, *q2; + NSObject *z1, *__unsafe_unretained z2; +} +@property (readonly,assign) id x; +@property (assign) id w; +@property (assign) id q1, q2; +@property (assign) id z1, z2; +@end + +@implementation Foo +@synthesize x,w,q1,q2,z1,z2; +@end diff --git a/test/ARCMT/assign-prop-with-arc-runtime.m.result b/test/ARCMT/assign-prop-with-arc-runtime.m.result new file mode 100644 index 0000000000..59f7608278 --- /dev/null +++ b/test/ARCMT/assign-prop-with-arc-runtime.m.result @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s -D__IPHONE_OS_VERSION_MIN_REQUIRED=50000 > %t +// RUN: diff %t %s.result +// RUN: arcmt-test --args -arch x86_64 %s -miphoneos-version-min=5.0 > %t +// RUN: diff %t %s.result +// RUN: arcmt-test --args -arch x86_64 %s -mmacosx-version-min=10.7 > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface Foo : NSObject { + NSObject *__weak x, *__weak w, *__weak q1, *__weak q2; + NSObject *__unsafe_unretained z1, *__unsafe_unretained z2; +} +@property (readonly,weak) id x; +@property (weak) id w; +@property (weak) id q1, q2; +@property (assign) id z1, z2; +@end + +@implementation Foo +@synthesize x,w,q1,q2,z1,z2; +@end diff --git a/test/ARCMT/atautorelease-2.m b/test/ARCMT/atautorelease-2.m new file mode 100644 index 0000000000..919e10edf2 --- /dev/null +++ b/test/ARCMT/atautorelease-2.m @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface NSAutoreleasePool +- drain; ++new; ++alloc; +-init; +-autorelease; +-release; +@end + +void NSLog(id, ...); + +int main (int argc, const char * argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSAutoreleasePool *chunkPool = [[NSAutoreleasePool alloc] init]; + + while (argc) { + [chunkPool release]; + return 0; + } + + [chunkPool drain]; + [pool drain]; + + return 0; +} diff --git a/test/ARCMT/atautorelease-2.m.result b/test/ARCMT/atautorelease-2.m.result new file mode 100644 index 0000000000..7a55cb43ae --- /dev/null +++ b/test/ARCMT/atautorelease-2.m.result @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface NSAutoreleasePool +- drain; ++new; ++alloc; +-init; +-autorelease; +-release; +@end + +void NSLog(id, ...); + +int main (int argc, const char * argv[]) { + @autoreleasepool { + @autoreleasepool { + + while (argc) { + return 0; + } + + } + } + + return 0; +} diff --git a/test/ARCMT/atautorelease-3.m b/test/ARCMT/atautorelease-3.m new file mode 100644 index 0000000000..4498908472 --- /dev/null +++ b/test/ARCMT/atautorelease-3.m @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface NSAutoreleasePool +- drain; ++new; ++alloc; +-init; +-autorelease; +- release; +@end + +void NSLog(id, ...); + +void test1(int x) { + // All this stuff get removed since nothing is happening inside. + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSAutoreleasePool *chunkPool = [[NSAutoreleasePool alloc] init]; + while (x) { + chunkPool = [[NSAutoreleasePool alloc] init]; + [chunkPool release]; + } + + [chunkPool drain]; + [pool drain]; +} + +void test2(int x) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSAutoreleasePool *chunkPool = [[NSAutoreleasePool alloc] init]; + while (x) { + chunkPool = [[NSAutoreleasePool alloc] init]; + ++x; + [chunkPool release]; + } + + [chunkPool drain]; + [pool drain]; +} diff --git a/test/ARCMT/atautorelease-3.m.result b/test/ARCMT/atautorelease-3.m.result new file mode 100644 index 0000000000..51b0e570b6 --- /dev/null +++ b/test/ARCMT/atautorelease-3.m.result @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface NSAutoreleasePool +- drain; ++new; ++alloc; +-init; +-autorelease; +- release; +@end + +void NSLog(id, ...); + +void test1(int x) { + // All this stuff get removed since nothing is happening inside. +} + +void test2(int x) { + @autoreleasepool { + @autoreleasepool { + while (x) { + @autoreleasepool { + ++x; + } + } + + } + } +} diff --git a/test/ARCMT/atautorelease-check.m b/test/ARCMT/atautorelease-check.m new file mode 100644 index 0000000000..126642cd38 --- /dev/null +++ b/test/ARCMT/atautorelease-check.m @@ -0,0 +1,144 @@ +// RUN: arcmt-test -check-only -verify --args %s + +#if __has_feature(objc_arr) +#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode"))) +#else +#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE +#endif + +typedef struct _NSZone NSZone; +typedef int BOOL; +typedef unsigned NSUInteger; + +@protocol NSObject +- (BOOL)isEqual:(id)object; +- (id)retain NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (NSUInteger)retainCount NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (oneway void)release NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +- (id)autorelease NS_AUTOMATED_REFCOUNT_UNAVAILABLE; + +- (NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +@end + +@protocol NSCopying +- (id)copyWithZone:(NSZone *)zone; +@end + +@protocol NSMutableCopying +- (id)mutableCopyWithZone:(NSZone *)zone; +@end + +@interface NSObject {} +- (id)init; + ++ (id)new; ++ (id)allocWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; ++ (id)alloc; +- (void)dealloc; + +- (void)finalize; + +- (id)copy; +- (id)mutableCopy; + ++ (id)copyWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; ++ (id)mutableCopyWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; +@end + +extern void NSRecycleZone(NSZone *zone); + +NS_AUTOMATED_REFCOUNT_UNAVAILABLE +@interface NSAutoreleasePool : NSObject { // expected-note 13 {{marked unavailable here}} +@private + void *_token; + void *_reserved3; + void *_reserved2; + void *_reserved; +} + ++ (void)addObject:(id)anObject; + +- (void)addObject:(id)anObject; + +- (void)drain; + +@end + + +void NSLog(id, ...); + +int main (int argc, const char * argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSAutoreleasePool *chunkPool = [[NSAutoreleasePool alloc] init]; // expected-error 2 {{'NSAutoreleasePool' is unavailable}} + + while (argc) { + [chunkPool release]; + // the following pool was not released in this scope, don't touch it. + chunkPool = [[NSAutoreleasePool alloc] init]; // expected-error {{'NSAutoreleasePool' is unavailable}} + } + + [chunkPool drain]; + [pool drain]; + + return 0; +} + +void f(void) { + NSAutoreleasePool * pool; // expected-error {{'NSAutoreleasePool' is unavailable}} + + for (int i=0; i != 10; ++i) { + id x = pool; // We won't touch a NSAutoreleasePool if we can't safely + // remove all the references to it. + } + + pool = [[NSAutoreleasePool alloc] init]; // expected-error {{'NSAutoreleasePool' is unavailable}} + NSLog(@"%s", "YES"); + [pool release]; +} + +void f2(void) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // expected-error 2 {{'NSAutoreleasePool' is unavailable}} \ + // expected-note {{scope begins here}} + + // 'x' is declared inside the "pool scope" but used outside it, if we create + // a @autorelease scope it will be undefined outside it so don't touch the pool. + int x = 0; // expected-note {{declared here}} + + [pool release]; // expected-note {{scope ends here}} + + ++x; // expected-error {{a name is referenced outside the NSAutoreleasePool scope that it was declared in}} +} + +void f3(void) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // expected-error 2 {{'NSAutoreleasePool' is unavailable}} \ + // expected-note {{scope begins here}} + + struct S { int x; }; // expected-note {{declared here}} + + [pool release]; // expected-note {{scope ends here}} + + struct S *var; // expected-error {{a name is referenced outside the NSAutoreleasePool scope that it was declared in}} + var->x = 0; +} + +void f4(void) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // expected-error 2 {{'NSAutoreleasePool' is unavailable}} \ + // expected-note {{scope begins here}} + + enum { Bar }; // expected-note {{declared here}} + + [pool release]; // expected-note {{scope ends here}} + + int x = Bar; // expected-error {{a name is referenced outside the NSAutoreleasePool scope that it was declared in}} +} + +void f5(void) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // expected-error 2 {{'NSAutoreleasePool' is unavailable}} \ + // expected-note {{scope begins here}} + + typedef int Bar; // expected-note {{declared here}} + + [pool release]; // expected-note {{scope ends here}} + + Bar x; // expected-error {{a name is referenced outside the NSAutoreleasePool scope that it was declared in}} +} diff --git a/test/ARCMT/atautorelease.m b/test/ARCMT/atautorelease.m new file mode 100644 index 0000000000..fa1a24b916 --- /dev/null +++ b/test/ARCMT/atautorelease.m @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +void NSLog(id, ...); + +int main (int argc, const char * argv[]) { + + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + + if (argc) { + NSAutoreleasePool * pool = [NSAutoreleasePool new]; + NSLog(@"%s", "YES"); + [pool drain]; + } + [pool drain]; + + NSAutoreleasePool * pool1 = [[NSAutoreleasePool alloc] init]; + NSLog(@"%s", "YES"); + [pool1 release]; + + return 0; +} + +void f(void) { + NSAutoreleasePool *pool1; + + pool1 = [NSAutoreleasePool new]; + int x = 4; + + NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init]; + ++x; + [pool2 drain]; + + [pool1 release]; +} + +int UIApplicationMain(int argc, char *argv[]); + +int main2(int argc, char *argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + int result = UIApplicationMain(argc, argv); + [pool release]; + return result; +} diff --git a/test/ARCMT/atautorelease.m.result b/test/ARCMT/atautorelease.m.result new file mode 100644 index 0000000000..8cd649aba9 --- /dev/null +++ b/test/ARCMT/atautorelease.m.result @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +void NSLog(id, ...); + +int main (int argc, const char * argv[]) { + + @autoreleasepool { + + if (argc) { + @autoreleasepool { + NSLog(@"%s", "YES"); + } + } + } + + @autoreleasepool { + NSLog(@"%s", "YES"); + } + + return 0; +} + +void f(void) { + + @autoreleasepool { + int x = 4; + + @autoreleasepool { + ++x; + } + + } +} + +int UIApplicationMain(int argc, char *argv[]); + +int main2(int argc, char *argv[]) { + @autoreleasepool { + int result = UIApplicationMain(argc, argv); + return result; + } +} diff --git a/test/ARCMT/autoreleases.m b/test/ARCMT/autoreleases.m new file mode 100644 index 0000000000..284d92773d --- /dev/null +++ b/test/ARCMT/autoreleases.m @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +typedef unsigned char BOOL; + +@interface NSObject { + id isa; +} ++new; ++alloc; +-init; +-autorelease; +@end + +@interface NSAutoreleasePool : NSObject +- drain; +@end + +@interface A : NSObject { +@package + id object; +} +@end + +@interface B : NSObject +- (BOOL)containsSelf:(A*)a; +@end + +@implementation A +@end + +@implementation B +- (BOOL)containsSelf:(A*)a { + return a->object == self; +} +@end + +void NSLog(id, ...); + +int main (int argc, const char * argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + A *a = [[A new] autorelease]; + B *b = [[B new] autorelease]; + NSLog(@"%s", [b containsSelf:a] ? "YES" : "NO"); + [pool drain]; + return 0; +} diff --git a/test/ARCMT/autoreleases.m.result b/test/ARCMT/autoreleases.m.result new file mode 100644 index 0000000000..fb2959fa90 --- /dev/null +++ b/test/ARCMT/autoreleases.m.result @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +typedef unsigned char BOOL; + +@interface NSObject { + id isa; +} ++new; ++alloc; +-init; +-autorelease; +@end + +@interface NSAutoreleasePool : NSObject +- drain; +@end + +@interface A : NSObject { +@package + id object; +} +@end + +@interface B : NSObject +- (BOOL)containsSelf:(A*)a; +@end + +@implementation A +@end + +@implementation B +- (BOOL)containsSelf:(A*)a { + return a->object == self; +} +@end + +void NSLog(id, ...); + +int main (int argc, const char * argv[]) { + @autoreleasepool { + A *a = [A new]; + B *b = [B new]; + NSLog(@"%s", [b containsSelf:a] ? "YES" : "NO"); + } + return 0; +} diff --git a/test/ARCMT/checking.m b/test/ARCMT/checking.m new file mode 100644 index 0000000000..ffb245e8bb --- /dev/null +++ b/test/ARCMT/checking.m @@ -0,0 +1,255 @@ +// RUN: arcmt-test -check-only -verify --args %s + +#include "Common.h" + +typedef const struct __CFString * CFStringRef; +extern const CFStringRef kUTTypePlainText; +extern const CFStringRef kUTTypeRTF; +@class NSString; +@class A; + +struct UnsafeS { + A *__unsafe_unretained unsafeObj; +}; + +@interface A : NSObject +- (id)retain; +- (id)retainCount; +- (id)autorelease; +- (id)init; +- (oneway void)release; +- (void)dealloc; +-(void)test; +@end + +@implementation A +-(void)test { + [super dealloc]; +} +-(void)dealloc { + [super dealloc]; +} + +- (id)retain { return self; } // expected-error {{ARC forbids implementation}} +- (id)retainCount { return self; } // expected-error {{ARC forbids implementation}} +- (id)autorelease { return self; } // expected-error {{ARC forbids implementation}} +- (oneway void)release { } // expected-error {{ARC forbids implementation}} +@end + +void test1(A *a, BOOL b, struct UnsafeS *unsafeS) { + [unsafeS->unsafeObj retain]; // expected-error {{It is not safe to remove 'retain' message on an __unsafe_unretained type}} \ + // expected-error {{ARC forbids explicit message send}} + [a dealloc]; + [a retain]; + [a retainCount]; // expected-error {{ARC forbids explicit message send of 'retainCount'}} + [a release]; + [a autorelease]; + + CFStringRef cfstr; + NSString *str = (NSString *)cfstr; // expected-error {{cast of C pointer type 'CFStringRef' (aka 'const struct __CFString *') to Objective-C pointer type 'NSString *' requires a bridged cast}} \ + // expected-note{{use __bridge to convert directly (no change in ownership)}} \ + // expected-note{{use __bridge_transfer to transfer ownership of a +1 'CFStringRef' (aka 'const struct __CFString *') into ARC}} + str = (NSString *)kUTTypePlainText; + str = b ? kUTTypeRTF : kUTTypePlainText; + str = (NSString *)(b ? kUTTypeRTF : kUTTypePlainText); + str = (NSString *)a; // no change. + + SEL s = @selector(retain); // expected-error {{ARC forbids use of 'retain' in a @selector}} + s = @selector(release); // expected-error {{ARC forbids use of 'release' in a @selector}} + s = @selector(autorelease); // expected-error {{ARC forbids use of 'autorelease' in a @selector}} + s = @selector(dealloc); // expected-error {{ARC forbids use of 'dealloc' in a @selector}} + + static id __autoreleasing X1; // expected-error {{global variables cannot have __autoreleasing lifetime}} +} + +struct S { + A* a; // expected-error {{ARC forbids Objective-C objects in structs or unions}} +}; + +@interface B +-(id)alloc; +- (id)initWithInt: (int) i; +@end + +void rdar8861761() { + B *o1 = [[B alloc] initWithInt:0]; + B *o2 = [B alloc]; + [o2 initWithInt:0]; +} + +@interface Test13 +- (id) init0; +- (void) noninit; +@end +@implementation Test13 +- (id) init0 { + self = 0; +} +- (void) noninit { + self = 0; // expected-error {{cannot assign to 'self' outside of a method in the init family}} + + for (id x in collection) { // expected-error {{use of undeclared identifier 'collection'}} + x = 0; + } +} +@end + +void * cvt(id arg) +{ + void* voidp_val; + (void)(int*)arg; // expected-error {{disallowed}} + (void)(id)arg; + (void)(__autoreleasing id*)arg; // expected-error {{disallowed}} + (void)(id*)arg; // expected-error {{pointer to non-const type 'id' with no explicit lifetime}} expected-error {{disallowed}} + + (void)(__autoreleasing id**)voidp_val; + (void)(void*)voidp_val; + (void)(void**)arg; // expected-error {{disallowed}} + cvt((void*)arg); // expected-error {{requires a bridged cast}} expected-error {{disallowed}} \ + // expected-note {{use __bridge}} expected-note {{use __bridge_retained}} + cvt(0); + (void)(__strong id**)(0); + return arg; // expected-error {{disallowed}} +} + + +void test12(id collection) { + for (id x in collection) { + x = 0; + } + + for (__strong id x in collection) { + x = 0; + } +} + +void test6(unsigned cond) { + // FIXME: Fix this automatically ? + switch (cond) { + case 0: + ; + id x; // expected-note {{jump bypasses initialization of retaining variable}} + + case 1: // expected-error {{switch case is in protected scope}} + break; + } +} + +@class Test8_incomplete; +@interface Test8_complete @end; +@interface Test8_super @end; +@interface Test8 : Test8_super +- (id) init00; +- (id) init01; // expected-note {{declaration in interface}} +- (id) init02; +- (id) init03; // covariance +- (id) init04; // covariance +- (id) init05; + +- (void) init10; // expected-note {{declaration in interface is not in the 'init' family because its result type is not an object pointer}} +- (void) init11; +- (void) init12; +- (void) init13; // expected-note {{declaration in interface is not in the 'init' family because its result type is not an object pointer}} +- (void) init14; // expected-note {{declaration in interface is not in the 'init' family because its result type is not an object pointer}} +- (void) init15; + +// These should be invalid to actually call. +- (Test8_incomplete*) init20; +- (Test8_incomplete*) init21; // expected-note {{declaration in interface}} +- (Test8_incomplete*) init22; +- (Test8_incomplete*) init23; +- (Test8_incomplete*) init24; +- (Test8_incomplete*) init25; + +- (Test8_super*) init30; // id exception to covariance +- (Test8_super*) init31; // expected-note {{declaration in interface}} +- (Test8_super*) init32; +- (Test8_super*) init33; +- (Test8_super*) init34; // covariance +- (Test8_super*) init35; + +- (Test8*) init40; // id exception to covariance +- (Test8*) init41; // expected-note {{declaration in interface}} +- (Test8*) init42; +- (Test8*) init43; // this should be a warning, but that's a general language thing, not an ARC thing +- (Test8*) init44; +- (Test8*) init45; + +- (Test8_complete*) init50; // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init51; // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init52; // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init53; // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init54; // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init55; // expected-error {{init methods must return a type related to the receiver type}} +@end +@implementation Test8 +- (id) init00 { return 0; } +- (id) init10 { return 0; } // expected-error {{method implementation does not match its declaration}} +- (id) init20 { return 0; } +- (id) init30 { return 0; } +- (id) init40 { return 0; } +- (id) init50 { return 0; } + +- (void) init01 {} // expected-error {{method was declared as an 'init' method, but its implementation doesn't match because its result type is not an object pointer}} +- (void) init11 {} +- (void) init21 {} // expected-error {{method was declared as an 'init' method, but its implementation doesn't match because its result type is not an object pointer}} +- (void) init31 {} // expected-error {{method was declared as an 'init' method, but its implementation doesn't match because its result type is not an object pointer}} +- (void) init41 {} // expected-error {{method was declared as an 'init' method, but its implementation doesn't match because its result type is not an object pointer}} +- (void) init51 {} + +- (Test8_incomplete*) init02 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_incomplete*) init12 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_incomplete*) init22 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_incomplete*) init32 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_incomplete*) init42 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_incomplete*) init52 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} + +- (Test8_super*) init03 { return 0; } +- (Test8_super*) init13 { return 0; } // expected-error {{method implementation does not match its declaration}} +- (Test8_super*) init23 { return 0; } +- (Test8_super*) init33 { return 0; } +- (Test8_super*) init43 { return 0; } +- (Test8_super*) init53 { return 0; } + +- (Test8*) init04 { return 0; } +- (Test8*) init14 { return 0; } // expected-error {{method implementation does not match its declaration}} +- (Test8*) init24 { return 0; } +- (Test8*) init34 { return 0; } +- (Test8*) init44 { return 0; } +- (Test8*) init54 { return 0; } + +- (Test8_complete*) init05 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init15 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init25 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init35 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init45 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +- (Test8_complete*) init55 { return 0; } // expected-error {{init methods must return a type related to the receiver type}} +@end + +@class Test9_incomplete; +@interface Test9 +- (Test9_incomplete*) init1; // expected-error {{init methods must return a type related to the receiver type}} +- (Test9_incomplete*) init2; +@end +id test9(Test9 *v) { + return [v init1]; +} + +// rdar://9491791 +void rdar9491791(int p) { + switch (p) { + case 3:; + NSObject *o = [[NSObject alloc] init]; // expected-note {{jump bypasses initialization of retaining variable}} + [o release]; + break; + default: // expected-error {{switch case is in protected scope}} + break; + } +} + +#define RELEASE_MACRO(x) do { [x release]; } while(1) + +// rdar://9504750 +void rdar9504750(id p) { + RELEASE_MACRO(p); // expected-error {{ARC forbids explicit message send of 'release'}} +} diff --git a/test/ARCMT/cxx-checking.mm b/test/ARCMT/cxx-checking.mm new file mode 100644 index 0000000000..cf47bba0c6 --- /dev/null +++ b/test/ARCMT/cxx-checking.mm @@ -0,0 +1,105 @@ +// RUN: arcmt-test -check-only -verify --args -Warc-abi %s + +// Classes that have an Objective-C object pointer. +struct HasObjectMember0 { // expected-warning{{'HasObjectMember0' cannot be shared between ARC and non-ARC code; add a copy constructor, a copy assignment operator, and a destructor to make it ABI-compatible}} + id x; +}; + +struct HasObjectMember1 { // expected-warning{{'HasObjectMember1' cannot be shared between ARC and non-ARC code; add a copy constructor, a copy assignment operator, and a destructor to make it ABI-compatible}} + id x[3]; +}; + +struct HasObjectMember2 { // expected-warning{{'HasObjectMember2' cannot be shared between ARC and non-ARC code; add a copy constructor, a copy assignment operator, and a destructor to make it ABI-compatible}} + id x[3][2]; +}; + +// Don't complain if the type has non-external linkage +namespace { + struct HasObjectMember3 { + id x[3][2]; + }; +} + +// Don't complain if the Objective-C pointer type was explicitly given +// no lifetime. +struct HasObjectMember3 { + __unsafe_unretained id x[3][2]; +}; + +struct HasBlockPointerMember0 { // expected-warning{{'HasBlockPointerMember0' cannot be shared between ARC and non-ARC code; add a copy constructor, a copy assignment operator, and a destructor to make it ABI-compatible}} + int (^bp)(int); +}; + +struct HasBlockPointerMember1 { // expected-warning{{'HasBlockPointerMember1' cannot be shared between ARC and non-ARC code; add a copy constructor, a copy assignment operator, and a destructor to make it ABI-compatible}} + int (^bp[2][3])(int); +}; + +struct NonPOD { + NonPOD(const NonPOD&); +}; + +struct HasObjectMemberAndNonPOD0 { // expected-warning{{'HasObjectMemberAndNonPOD0' cannot be shared between ARC and non-ARC code; add a non-trivial copy assignment operator to make it ABI-compatible}} \ + // expected-warning{{'HasObjectMemberAndNonPOD0' cannot be shared between ARC and non-ARC code; add a non-trivial destructor to make it ABI-compatible}} + id x; + NonPOD np; +}; + +struct HasObjectMemberAndNonPOD1 { // expected-warning{{'HasObjectMemberAndNonPOD1' cannot be shared between ARC and non-ARC code; add a non-trivial copy assignment operator to make it ABI-compatible}} \ + // expected-warning{{'HasObjectMemberAndNonPOD1' cannot be shared between ARC and non-ARC code; add a non-trivial destructor to make it ABI-compatible}} + NonPOD np; + id x[3]; +}; + +struct HasObjectMemberAndNonPOD2 { // expected-warning{{'HasObjectMemberAndNonPOD2' cannot be shared between ARC and non-ARC code; add a non-trivial copy assignment operator to make it ABI-compatible}} \ + // expected-warning{{'HasObjectMemberAndNonPOD2' cannot be shared between ARC and non-ARC code; add a non-trivial destructor to make it ABI-compatible}} + NonPOD np; + id x[3][2]; +}; + +struct HasObjectMemberAndNonPOD3 { + HasObjectMemberAndNonPOD3 &operator=(const HasObjectMemberAndNonPOD3&); + ~HasObjectMemberAndNonPOD3(); + NonPOD np; + id x[3][2]; +}; + +struct HasBlockPointerMemberAndNonPOD0 { // expected-warning{{'HasBlockPointerMemberAndNonPOD0' cannot be shared between ARC and non-ARC code; add a non-trivial copy assignment operator to make it ABI-compatible}} \ +// expected-warning{{'HasBlockPointerMemberAndNonPOD0' cannot be shared between ARC and non-ARC code; add a non-trivial destructor to make it ABI-compatible}} + NonPOD np; + int (^bp)(int); +}; + +struct HasBlockPointerMemberAndNonPOD1 { // expected-warning{{'HasBlockPointerMemberAndNonPOD1' cannot be shared between ARC and non-ARC code; add a non-trivial copy assignment operator to make it ABI-compatible}} \ +// expected-warning{{'HasBlockPointerMemberAndNonPOD1' cannot be shared between ARC and non-ARC code; add a non-trivial destructor to make it ABI-compatible}} + NonPOD np; + int (^bp[2][3])(int); +}; + +int check_non_pod_objc_pointer0[__is_pod(id)? -1 : 1]; +int check_non_pod_objc_pointer1[__is_pod(__strong id)? -1 : 1]; +int check_non_pod_objc_pointer2[__is_pod(__unsafe_unretained id)? 1 : -1]; +int check_non_pod_objc_pointer3[__is_pod(id[2][3])? -1 : 1]; +int check_non_pod_objc_pointer4[__is_pod(__unsafe_unretained id[2][3])? 1 : -1]; +int check_non_pod_block0[__is_pod(int (^)(int))? -1 : 1]; +int check_non_pod_block1[__is_pod(int (^ __unsafe_unretained)(int))? 1 : -1]; + +struct FlexibleArrayMember0 { + int length; + id array[]; // expected-error{{flexible array member 'array' of non-POD element type 'id __strong[]'}} +}; + +struct FlexibleArrayMember1 { + int length; + __unsafe_unretained id array[]; +}; + +// It's okay to pass a retainable type through an ellipsis. +void variadic(...); +void test_variadic() { + variadic(1, 17, @"Foo"); +} + +// It's okay to create a VLA of retainable types. +void vla(int n) { + id vla[n]; +} diff --git a/test/ARCMT/dealloc.m b/test/ARCMT/dealloc.m new file mode 100644 index 0000000000..6166c426fd --- /dev/null +++ b/test/ARCMT/dealloc.m @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface A +- (id)retain; +- (id)autorelease; +- (oneway void)release; +- (void)dealloc; +@end + +void test1(A *a) { + [a dealloc]; +} + +@interface Test2 : A +- (void) dealloc; +@end + +@implementation Test2 +- (void) dealloc { + [super dealloc]; +} +@end diff --git a/test/ARCMT/dealloc.m.result b/test/ARCMT/dealloc.m.result new file mode 100644 index 0000000000..445d91a8b7 --- /dev/null +++ b/test/ARCMT/dealloc.m.result @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface A +- (id)retain; +- (id)autorelease; +- (oneway void)release; +- (void)dealloc; +@end + +void test1(A *a) { +} + +@interface Test2 : A +- (void) dealloc; +@end + +@implementation Test2 +@end diff --git a/test/ARCMT/init.m b/test/ARCMT/init.m new file mode 100644 index 0000000000..419288c355 --- /dev/null +++ b/test/ARCMT/init.m @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface NSObject +-init; +@end + +@interface A : NSObject +-init; +-init2; +-foo; ++alloc; +@end + +@implementation A +-(id) init { + [self init]; + id a; + [a init]; + a = [[A alloc] init]; + + return self; +} + +-(id) init2 { + [super init]; + return self; +} + +-(id) foo { + [self init]; + [super init]; + + return self; +} +@end diff --git a/test/ARCMT/init.m.result b/test/ARCMT/init.m.result new file mode 100644 index 0000000000..9142c37a2f --- /dev/null +++ b/test/ARCMT/init.m.result @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface NSObject +-init; +@end + +@interface A : NSObject +-init; +-init2; +-foo; ++alloc; +@end + +@implementation A +-(id) init { + self = [self init]; + id a; + [a init]; + a = [[A alloc] init]; + + return self; +} + +-(id) init2 { + self = [super init]; + return self; +} + +-(id) foo { + [self init]; + [super init]; + + return self; +} +@end diff --git a/test/ARCMT/nonobjc-to-objc-cast-2.m b/test/ARCMT/nonobjc-to-objc-cast-2.m new file mode 100644 index 0000000000..dba5227fae --- /dev/null +++ b/test/ARCMT/nonobjc-to-objc-cast-2.m @@ -0,0 +1,14 @@ +// RUN: arcmt-test -check-only -verify --args %s + +typedef int BOOL; +typedef const struct __CFString * CFStringRef; + +@class NSString; + +void f(BOOL b) { + CFStringRef cfstr; + NSString *str = (NSString *)cfstr; // expected-error {{cast of C pointer type 'CFStringRef' (aka 'const struct __CFString *') to Objective-C pointer type 'NSString *' requires a bridged cast}} \ + // expected-note{{use __bridge to convert directly (no change in ownership)}} \ + // expected-note{{use __bridge_transfer to transfer ownership of a +1 'CFStringRef' (aka 'const struct __CFString *') into ARC}} + void *vp = str; // expected-error {{disallowed}} +} diff --git a/test/ARCMT/nonobjc-to-objc-cast.m b/test/ARCMT/nonobjc-to-objc-cast.m new file mode 100644 index 0000000000..1fbe73f359 --- /dev/null +++ b/test/ARCMT/nonobjc-to-objc-cast.m @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface NSString : NSObject +@end + +typedef const struct __CFString * CFStringRef; +extern const CFStringRef kUTTypePlainText; +extern const CFStringRef kUTTypeRTF; + +typedef const struct __CFAllocator * CFAllocatorRef; +typedef const struct __CFUUID * CFUUIDRef; + +extern const CFAllocatorRef kCFAllocatorDefault; + +extern CFStringRef CFUUIDCreateString(CFAllocatorRef alloc, CFUUIDRef uuid); + +void f(BOOL b, id p) { + NSString *str = (NSString *)kUTTypePlainText; + str = b ? kUTTypeRTF : kUTTypePlainText; + str = (NSString *)(b ? kUTTypeRTF : kUTTypePlainText); + str = (NSString *)p; // no change. + + // FIXME: Add objc -> c examples that we can handle. + + CFUUIDRef _uuid; + NSString *_uuidString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, _uuid); + _uuidString = [(NSString *)CFUUIDCreateString(kCFAllocatorDefault, _uuid) autorelease]; +} diff --git a/test/ARCMT/nonobjc-to-objc-cast.m.result b/test/ARCMT/nonobjc-to-objc-cast.m.result new file mode 100644 index 0000000000..856b80f8d9 --- /dev/null +++ b/test/ARCMT/nonobjc-to-objc-cast.m.result @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface NSString : NSObject +@end + +typedef const struct __CFString * CFStringRef; +extern const CFStringRef kUTTypePlainText; +extern const CFStringRef kUTTypeRTF; + +typedef const struct __CFAllocator * CFAllocatorRef; +typedef const struct __CFUUID * CFUUIDRef; + +extern const CFAllocatorRef kCFAllocatorDefault; + +extern CFStringRef CFUUIDCreateString(CFAllocatorRef alloc, CFUUIDRef uuid); + +void f(BOOL b, id p) { + NSString *str = (__bridge NSString *)kUTTypePlainText; + str = (__bridge NSString *)(b ? kUTTypeRTF : kUTTypePlainText); + str = (__bridge NSString *)(b ? kUTTypeRTF : kUTTypePlainText); + str = (NSString *)p; // no change. + + // FIXME: Add objc -> c examples that we can handle. + + CFUUIDRef _uuid; + NSString *_uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, _uuid); + _uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, _uuid); +} diff --git a/test/ARCMT/releases.m b/test/ARCMT/releases.m new file mode 100644 index 0000000000..ddcdf93f32 --- /dev/null +++ b/test/ARCMT/releases.m @@ -0,0 +1,80 @@ +// RUN: %clang_cc1 -fobjc-nonfragile-abi -fblocks -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +typedef int BOOL; + +id IhaveSideEffect(); + +@protocol NSObject +- (BOOL)isEqual:(id)object; +- (id)retain; +- (oneway void)release; +@end + +@interface NSObject {} +@end + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(void)test:(id)obj; +@end + +@implementation Foo + +@synthesize bar; + +-(void)test:(id)obj { + id x = self.bar; + [x retain]; + self.bar = obj; + // do stuff with x; + [x release]; + + [IhaveSideEffect() release]; + + [x release], x = 0; +} + +@end + +void func(Foo *p) { + [p release]; + (([p release])); +} + +@interface Baz { + id _foo; +} +@end + +@implementation Baz +- dealloc { + [_foo release]; + return 0; +} +@end + +void block_test(Foo *p) { + id (^B)() = ^() { + if (p) { + id (^IB)() = ^() { + id bar = [p retain]; + [p release]; + return bar; + }; + IB(); + } + return [p retain]; + }; +} + +#define RELEASE_MACRO(x) [x release] +#define RELEASE_MACRO2(x) RELEASE_MACRO(x) + +void test2(id p) { + RELEASE_MACRO(p); + RELEASE_MACRO2(p); +} diff --git a/test/ARCMT/releases.m.result b/test/ARCMT/releases.m.result new file mode 100644 index 0000000000..b502cbb91d --- /dev/null +++ b/test/ARCMT/releases.m.result @@ -0,0 +1,72 @@ +// RUN: %clang_cc1 -fobjc-nonfragile-abi -fblocks -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +typedef int BOOL; + +id IhaveSideEffect(); + +@protocol NSObject +- (BOOL)isEqual:(id)object; +- (id)retain; +- (oneway void)release; +@end + +@interface NSObject {} +@end + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(void)test:(id)obj; +@end + +@implementation Foo + +@synthesize bar; + +-(void)test:(id)obj { + id x = self.bar; + self.bar = obj; + // do stuff with x; + + IhaveSideEffect(); + + x = 0; +} + +@end + +void func(Foo *p) { +} + +@interface Baz { + id _foo; +} +@end + +@implementation Baz +- dealloc { + return 0; +} +@end + +void block_test(Foo *p) { + id (^B)() = ^() { + if (p) { + id (^IB)() = ^() { + id bar = p; + return bar; + }; + IB(); + } + return p; + }; +} + +#define RELEASE_MACRO(x) [x release] +#define RELEASE_MACRO2(x) RELEASE_MACRO(x) + +void test2(id p) { +} diff --git a/test/ARCMT/remove-dealloc-method.m b/test/ARCMT/remove-dealloc-method.m new file mode 100644 index 0000000000..7689bc0a13 --- /dev/null +++ b/test/ARCMT/remove-dealloc-method.m @@ -0,0 +1,26 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#define nil ((void*) 0) + +@interface Foo +@property (retain) id x; +@property (retain) id y; +@property (retain) id w; +@property (retain) id z; +@end + +@implementation Foo +@synthesize x; +@synthesize y; +@synthesize w; +@synthesize z; + +- (void) dealloc { + self.x = 0; + [self setY:nil]; + w = nil; + self.z = nil; +} +@end diff --git a/test/ARCMT/remove-dealloc-method.m.result b/test/ARCMT/remove-dealloc-method.m.result new file mode 100644 index 0000000000..d423243a8f --- /dev/null +++ b/test/ARCMT/remove-dealloc-method.m.result @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#define nil ((void*) 0) + +@interface Foo +@property (retain) id x; +@property (retain) id y; +@property (retain) id w; +@property (retain) id z; +@end + +@implementation Foo +@synthesize x; +@synthesize y; +@synthesize w; +@synthesize z; + +@end diff --git a/test/ARCMT/remove-dealloc-zerouts.m b/test/ARCMT/remove-dealloc-zerouts.m new file mode 100644 index 0000000000..7bd00a3618 --- /dev/null +++ b/test/ARCMT/remove-dealloc-zerouts.m @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface Foo +@property (retain) id x; +@property (retain) id y; +@property (retain) id w; +@property (retain) id z; +@property (strong) id q; +@end + +@implementation Foo +@synthesize x; +@synthesize y; +@synthesize w; +@synthesize q; +@dynamic z; + +- (void) dealloc { + self.x = self.y = self.w = 0; + self.x = 0, w = 0, y = 0; + [self setY:0]; + w = 0; + q = 0; + self.z = 0; +} +@end + +@interface Bar +@property (retain) Foo *a; +- (void) setA:(Foo*) val; +- (id) a; +@end + +@implementation Bar +- (void) dealloc { + [self setA:0]; // This is user-defined setter overriding synthesize, don't touch it. + self.a.x = 0; // every dealloc must zero out its own ivar. This patter is not recognized. +} +@synthesize a; +- (void) setA:(Foo*) val { } +- (id) a {return 0;} +@end diff --git a/test/ARCMT/remove-dealloc-zerouts.m.result b/test/ARCMT/remove-dealloc-zerouts.m.result new file mode 100644 index 0000000000..ba93b3f088 --- /dev/null +++ b/test/ARCMT/remove-dealloc-zerouts.m.result @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +@interface Foo +@property (retain) id x; +@property (retain) id y; +@property (retain) id w; +@property (retain) id z; +@property (strong) id q; +@end + +@implementation Foo +@synthesize x; +@synthesize y; +@synthesize w; +@synthesize q; +@dynamic z; + +- (void) dealloc { + self.z = 0; +} +@end + +@interface Bar +@property (retain) Foo *a; +- (void) setA:(Foo*) val; +- (id) a; +@end + +@implementation Bar +- (void) dealloc { + [self setA:0]; // This is user-defined setter overriding synthesize, don't touch it. + self.a.x = 0; // every dealloc must zero out its own ivar. This patter is not recognized. +} +@synthesize a; +- (void) setA:(Foo*) val { } +- (id) a {return 0;} +@end diff --git a/test/ARCMT/remove-statements.m b/test/ARCMT/remove-statements.m new file mode 100644 index 0000000000..6fb08a7cc2 --- /dev/null +++ b/test/ARCMT/remove-statements.m @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface myController : NSObject +-(id)test:(id)x; +@end + +#define MY_MACRO1(x) +#define MY_MACRO2(x) (void)x + +@implementation myController +-(id) test:(id) x { + [[x retain] autorelease]; + return [[x retain] autorelease]; +} + +-(void)dealloc +{ + id array, array_already_empty; + for (id element in array_already_empty) { + } + + [array release]; + ; + + int b, b_array_already_empty; + if (b) + [array release]; + if (b_array_already_empty) ; + + if (b) { + [array release]; + } + if (b_array_already_empty) { + } + + if (b) + MY_MACRO1(array); + if (b) + MY_MACRO2(array); +} +@end diff --git a/test/ARCMT/remove-statements.m.result b/test/ARCMT/remove-statements.m.result new file mode 100644 index 0000000000..ab3390f9e8 --- /dev/null +++ b/test/ARCMT/remove-statements.m.result @@ -0,0 +1,38 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface myController : NSObject +-(id)test:(id)x; +@end + +#define MY_MACRO1(x) +#define MY_MACRO2(x) (void)x + +@implementation myController +-(id) test:(id) x { + return x; +} + +-(void)dealloc +{ + id array, array_already_empty; + for (id element in array_already_empty) { + } + + ; + + int b, b_array_already_empty; + if (b_array_already_empty) ; + + if (b_array_already_empty) { + } + + if (b) + MY_MACRO1(array); + if (b) + MY_MACRO2(array); +} +@end diff --git a/test/ARCMT/retains.m b/test/ARCMT/retains.m new file mode 100644 index 0000000000..c251f88682 --- /dev/null +++ b/test/ARCMT/retains.m @@ -0,0 +1,71 @@ +// RUN: %clang_cc1 -fobjc-nonfragile-abi -fblocks -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +id IhaveSideEffect(); + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(id)test:(id)obj; +-(id)something; +@end + +#define Something_Macro(key, comment) \ + [[Foo new] something] + +@implementation Foo + +@synthesize bar; + +-(id)something {} + +-(id)test:(id)obj { + id x = self.bar; + [x retain]; + self.bar = obj; + if (obj) + [obj retain]; + + [Something_Macro(@"foo", "@bar") retain]; + + [IhaveSideEffect() retain]; + + [[self something] retain]; + + [[self retain] something]; + + [[IhaveSideEffect() retain] autorelease]; + [[x retain] autorelease]; + // do stuff with x; + [x release]; + return [self retain]; +} + +- (id)test1 { + id x=0; + ([x retain]); + return ((([x retain]))); +} +@end + +id foo (Foo *p) { + p = [p retain]; + return ([p retain]); +} + +void block_tests(Foo *p) { + id (^B)() = ^() { + if (p) { + id (^IB)() = ^() { + id bar = [p retain]; + return bar; + }; + IB(); + } + return [p retain]; + }; +} diff --git a/test/ARCMT/retains.m.result b/test/ARCMT/retains.m.result new file mode 100644 index 0000000000..b8f625a91c --- /dev/null +++ b/test/ARCMT/retains.m.result @@ -0,0 +1,65 @@ +// RUN: %clang_cc1 -fobjc-nonfragile-abi -fblocks -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +#include "Common.h" + +id IhaveSideEffect(); + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(id)test:(id)obj; +-(id)something; +@end + +#define Something_Macro(key, comment) \ + [[Foo new] something] + +@implementation Foo + +@synthesize bar; + +-(id)something {} + +-(id)test:(id)obj { + id x = self.bar; + self.bar = obj; + + Something_Macro(@"foo", "@bar"); + + IhaveSideEffect(); + + [self something]; + + [self something]; + + IhaveSideEffect(); + // do stuff with x; + return self; +} + +- (id)test1 { + id x=0; + return (((x))); +} +@end + +id foo (Foo *p) { + p = p; + return (p); +} + +void block_tests(Foo *p) { + id (^B)() = ^() { + if (p) { + id (^IB)() = ^() { + id bar = p; + return bar; + }; + IB(); + } + return p; + }; +} diff --git a/test/ARCMT/rewrite-block-var.m b/test/ARCMT/rewrite-block-var.m new file mode 100644 index 0000000000..70da6786c0 --- /dev/null +++ b/test/ARCMT/rewrite-block-var.m @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fblocks -fsyntax-only -fobjc-arc -x objective-c %s.result -D__IPHONE_OS_VERSION_MIN_REQUIRED=50000 +// RUN: arcmt-test --args -arch x86_64 %s -D__IPHONE_OS_VERSION_MIN_REQUIRED=50000 > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface Foo : NSObject +-(Foo *)something; +@end + +void bar(void (^block)()); + +void test1(Foo *p) { + __block Foo *x = p; // __block used just to break cycle. + bar(^{ + [x something]; + }); +} + +void test2(Foo *p) { + __block Foo *x; // __block used as output variable. + bar(^{ + x = [p something]; + }); +} diff --git a/test/ARCMT/rewrite-block-var.m.result b/test/ARCMT/rewrite-block-var.m.result new file mode 100644 index 0000000000..a37e6af5c4 --- /dev/null +++ b/test/ARCMT/rewrite-block-var.m.result @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fblocks -fsyntax-only -fobjc-arc -x objective-c %s.result -D__IPHONE_OS_VERSION_MIN_REQUIRED=50000 +// RUN: arcmt-test --args -arch x86_64 %s -D__IPHONE_OS_VERSION_MIN_REQUIRED=50000 > %t +// RUN: diff %t %s.result + +#include "Common.h" + +@interface Foo : NSObject +-(Foo *)something; +@end + +void bar(void (^block)()); + +void test1(Foo *p) { + __weak Foo *x = p; // __block used just to break cycle. + bar(^{ + [x something]; + }); +} + +void test2(Foo *p) { + __block Foo *x; // __block used as output variable. + bar(^{ + x = [p something]; + }); +} diff --git a/test/ARCMT/safe-arc-assign.m b/test/ARCMT/safe-arc-assign.m new file mode 100644 index 0000000000..2b8f93e51b --- /dev/null +++ b/test/ARCMT/safe-arc-assign.m @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +void test12(id collection) { + for (id x in collection) { + x = 0; + x = 0; + } + + for (__strong id x in collection) { + x = 0; + } +} diff --git a/test/ARCMT/safe-arc-assign.m.result b/test/ARCMT/safe-arc-assign.m.result new file mode 100644 index 0000000000..eac3d62eb1 --- /dev/null +++ b/test/ARCMT/safe-arc-assign.m.result @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -arch x86_64 %s > %t +// RUN: diff %t %s.result + +void test12(id collection) { + for (__strong id x in collection) { + x = 0; + x = 0; + } + + for (__strong id x in collection) { + x = 0; + } +} diff --git a/test/ARCMT/with-working-dir.m b/test/ARCMT/with-working-dir.m new file mode 100644 index 0000000000..ebfd900378 --- /dev/null +++ b/test/ARCMT/with-working-dir.m @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -working-directory %S with-working-dir.m > %t +// RUN: diff %t %s.result + +typedef int BOOL; +id IhaveSideEffect(); + +@protocol NSObject +- (BOOL)isEqual:(id)object; +- (id)retain; +- (oneway void)release; +- (id)something; +@end + +@interface NSObject {} +@end + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(id)test:(id)obj; +@end + +@implementation Foo + +@synthesize bar; + +-(id)test:(id)obj { + id x = self.bar; + [x retain]; + self.bar = obj; + if (obj) + [obj retain]; + + [IhaveSideEffect() retain]; + + [[self something] retain]; + + [[self retain] something]; + + // do stuff with x; + [x release]; + return [self retain]; +} + +@end diff --git a/test/ARCMT/with-working-dir.m.result b/test/ARCMT/with-working-dir.m.result new file mode 100644 index 0000000000..19cdec48aa --- /dev/null +++ b/test/ARCMT/with-working-dir.m.result @@ -0,0 +1,43 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -x objective-c %s.result +// RUN: arcmt-test --args -working-directory %S with-working-dir.m > %t +// RUN: diff %t %s.result + +typedef int BOOL; +id IhaveSideEffect(); + +@protocol NSObject +- (BOOL)isEqual:(id)object; +- (id)retain; +- (oneway void)release; +- (id)something; +@end + +@interface NSObject {} +@end + +@interface Foo : NSObject { + id bar; +} +@property (retain) id bar; +-(id)test:(id)obj; +@end + +@implementation Foo + +@synthesize bar; + +-(id)test:(id)obj { + id x = self.bar; + self.bar = obj; + + IhaveSideEffect(); + + [self something]; + + [self something]; + + // do stuff with x; + return self; +} + +@end diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ae33b782d4..8f8aa097ce 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(libclang) add_subdirectory(c-index-test) +add_subdirectory(arcmt-test) add_subdirectory(driver) diff --git a/tools/Makefile b/tools/Makefile index 000293f09c..e0afc6a04a 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -8,12 +8,12 @@ ##===----------------------------------------------------------------------===## CLANG_LEVEL := .. -DIRS := driver libclang c-index-test +DIRS := driver libclang c-index-test arcmt-test include $(CLANG_LEVEL)/../../Makefile.config ifeq ($(OS), $(filter $(OS), Minix)) -DIRS := $(filter-out libclang c-index-test, $(DIRS)) +DIRS := $(filter-out libclang c-index-test, arcmt-test, $(DIRS)) endif include $(CLANG_LEVEL)/Makefile diff --git a/tools/arcmt-test/CMakeLists.txt b/tools/arcmt-test/CMakeLists.txt new file mode 100644 index 0000000000..11ea6e4390 --- /dev/null +++ b/tools/arcmt-test/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_USED_LIBS + libclang + clangARCMigrate + clangRewrite + ) + +set( LLVM_LINK_COMPONENTS + support + mc + ) + +add_clang_executable(arcmt-test + arcmt-test.cpp + ) diff --git a/tools/arcmt-test/Makefile b/tools/arcmt-test/Makefile new file mode 100644 index 0000000000..cf435bd417 --- /dev/null +++ b/tools/arcmt-test/Makefile @@ -0,0 +1,24 @@ +##===- tools/arcmt-test/Makefile ---------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +CLANG_LEVEL := ../.. + +TOOLNAME = arcmt-test + +# No plugins, optimize startup time. +TOOL_NO_EXPORTS = 1 + +# Don't install this. It is used for tests. +NO_INSTALL = 1 + +LINK_COMPONENTS := support mc +USEDLIBS = clang.a clangIndex.a clangARCMigrate.a clangRewrite.a \ + clangFrontend.a clangDriver.a clangSerialization.a clangParse.a \ + clangSema.a clangAnalysis.a clangAST.a clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile diff --git a/tools/arcmt-test/arcmt-test.cpp b/tools/arcmt-test/arcmt-test.cpp new file mode 100644 index 0000000000..a5a7125f1e --- /dev/null +++ b/tools/arcmt-test/arcmt-test.cpp @@ -0,0 +1,253 @@ +//===-- arcmt-test.cpp - ARC Migration Tool testbed -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/ARCMigrate/ARCMT.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Frontend/VerifyDiagnosticsClient.h" +#include "clang/Frontend/Utils.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Signals.h" + +using namespace clang; +using namespace arcmt; + +static llvm::cl::opt +CheckOnly("check-only", + llvm::cl::desc("Just check for issues that need to be handled manually")); + +//static llvm::cl::opt +//TestResultForARC("test-result", +//llvm::cl::desc("Test the result of transformations by parsing it in ARC mode")); + +static llvm::cl::opt +OutputTransformations("output-transformations", + llvm::cl::desc("Print the source transformations")); + +static llvm::cl::opt +VerifyDiags("verify",llvm::cl::desc("Verify emitted diagnostics and warnings")); + +static llvm::cl::opt +VerboseOpt("v", llvm::cl::desc("Enable verbose output")); + +static llvm::cl::extrahelp extraHelp( + "\nusage with compiler args: arcmt-test [options] --args [compiler flags]\n"); + +// This function isn't referenced outside its translation unit, but it +// can't use the "static" keyword because its address is used for +// GetMainExecutable (since some platforms don't support taking the +// address of main, and some platforms can't implement GetMainExecutable +// without being given the address of a function in the main executable). +llvm::sys::Path GetExecutablePath(const char *Argv0) { + // This just needs to be some symbol in the binary; C++ doesn't + // allow taking the address of ::main however. + void *MainAddr = (void*) (intptr_t) GetExecutablePath; + return llvm::sys::Path::GetMainExecutable(Argv0, MainAddr); +} + +static void printSourceLocation(SourceLocation loc, ASTContext &Ctx, + llvm::raw_ostream &OS); +static void printSourceRange(CharSourceRange range, ASTContext &Ctx, + llvm::raw_ostream &OS); + +namespace { + +class PrintTransforms : public MigrationProcess::RewriteListener { + ASTContext *Ctx; + llvm::raw_ostream &OS; + +public: + PrintTransforms(llvm::raw_ostream &OS) + : Ctx(0), OS(OS) { } + + virtual void start(ASTContext &ctx) { Ctx = &ctx; } + virtual void finish() { Ctx = 0; } + + virtual void insert(SourceLocation loc, llvm::StringRef text) { + assert(Ctx); + OS << "Insert: "; + printSourceLocation(loc, *Ctx, OS); + OS << " \"" << text << "\"\n"; + } + + virtual void remove(CharSourceRange range) { + assert(Ctx); + OS << "Remove: "; + printSourceRange(range, *Ctx, OS); + OS << '\n'; + } +}; + +} // anonymous namespace + +static bool checkForMigration(llvm::StringRef resourcesPath, + llvm::ArrayRef Args) { + DiagnosticClient *DiagClient = + new TextDiagnosticPrinter(llvm::errs(), DiagnosticOptions()); + llvm::IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + llvm::IntrusiveRefCntPtr Diags(new Diagnostic(DiagID, DiagClient)); + // Chain in -verify checker, if requested. + VerifyDiagnosticsClient *verifyDiag = 0; + if (VerifyDiags) { + verifyDiag = new VerifyDiagnosticsClient(*Diags, Diags->takeClient()); + Diags->setClient(verifyDiag); + } + + llvm::OwningPtr CI; + CI.reset(clang::createInvocationFromCommandLine(Args, Diags)); + if (!CI) + return true; + + if (CI->getFrontendOpts().Inputs.empty()) { + llvm::errs() << "error: no input files\n"; + return true; + } + + if (!CI->getLangOpts().ObjC1) + return false; + + return arcmt::checkForManualIssues(*CI, + CI->getFrontendOpts().Inputs[0].second, + CI->getFrontendOpts().Inputs[0].first, + Diags->getClient()); +} + +static void printResult(FileRemapper &remapper, llvm::raw_ostream &OS) { + CompilerInvocation CI; + remapper.applyMappings(CI); + PreprocessorOptions &PPOpts = CI.getPreprocessorOpts(); + // The changed files will be in memory buffers, print them. + for (unsigned i = 0, e = PPOpts.RemappedFileBuffers.size(); i != e; ++i) { + const llvm::MemoryBuffer *mem = PPOpts.RemappedFileBuffers[i].second; + OS << mem->getBuffer(); + } +} + +static bool performTransformations(llvm::StringRef resourcesPath, + llvm::ArrayRef Args) { + // Check first. + if (checkForMigration(resourcesPath, Args)) + return true; + + DiagnosticClient *DiagClient = + new TextDiagnosticPrinter(llvm::errs(), DiagnosticOptions()); + llvm::IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + llvm::IntrusiveRefCntPtr TopDiags(new Diagnostic(DiagID, DiagClient)); + + llvm::OwningPtr origCI; + origCI.reset(clang::createInvocationFromCommandLine(Args, TopDiags)); + if (!origCI) + return true; + + if (origCI->getFrontendOpts().Inputs.empty()) { + llvm::errs() << "error: no input files\n"; + return true; + } + + if (!origCI->getLangOpts().ObjC1) + return false; + + MigrationProcess migration(*origCI, DiagClient); + + std::vector transforms = arcmt::getAllTransformations(); + assert(!transforms.empty()); + + llvm::OwningPtr transformPrinter; + if (OutputTransformations) + transformPrinter.reset(new PrintTransforms(llvm::outs())); + + for (unsigned i=0, e = transforms.size(); i != e; ++i) { + bool err = migration.applyTransform(transforms[i], transformPrinter.get()); + if (err) return true; + + if (VerboseOpt) { + if (i == e-1) + llvm::errs() << "\n##### FINAL RESULT #####\n"; + else + llvm::errs() << "\n##### OUTPUT AFTER "<< i+1 <<". TRANSFORMATION #####\n"; + printResult(migration.getRemapper(), llvm::errs()); + llvm::errs() << "\n##########################\n\n"; + } + } + + if (!OutputTransformations) + printResult(migration.getRemapper(), llvm::outs()); + + // FIXME: TestResultForARC + + return false; +} + +//===----------------------------------------------------------------------===// +// Misc. functions. +//===----------------------------------------------------------------------===// + +static void printSourceLocation(SourceLocation loc, ASTContext &Ctx, + llvm::raw_ostream &OS) { + SourceManager &SM = Ctx.getSourceManager(); + PresumedLoc PL = SM.getPresumedLoc(loc); + + OS << llvm::sys::path::filename(PL.getFilename()); + OS << ":" << PL.getLine() << ":" + << PL.getColumn(); +} + +static void printSourceRange(CharSourceRange range, ASTContext &Ctx, + llvm::raw_ostream &OS) { + SourceManager &SM = Ctx.getSourceManager(); + const LangOptions &langOpts = Ctx.getLangOptions(); + + PresumedLoc PL = SM.getPresumedLoc(range.getBegin()); + + OS << llvm::sys::path::filename(PL.getFilename()); + OS << " [" << PL.getLine() << ":" + << PL.getColumn(); + OS << " - "; + + SourceLocation end = range.getEnd(); + PL = SM.getPresumedLoc(end); + + unsigned endCol = PL.getColumn() - 1; + if (!range.isTokenRange()) + endCol += Lexer::MeasureTokenLength(end, SM, langOpts); + OS << PL.getLine() << ":" << endCol << "]"; +} + +//===----------------------------------------------------------------------===// +// Command line processing. +//===----------------------------------------------------------------------===// + +int main(int argc, const char **argv) { + using llvm::StringRef; + void *MainAddr = (void*) (intptr_t) GetExecutablePath; + llvm::sys::PrintStackTraceOnErrorSignal(); + + std::string + resourcesPath = CompilerInvocation::GetResourcesPath(argv[0], MainAddr); + + int optargc = 0; + for (; optargc != argc; ++optargc) { + if (StringRef(argv[optargc]) == "--args") + break; + } + llvm::cl::ParseCommandLineOptions(optargc, const_cast(argv), "arcmt-test"); + + if (optargc == argc) { + llvm::cl::PrintHelpMessage(); + return 1; + } + + llvm::ArrayRef Args(argv+optargc+1, argc-optargc-1); + + if (CheckOnly) + return checkForMigration(resourcesPath, Args); + + return performTransformations(resourcesPath, Args); +} diff --git a/tools/driver/CMakeLists.txt b/tools/driver/CMakeLists.txt index 0c41490175..e6d0f1ac37 100644 --- a/tools/driver/CMakeLists.txt +++ b/tools/driver/CMakeLists.txt @@ -9,6 +9,7 @@ set( LLVM_USED_LIBS clangIndex clangLex clangParse + clangARCMigrate clangRewrite clangSema clangSerialization diff --git a/tools/driver/Makefile b/tools/driver/Makefile index abe70983df..1ba8bc24d1 100644 --- a/tools/driver/Makefile +++ b/tools/driver/Makefile @@ -41,7 +41,7 @@ USEDLIBS = clangFrontendTool.a clangFrontend.a clangDriver.a \ clangSerialization.a clangCodeGen.a clangParse.a clangSema.a \ clangStaticAnalyzerFrontend.a clangStaticAnalyzerCheckers.a \ clangStaticAnalyzerCore.a \ - clangAnalysis.a clangIndex.a clangRewrite.a \ + clangAnalysis.a clangIndex.a clangARCMigrate.a clangRewrite.a \ clangAST.a clangLex.a clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/tools/scan-build/ccc-analyzer b/tools/scan-build/ccc-analyzer index 7793a8db49..5697b214a2 100755 --- a/tools/scan-build/ccc-analyzer +++ b/tools/scan-build/ccc-analyzer @@ -365,11 +365,9 @@ my %LangMap = ( 'cp' => 'c++', 'cpp' => 'c++', 'cc' => 'c++', - 'ii' => 'c++', 'i' => 'c-cpp-output', 'm' => 'objective-c', - 'mi' => 'objective-c-cpp-output', - 'mm' => 'objective-c++' + 'mi' => 'objective-c-cpp-output' ); my %UniqueOptions = ( @@ -382,11 +380,14 @@ my %UniqueOptions = ( my %LangsAccepted = ( "objective-c" => 1, - "c" => 1, - "c++" => 1, - "objective-c++" => 1 + "c" => 1 ); +if (defined $ENV{'CCC_ANALYZER_CPLUSPLUS'}) { + $LangsAccepted{"c++"} = 1; + $LangsAccepted{"objective-c++"} = 1; +} + ##----------------------------------------------------------------------------## # Main Logic. ##----------------------------------------------------------------------------## @@ -620,9 +621,9 @@ if ($Action eq 'compile' or $Action eq 'link') { push @AnalyzeArgs, "-analyzer-constraints=$ConstraintsModel"; } - if (defined $Analyses) { - push @AnalyzeArgs, split '\s+', $Analyses; - } +# if (defined $Analyses) { +# push @AnalyzeArgs, split '\s+', $Analyses; +# } if (defined $OutputFormat) { push @AnalyzeArgs, "-analyzer-output=" . $OutputFormat; diff --git a/unittests/Frontend/Makefile b/unittests/Frontend/Makefile index 4d9937f517..67c2f200de 100644 --- a/unittests/Frontend/Makefile +++ b/unittests/Frontend/Makefile @@ -13,7 +13,7 @@ LINK_COMPONENTS := support mc USEDLIBS = clangFrontendTool.a clangFrontend.a clangDriver.a \ clangSerialization.a clangCodeGen.a clangParse.a clangSema.a \ clangStaticAnalyzerCheckers.a clangStaticAnalyzerCore.a \ - clangAnalysis.a clangIndex.a clangRewrite.a \ + clangAnalysis.a clangIndex.a clangARCMigrate.a clangRewrite.a \ clangAST.a clangLex.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile