From 489921b003affda4eb030dea61fbb78e546d404c Mon Sep 17 00:00:00 2001 From: Frederic Riss Date: Thu, 5 Nov 2015 17:54:40 -0800 Subject: [PATCH 001/585] Apply Swift-related changes to the swift-clang repo apple-llvm-split-commit: 84b5a21c31cb5b0d7d958a478bc01964939b6952 apple-llvm-split-dir: clang/ --- .../include/clang/APINotes/APINotesManager.h | 91 ++ clang/include/clang/APINotes/APINotesReader.h | 144 ++ clang/include/clang/APINotes/APINotesWriter.h | 98 ++ .../clang/APINotes/APINotesYAMLCompiler.h | 56 + clang/include/clang/APINotes/Types.h | 409 ++++++ clang/include/clang/AST/DeclBase.h | 9 +- clang/include/clang/Basic/Attr.td | 46 + clang/include/clang/Basic/AttrDocs.td | 27 + .../clang/Basic/DiagnosticCommonKinds.td | 5 + .../clang/Basic/DiagnosticFrontendKinds.td | 4 + .../clang/Basic/DiagnosticSemaKinds.td | 25 + clang/include/clang/Basic/FileSystemOptions.h | 3 + clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Basic/SourceMgrAdapter.h | 83 ++ clang/include/clang/Driver/Options.td | 8 + clang/include/clang/Sema/Sema.h | 21 +- clang/lib/APINotes/APINotesFormat.h | 239 +++ clang/lib/APINotes/APINotesManager.cpp | 453 ++++++ clang/lib/APINotes/APINotesReader.cpp | 1297 +++++++++++++++++ clang/lib/APINotes/APINotesWriter.cpp | 851 +++++++++++ clang/lib/APINotes/APINotesYAMLCompiler.cpp | 884 +++++++++++ clang/lib/APINotes/CMakeLists.txt | 15 + clang/lib/APINotes/Makefile | 18 + clang/lib/APINotes/Types.cpp | 55 + clang/lib/AST/DeclBase.cpp | 22 +- clang/lib/Basic/CMakeLists.txt | 1 + clang/lib/Basic/SourceMgrAdapter.cpp | 137 ++ clang/lib/CMakeLists.txt | 1 + clang/lib/CodeGen/CGObjCGNU.cpp | 5 +- clang/lib/CodeGen/CGObjCMac.cpp | 67 +- clang/lib/CodeGen/CGObjCRuntime.h | 5 +- clang/lib/CodeGen/CodeGenModule.cpp | 4 +- clang/lib/Driver/Job.cpp | 3 +- clang/lib/Driver/Tools.cpp | 27 + clang/lib/Frontend/CompilerInvocation.cpp | 9 + clang/lib/Lex/PPMacroExpansion.cpp | 1 + clang/lib/Makefile | 6 +- clang/lib/Parse/ParseCXXInlineMethods.cpp | 1 + clang/lib/Parse/ParseDeclCXX.cpp | 4 +- clang/lib/Parse/ParseObjc.cpp | 2 + clang/lib/Sema/CMakeLists.txt | 2 + clang/lib/Sema/Sema.cpp | 2 +- clang/lib/Sema/SemaAPINotes.cpp | 359 +++++ clang/lib/Sema/SemaDecl.cpp | 11 + clang/lib/Sema/SemaDeclAttr.cpp | 281 ++++ clang/lib/Sema/SemaDeclCXX.cpp | 3 + clang/lib/Sema/SemaDeclObjC.cpp | 33 +- clang/lib/Sema/SemaTemplate.cpp | 5 + clang/lib/Sema/SemaType.cpp | 85 +- .../Inputs/BrokenHeaders/APINotes.apinotes | 4 + .../Inputs/BrokenHeaders/SomeBrokenLib.h | 6 + .../Inputs/BrokenHeaders2/APINotes.apinotes | 7 + .../Inputs/BrokenHeaders2/SomeBrokenLib.h | 6 + .../APINotes/SomeKit.apinotes | 24 + .../APINotes/SomeKit_private.apinotes | 15 + .../SomeKit.framework/Headers/SomeKit.h | 23 + .../Headers/SomeKitForNullAnnotation.h | 55 + .../PrivateHeaders/SomeKit_Private.h | 16 + .../SomeKit_PrivateForNullAnnotation.h | 17 + .../APINotes/Inputs/Headers/APINotes.apinotes | 17 + .../test/APINotes/Inputs/Headers/HeaderLib.h | 13 + .../APINotes/Inputs/os-availability.apinotes | 53 + clang/test/APINotes/Inputs/roundtrip.apinotes | 79 + clang/test/APINotes/availability.m | 29 + clang/test/APINotes/cache.m | 33 + clang/test/APINotes/cache_pruning.m | 49 + clang/test/APINotes/nullability.c | 14 + clang/test/APINotes/nullability.m | 13 + clang/test/APINotes/objc_designated_inits.m | 16 + clang/test/APINotes/yaml-convert-diags.c | 6 + clang/test/APINotes/yaml-os-availability.c | 31 + clang/test/APINotes/yaml-parse-diags.c | 6 + clang/test/APINotes/yaml-reader-errors.c | 65 + clang/test/APINotes/yaml-reader-test.c | 102 ++ clang/test/APINotes/yaml-roundtrip.c | 10 + clang/test/Misc/warning-flags.c | 3 +- clang/test/Sema/attr-availability.c | 15 + clang/test/Sema/attr-noescape.c | 12 + clang/test/SemaObjC/attr-swift.m | 158 ++ .../SemaObjC/subclassing-restricted-attr.m | 23 + clang/tools/arcmt-test/Makefile | 2 +- clang/tools/c-arcmt-test/Makefile | 3 +- clang/tools/c-index-test/Makefile | 2 +- clang/tools/clang-check/CMakeLists.txt | 1 + clang/tools/clang-check/Makefile | 2 +- clang/tools/clang-format/Makefile | 2 +- clang/tools/diagtool/Makefile | 2 +- clang/tools/driver/CMakeLists.txt | 2 + clang/tools/driver/Makefile | 3 +- clang/tools/driver/apinotes_main.cpp | 149 ++ clang/tools/driver/driver.cpp | 4 + clang/tools/libclang/CMakeLists.txt | 1 + clang/tools/libclang/Makefile | 2 +- clang/unittests/AST/DeclTest.cpp | 51 + clang/unittests/AST/Makefile | 3 +- clang/unittests/ASTMatchers/Dynamic/Makefile | 3 +- clang/unittests/ASTMatchers/Makefile | 3 +- clang/unittests/CodeGen/Makefile | 2 +- clang/unittests/Format/Makefile | 2 +- clang/unittests/Frontend/Makefile | 2 +- clang/unittests/Lex/CMakeLists.txt | 1 + clang/unittests/Lex/Makefile | 2 +- clang/unittests/Sema/Makefile | 3 +- clang/unittests/Tooling/Makefile | 2 +- clang/unittests/libclang/Makefile | 2 +- 105 files changed, 6983 insertions(+), 106 deletions(-) create mode 100644 clang/include/clang/APINotes/APINotesManager.h create mode 100644 clang/include/clang/APINotes/APINotesReader.h create mode 100644 clang/include/clang/APINotes/APINotesWriter.h create mode 100644 clang/include/clang/APINotes/APINotesYAMLCompiler.h create mode 100644 clang/include/clang/APINotes/Types.h create mode 100644 clang/include/clang/Basic/SourceMgrAdapter.h create mode 100644 clang/lib/APINotes/APINotesFormat.h create mode 100644 clang/lib/APINotes/APINotesManager.cpp create mode 100644 clang/lib/APINotes/APINotesReader.cpp create mode 100644 clang/lib/APINotes/APINotesWriter.cpp create mode 100644 clang/lib/APINotes/APINotesYAMLCompiler.cpp create mode 100644 clang/lib/APINotes/CMakeLists.txt create mode 100644 clang/lib/APINotes/Makefile create mode 100644 clang/lib/APINotes/Types.cpp create mode 100644 clang/lib/Basic/SourceMgrAdapter.cpp create mode 100644 clang/lib/Sema/SemaAPINotes.cpp create mode 100644 clang/test/APINotes/Inputs/BrokenHeaders/APINotes.apinotes create mode 100644 clang/test/APINotes/Inputs/BrokenHeaders/SomeBrokenLib.h create mode 100644 clang/test/APINotes/Inputs/BrokenHeaders2/APINotes.apinotes create mode 100644 clang/test/APINotes/Inputs/BrokenHeaders2/SomeBrokenLib.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit_private.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKitForNullAnnotation.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_Private.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_PrivateForNullAnnotation.h create mode 100644 clang/test/APINotes/Inputs/Headers/APINotes.apinotes create mode 100644 clang/test/APINotes/Inputs/Headers/HeaderLib.h create mode 100644 clang/test/APINotes/Inputs/os-availability.apinotes create mode 100644 clang/test/APINotes/Inputs/roundtrip.apinotes create mode 100644 clang/test/APINotes/availability.m create mode 100644 clang/test/APINotes/cache.m create mode 100644 clang/test/APINotes/cache_pruning.m create mode 100644 clang/test/APINotes/nullability.c create mode 100644 clang/test/APINotes/nullability.m create mode 100644 clang/test/APINotes/objc_designated_inits.m create mode 100644 clang/test/APINotes/yaml-convert-diags.c create mode 100644 clang/test/APINotes/yaml-os-availability.c create mode 100644 clang/test/APINotes/yaml-parse-diags.c create mode 100644 clang/test/APINotes/yaml-reader-errors.c create mode 100644 clang/test/APINotes/yaml-reader-test.c create mode 100644 clang/test/APINotes/yaml-roundtrip.c create mode 100644 clang/test/Sema/attr-noescape.c create mode 100644 clang/test/SemaObjC/attr-swift.m create mode 100644 clang/test/SemaObjC/subclassing-restricted-attr.m create mode 100644 clang/tools/driver/apinotes_main.cpp diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h new file mode 100644 index 0000000000000..503b36536e621 --- /dev/null +++ b/clang/include/clang/APINotes/APINotesManager.h @@ -0,0 +1,91 @@ +//===--- APINotesManager.h - Manage API Notes Files -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the HeaderSearch interface. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_APINOTES_APINOTESMANAGER_H +#define LLVM_CLANG_APINOTES_APINOTESMANAGER_H + +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { + +class DirectoryEntry; +class FileEntry; +class SourceManager; + +namespace api_notes { + +class APINotesReader; + +/// The API notes manager helps find API notes associated with declarations. +/// +/// API notes are externally-provided annotations for declarations that can +/// introduce new attributes (covering availability, nullability of +/// parameters/results, and so on) for specific declarations without directly +/// modifying the headers that contain those declarations. +/// +/// The API notes manager is responsible for finding and loading the +/// external API notes files that correspond to a given header. Its primary +/// operation is \c findAPINotes(), which finds the API notes reader that +/// provides information about the declarations at that location. +class APINotesManager { + typedef llvm::PointerUnion + ReaderEntry; + + SourceManager &SourceMgr; + + /// Whether we have already pruned the API notes cache. + bool PrunedCache; + + /// A mapping from header file directories to the API notes reader for + /// that directory, or a redirection to another directory entry that may + /// have more information, or NULL to indicate that there is no API notes + /// reader for this directory. + llvm::DenseMap Readers; + + /// Load the given API notes file for the given header directory. + /// + /// \param HeaderDir The directory at which we + /// + /// \returns true if an error occurred. + bool loadAPINotes(const DirectoryEntry *HeaderDir, + const FileEntry *APINotesFile); + + /// Attempt to load API notes for the given framework. + /// + /// \param FrameworkPath The path to the framework. + /// \param Public Whether to load the public API notes. Otherwise, attempt + /// to load the private API notes. + /// + /// \returns the header directory entry (e.g., for Headers or PrivateHeaders) + /// for which the API notes were successfully loaded, or NULL if API notes + /// could not be loaded for any reason. + const DirectoryEntry *loadFrameworkAPINotes(llvm::StringRef FrameworkPath, + llvm::StringRef FrameworkName, + bool Public); + +public: + APINotesManager(SourceManager &SourceMgr); + ~APINotesManager(); + + /// Find the API notes reader that corresponds to the given source location. + APINotesReader *findAPINotes(SourceLocation Loc); +}; + +} // end namespace api_notes +} // end namespace clang + +#endif diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h new file mode 100644 index 0000000000000..77b5f16bb42f4 --- /dev/null +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -0,0 +1,144 @@ +//===--- APINotesReader.h - API Notes Reader ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the \c APINotesReader class that reads source +// API notes data providing additional information about source code as +// a separate input, such as the non-nil/nilable annotations for +// method parameters. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_API_NOTES_READER_H +#define LLVM_CLANG_API_NOTES_READER_H + +#include "clang/APINotes/Types.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/MemoryBuffer.h" +#include + +namespace clang { +namespace api_notes { + +/// A class that reads API notes data from a binary file that was written by +/// the \c APINotesWriter. +class APINotesReader { + class Implementation; + + Implementation &Impl; + + APINotesReader(std::unique_ptr inputBuffer, bool &failed); + +public: + /// Create a new API notes reader from the given member buffer, which + /// contains the contents of a binary API notes file. + /// + /// \returns the new API notes reader, or null if an error occurred. + static std::unique_ptr + get(std::unique_ptr inputBuffer); + + ~APINotesReader(); + + APINotesReader(const APINotesReader &) = delete; + APINotesReader &operator=(const APINotesReader &) = delete; + + /// Retrieve the name of the module for which this reader is providing API + /// notes. + StringRef getModuleName() const; + + /// Look for information regarding the given Objective-C class. + /// + /// \param name The name of the class we're looking for. + /// + /// \returns The ID and information about the class, if known. + Optional> + lookupObjCClass(StringRef name); + + /// Look for information regarding the given Objective-C protocol. + /// + /// \param name The name of the protocol we're looking for. + /// + /// \returns The ID and information about the protocol, if known. + Optional> + lookupObjCProtocol(StringRef name); + + /// Look for information regarding the given Objective-C property in + /// the given context. + /// + /// \param contextID The ID that references the context we are looking for. + /// \param name The name of the property we're looking for. + /// + /// \returns Information about the property, if known. + Optional lookupObjCProperty(ContextID contextID, + StringRef name); + + /// Look for information regarding the given Objective-C method in + /// the given context. + /// + /// \param contextID The ID that references the context we are looking for. + /// \param selector The selector naming the method we're looking for. + /// \param isInstanceMethod Whether we are looking for an instance method. + /// + /// \returns Information about the method, if known. + Optional lookupObjCMethod(ContextID contextID, + ObjCSelectorRef selector, + bool isInstanceMethod); + + /// Look for information regarding the given global variable. + /// + /// \param name The name of the global variable. + /// + /// \returns information about the global variable, if known. + Optional lookupGlobalVariable(StringRef name); + + /// Look for information regarding the given global function. + /// + /// \param name The name of the global function. + /// + /// \returns information about the global function, if known. + Optional lookupGlobalFunction(StringRef name); + + /// Visitor used when walking the contents of the API notes file. + class Visitor { + public: + virtual ~Visitor(); + + /// Visit an Objective-C class. + virtual void visitObjCClass(ContextID contextID, StringRef name, + const ObjCContextInfo &info); + + /// Visit an Objective-C protocol. + virtual void visitObjCProtocol(ContextID contextID, StringRef name, + const ObjCContextInfo &info); + + /// Visit an Objective-C method. + virtual void visitObjCMethod(ContextID contextID, StringRef selector, + bool isInstanceMethod, + const ObjCMethodInfo &info); + + /// Visit an Objective-C property. + virtual void visitObjCProperty(ContextID contextID, StringRef name, + const ObjCPropertyInfo &info); + + /// Visit a global variable. + virtual void visitGlobalVariable(StringRef name, + const GlobalVariableInfo &info); + + /// Visit a global function. + virtual void visitGlobalFunction(StringRef name, + const GlobalFunctionInfo &info); + }; + + /// Visit the contents of the API notes file, passing each entity to the + /// given visitor. + void visit(Visitor &visitor); +}; + +} // end namespace api_notes +} // end namespace clang + +#endif // LLVM_CLANG_API_NOTES_READER_H diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h new file mode 100644 index 0000000000000..dca0773aa28f8 --- /dev/null +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -0,0 +1,98 @@ +//===--- APINotesWriter.h - API Notes Writer ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the \c APINotesWriter class that writes out source +// API notes data providing additional information about source code as +// a separate input, such as the non-nil/nilable annotations for +// method parameters. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_API_NOTES_WRITER_H +#define LLVM_CLANG_API_NOTES_WRITER_H + +#include "clang/APINotes/Types.h" + +namespace llvm { + class raw_ostream; +} + +namespace clang { +namespace api_notes { + +/// A class that writes API notes data to a binary representation that can be +/// read by the \c APINotesReader. +class APINotesWriter { + class Implementation; + Implementation &Impl; + +public: + /// Create a new API notes writer with the given module name. + APINotesWriter(StringRef moduleName); + ~APINotesWriter(); + + APINotesWriter(const APINotesWriter &) = delete; + APINotesWriter &operator=(const APINotesWriter &) = delete; + + /// Write the API notes data to the given stream. + void writeToStream(llvm::raw_ostream &os); + + /// Add information about a specific Objective-C class. + /// + /// \param name The name of this class. + /// \param info Information about this class. + /// + /// \returns the ID of the class, which can be used to add properties and + /// methods to the class. + ContextID addObjCClass(StringRef name, const ObjCContextInfo &info); + + /// Add information about a specific Objective-C protocol. + /// + /// \param name The name of this protocol. + /// \param info Information about this protocol. + /// + /// \returns the ID of the protocol, which can be used to add properties and + /// methods to the protocol. + ContextID addObjCProtocol(StringRef name, const ObjCContextInfo &info); + + /// Add information about a specific Objective-C property. + /// + /// \param contextID The context in which this property resides. + /// \param name The name of this property. + /// \param info Information about this property. + void addObjCProperty(ContextID contextID, StringRef name, + const ObjCPropertyInfo &info); + + /// Add information about a specific Objective-C method. + /// + /// \param contextID The context in which this method resides. + /// \param selector The selector that names this method. + /// \param isInstanceMethod Whether this method is an instance method + /// (vs. a class method). + /// \param info Information about this method. + void addObjCMethod(ContextID contextID, ObjCSelectorRef selector, + bool isInstanceMethod, const ObjCMethodInfo &info); + + /// Add information about a global variable. + /// + /// \param name The name of this global variable. + /// \param info Information about this global variable. + void addGlobalVariable(StringRef name, const GlobalVariableInfo &info); + + /// Add information about a global function. + /// + /// \param name The name of this global function. + /// \param info Information about this global function. + void addGlobalFunction(StringRef name, const GlobalFunctionInfo &info); +}; + +} // end namespace api_notes +} // end namespace clang + +#endif // LLVM_CLANG_API_NOTES_WRITER_H + diff --git a/clang/include/clang/APINotes/APINotesYAMLCompiler.h b/clang/include/clang/APINotes/APINotesYAMLCompiler.h new file mode 100644 index 0000000000000..e897b5cf90496 --- /dev/null +++ b/clang/include/clang/APINotes/APINotesYAMLCompiler.h @@ -0,0 +1,56 @@ +//=== APINotesYAMLCompiler.h - API Notes YAML to binary compiler *- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file reads sidecar API notes specified in YAML format. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_API_NOTES_YAML_COMPILER_H +#define LLVM_CLANG_API_NOTES_YAML_COMPILER_H +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/SourceMgr.h" +#include + +namespace llvm { + class raw_ostream; + class MemoryBuffer; +} + +namespace clang { +namespace api_notes { + + enum class ActionType { + None, + YAMLToBinary, + BinaryToYAML, + Dump, + }; + + enum class OSType { + OSX, + IOS, + Absent + }; + + /// Converts API notes from YAML format to binary format. + bool compileAPINotes(llvm::StringRef yamlInput, + llvm::raw_ostream &os, + OSType targetOS, + llvm::SourceMgr::DiagHandlerTy diagHandler = nullptr, + void *diagHandlerCtxt = nullptr); + + bool parseAndDumpAPINotes(llvm::StringRef yamlInput); + + /// Converts API notes from the compiled binary format to the YAML format. + bool decompileAPINotes(std::unique_ptr input, + llvm::raw_ostream &os); +} // end namespace api_notes +} // end namespace clang + +#endif // LLVM_CLANG_API_NOTES_YAML_COMPILER_H diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h new file mode 100644 index 0000000000000..8173bf4f3a25b --- /dev/null +++ b/clang/include/clang/APINotes/Types.h @@ -0,0 +1,409 @@ +//===--- Types.h - API Notes Data Types --------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines data types used in the representation of API notes data. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_API_NOTES_TYPES_H +#define LLVM_CLANG_API_NOTES_TYPES_H +#include "clang/Basic/Specifiers.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace llvm { + class raw_ostream; +} + +namespace clang { +namespace api_notes { + +/// The file extension used for the source representation of API notes. +static const char SOURCE_APINOTES_EXTENSION[] = "apinotes"; + +/// The file extension used for the binary representation of API notes. +static const char BINARY_APINOTES_EXTENSION[] = "apinotesc"; + +using llvm::ArrayRef; +using llvm::StringRef; +using llvm::Optional; +using llvm::None; + +/// Describes whether to classify a factory method as an initializer. +enum class FactoryAsInitKind { + /// Infer based on name and type (the default). + Infer, + /// Treat as a class method. + AsClassMethod, + /// Treat as an initializer. + AsInitializer +}; + +/// Opaque context ID used to refer to an Objective-C class or protocol. +class ContextID { +public: + unsigned Value; + + explicit ContextID(unsigned value) : Value(value) { } +}; + +/// Describes API notes data for any entity. +/// +/// This is used as the base of +class CommonEntityInfo { +public: + /// Message to use when this entity is unavailable. + std::string UnavailableMsg; + + /// Whether this entity is marked unavailable. + unsigned Unavailable : 1; + + CommonEntityInfo() : Unavailable(0) { } + + friend bool operator==(const CommonEntityInfo &lhs, + const CommonEntityInfo &rhs) { + return lhs.UnavailableMsg == rhs.UnavailableMsg && + lhs.Unavailable == rhs.Unavailable; + } + + friend bool operator!=(const CommonEntityInfo &lhs, + const CommonEntityInfo &rhs) { + return !(lhs == rhs); + } + + friend CommonEntityInfo &operator|=(CommonEntityInfo &lhs, + const CommonEntityInfo &rhs) { + // Merge unavailability. + if (rhs.Unavailable) { + lhs.Unavailable = true; + if (rhs.UnavailableMsg.length() != 0 && + lhs.UnavailableMsg.length() == 0) { + lhs.UnavailableMsg = rhs.UnavailableMsg; + } + } + + return lhs; + } + +}; + +/// Describes API notes data for an Objective-C class or protocol. +class ObjCContextInfo : public CommonEntityInfo { + /// Whether this class has a default nullability. + unsigned HasDefaultNullability : 1; + + /// The default nullability. + unsigned DefaultNullability : 2; + + /// Whether this class has designated initializers recorded. + unsigned HasDesignatedInits : 1; + +public: + ObjCContextInfo() + : CommonEntityInfo(), + HasDefaultNullability(0), + DefaultNullability(0), + HasDesignatedInits(0) + { } + + /// Determine the default nullability for properties and methods of this + /// class. + /// + /// \returns the default nullability, if implied, or None if there is no + Optional getDefaultNullability() const { + if (HasDefaultNullability) + return static_cast(DefaultNullability); + + return None; + } + + /// Set the default nullability for properties and methods of this class. + void setDefaultNullability(NullabilityKind kind) { + HasDefaultNullability = true; + DefaultNullability = static_cast(kind); + } + + bool hasDesignatedInits() const { return HasDesignatedInits; } + void setHasDesignatedInits(bool value) { HasDesignatedInits = value; } + + /// Strip off any information within the class information structure that is + /// module-local, such as 'audited' flags. + void stripModuleLocalInfo() { + HasDefaultNullability = false; + DefaultNullability = 0; + } + + friend bool operator==(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.HasDefaultNullability == rhs.HasDefaultNullability && + lhs.DefaultNullability == rhs.DefaultNullability && + lhs.HasDesignatedInits == rhs.HasDesignatedInits; + } + + friend bool operator!=(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { + return !(lhs == rhs); + } + + friend ObjCContextInfo &operator|=(ObjCContextInfo &lhs, + const ObjCContextInfo &rhs) { + // Merge inherited info. + static_cast(lhs) |= rhs; + + // Merge nullability. + if (!lhs.getDefaultNullability()) { + if (auto nullable = rhs.getDefaultNullability()) { + lhs.setDefaultNullability(*nullable); + } + } + + lhs.HasDesignatedInits |= rhs.HasDesignatedInits; + return lhs; + } + + void dump(llvm::raw_ostream &os); +}; + +/// API notes for a variable/property. +class VariableInfo : public CommonEntityInfo { + /// Whether this property has been audited for nullability. + unsigned NullabilityAudited : 1; + + /// The kind of nullability for this property. Only valid if the nullability + /// has been audited. + unsigned Nullable : 2; + +public: + VariableInfo() + : CommonEntityInfo(), + NullabilityAudited(false), + Nullable(0) { } + + Optional getNullability() const { + if (NullabilityAudited) + return static_cast(Nullable); + + return None; + } + + void setNullabilityAudited(NullabilityKind kind) { + NullabilityAudited = true; + Nullable = static_cast(kind); + } + + + friend bool operator==(const VariableInfo &lhs, const VariableInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.NullabilityAudited == rhs.NullabilityAudited && + lhs.Nullable == rhs.Nullable; + } + + friend bool operator!=(const VariableInfo &lhs, const VariableInfo &rhs) { + return !(lhs == rhs); + } + +}; + +/// Describes API notes data for an Objective-C property. +class ObjCPropertyInfo : public VariableInfo { +public: + ObjCPropertyInfo() : VariableInfo() { } + + /// Merge class-wide information into the given property. + friend ObjCPropertyInfo &operator|=(ObjCPropertyInfo &lhs, + const ObjCContextInfo &rhs) { + // Merge nullability. + if (!lhs.getNullability()) { + if (auto nullable = rhs.getDefaultNullability()) { + lhs.setNullabilityAudited(*nullable); + } + } + + return lhs; + } +}; + +/// A temporary reference to an Objective-C selector, suitable for +/// referencing selector data on the stack. +/// +/// Instances of this struct do not store references to any of the +/// data they contain; it is up to the user to ensure that the data +/// referenced by the identifier list persists. +struct ObjCSelectorRef { + unsigned NumPieces; + ArrayRef Identifiers; +}; + +/// API notes for a function or method. +class FunctionInfo : public CommonEntityInfo { +private: + static unsigned const NullabilityKindMask = 0x3; + static unsigned const NullabilityKindSize = 2; + +public: + /// Whether the signature has been audited with respect to nullability. + /// If yes, we consider all types to be non-nullable unless otherwise noted. + /// If this flag is not set, the pointer types are considered to have + /// unknown nullability. + unsigned NullabilityAudited : 1; + + /// Number of types whose nullability is encoded with the NullabilityPayload. + unsigned NumAdjustedNullable : 8; + + /// Stores the nullability of the return type and the parameters. + // NullabilityKindSize bits are used to encode the nullability. The info + // about the return type is stored at position 0, followed by the nullability + // of the parameters. + uint64_t NullabilityPayload = 0; + + FunctionInfo() + : CommonEntityInfo(), + NullabilityAudited(false), + NumAdjustedNullable(0) { } + + static unsigned getMaxNullabilityIndex() { + return ((sizeof(NullabilityPayload) * CHAR_BIT)/NullabilityKindSize); + } + + void addTypeInfo(unsigned index, NullabilityKind kind) { + assert(index <= getMaxNullabilityIndex()); + assert(static_cast(kind) < NullabilityKindMask); + NullabilityAudited = true; + if (NumAdjustedNullable < index + 1) + NumAdjustedNullable = index + 1; + + // Mask the bits. + NullabilityPayload &= ~(NullabilityKindMask << (index * NullabilityKindSize)); + + // Set the value. + unsigned kindValue = + (static_cast(kind)) << (index * NullabilityKindSize); + NullabilityPayload |= kindValue; + } + + /// Adds the return type info. + void addReturnTypeInfo(NullabilityKind kind) { + addTypeInfo(0, kind); + } + + /// Adds the parameter type info. + void addParamTypeInfo(unsigned index, NullabilityKind kind) { + addTypeInfo(index + 1, kind); + } + +private: + NullabilityKind getTypeInfo(unsigned index) const { + assert(NullabilityAudited && + "Checking the type adjustment on non-audited method."); + // If we don't have info about this parameter, return the default. + if (index > NumAdjustedNullable) + return NullabilityKind::NonNull; + return static_cast(( NullabilityPayload + >> (index * NullabilityKindSize) ) + & NullabilityKindMask); + } + +public: + NullabilityKind getParamTypeInfo(unsigned index) const { + return getTypeInfo(index + 1); + } + + NullabilityKind getReturnTypeInfo() const { + return getTypeInfo(0); + } + + friend bool operator==(const FunctionInfo &lhs, const FunctionInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.NullabilityAudited == rhs.NullabilityAudited && + lhs.NumAdjustedNullable == rhs.NumAdjustedNullable && + lhs.NullabilityPayload == rhs.NullabilityPayload; + } + + friend bool operator!=(const FunctionInfo &lhs, const FunctionInfo &rhs) { + return !(lhs == rhs); + } + +}; + +/// Describes API notes data for an Objective-C method. +class ObjCMethodInfo : public FunctionInfo { +public: + /// Whether this is a designated initializer of its class. + unsigned DesignatedInit : 1; + + /// Whether to treat this method as a factory or initializer. + unsigned FactoryAsInit : 2; + + /// Whether this is a required initializer. + unsigned Required : 1; + + ObjCMethodInfo() + : FunctionInfo(), + DesignatedInit(false), + FactoryAsInit(static_cast(FactoryAsInitKind::Infer)), + Required(false) { } + + FactoryAsInitKind getFactoryAsInitKind() const { + return static_cast(FactoryAsInit); + } + + void setFactoryAsInitKind(FactoryAsInitKind kind) { + FactoryAsInit = static_cast(kind); + } + + friend bool operator==(const ObjCMethodInfo &lhs, const ObjCMethodInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.DesignatedInit == rhs.DesignatedInit && + lhs.FactoryAsInit == rhs.FactoryAsInit && + lhs.Required == rhs.Required; + } + + friend bool operator!=(const ObjCMethodInfo &lhs, const ObjCMethodInfo &rhs) { + return !(lhs == rhs); + } + + void mergePropInfoIntoSetter(const ObjCPropertyInfo &pInfo); + + void mergePropInfoIntoGetter(const ObjCPropertyInfo &pInfo); + + /// Merge class-wide information into the given method. + friend ObjCMethodInfo &operator|=(ObjCMethodInfo &lhs, + const ObjCContextInfo &rhs) { + // Merge nullability. + if (!lhs.NullabilityAudited) { + if (auto nullable = rhs.getDefaultNullability()) { + lhs.NullabilityAudited = true; + lhs.addTypeInfo(0, *nullable); + } + } + + return lhs; + } + + void dump(llvm::raw_ostream &os); +}; + +/// Describes API notes data for a global variable. +class GlobalVariableInfo : public VariableInfo { +public: + GlobalVariableInfo() : VariableInfo() { } +}; + +/// Describes API notes data for a global function. +class GlobalFunctionInfo : public FunctionInfo { +public: + GlobalFunctionInfo() : FunctionInfo() { } +}; + +} // end namespace api_notes +} // end namespace clang + +#endif // LLVM_CLANG_API_NOTES_TYPES_H diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h index f563a04d06dab..9250a9d322fd2 100644 --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -17,6 +17,7 @@ #include "clang/AST/AttrIterator.h" #include "clang/AST/DeclarationName.h" #include "clang/Basic/Specifiers.h" +#include "clang/Basic/VersionTuple.h" #include "llvm/ADT/PointerUnion.h" #include "llvm/ADT/iterator.h" #include "llvm/ADT/iterator_range.h" @@ -586,11 +587,15 @@ class Decl { /// the given declaration (e.g., preferring 'unavailable' to /// 'deprecated'). /// - /// \param Message If non-NULL and the result is not \c + /// \param[out] Message If non-NULL and the result is not \c /// AR_Available, will be set to a (possibly empty) message /// describing why the declaration has not been introduced, is /// deprecated, or is unavailable. - AvailabilityResult getAvailability(std::string *Message = nullptr) const; + /// \param Version The version of the target OS to determine availability for. + /// If \c None, uses the version specified in the ASTContext's target info. + AvailabilityResult + getAvailability(std::string *Message = nullptr, + Optional Version = None) const; /// \brief Determine whether this declaration is marked 'deprecated'. /// diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index bd9b7ec1a59c5..61e00b7dc66d1 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -85,6 +85,9 @@ def NormalVar : SubsetSubjectisBitField()}]>; +def ObjCClassMethod : SubsetSubjectisInstanceMethod()}]>; + def ObjCInstanceMethod : SubsetSubjectisInstanceMethod()}]>; @@ -462,6 +465,7 @@ def Availability : InheritableAttr { .Case("macosx_app_extension", "OS X (App Extension)") .Case("tvos_app_extension", "tvOS (App Extension)") .Case("watchos_app_extension", "watchOS (App Extension)") + .Case("swift", "Swift") .Default(llvm::StringRef()); } }]; let HasCustomParsing = 1; @@ -1010,6 +1014,12 @@ def ObjCKindOf : TypeAttr { let Documentation = [Undocumented]; } +def NoEscape : InheritableAttr { + let Spellings = [GCC<"noescape">]; + let Subjects = SubjectList<[ParmVar], WarnDiag, "ExpectedParameter">; + let Documentation = [Undocumented]; +} + def AssumeAligned : InheritableAttr { let Spellings = [GCC<"assume_aligned">]; let Subjects = SubjectList<[ObjCMethod, Function]>; @@ -1144,6 +1154,18 @@ def ObjCRootClass : InheritableAttr { let Documentation = [Undocumented]; } +def ObjCSubclassingRestricted : InheritableAttr { + let Spellings = [GNU<"objc_subclassing_restricted">]; + let Subjects = SubjectList<[ObjCInterface], ErrorDiag>; + let Documentation = [Undocumented]; +} + +def ObjCCompleteDefinition : InheritableAttr { + let Spellings = [GNU<"objc_complete_definition">]; + let Subjects = SubjectList<[ObjCInterface], ErrorDiag>; + let Documentation = [Undocumented]; +} + def ObjCExplicitProtocolImpl : InheritableAttr { let Spellings = [GNU<"objc_protocol_requires_explicit_implementation">]; let Subjects = SubjectList<[ObjCProtocol], ErrorDiag>; @@ -1240,6 +1262,30 @@ def Regparm : TypeAttr { let Documentation = [RegparmDocs]; } +def SwiftError : InheritableAttr { + let Spellings = [GCC<"swift_error">]; + let Args = [EnumArgument<"Convention", "ConventionKind", + ["none", "nonnull_error", "null_result", "zero_result", "nonzero_result"], + ["None", "NonNullError", "NullResult", "ZeroResult", "NonZeroResult"]>]; + let Subjects = SubjectList<[ObjCMethod, Function], ErrorDiag>; + let Documentation = [SwiftErrorDocs]; +} + +def SwiftName : InheritableAttr { + let Spellings = [GCC<"swift_name">]; + let Args = [StringArgument<"Name">]; + // Proper subject list disabled because of the custom error needed. + // Let's avoid merge conflicts for now. +// let Subjects = SubjectList<[EnumConstant, ObjCProtocol, ObjCClassMethod], +// ErrorDiag, "ExpectedSwiftNameSubjects">; + let Documentation = [Undocumented]; +} + +def SwiftPrivate : InheritableAttr { + let Spellings = [GCC<"swift_private">]; + let Documentation = [Undocumented]; +} + def ReqdWorkGroupSize : InheritableAttr { let Spellings = [GNU<"reqd_work_group_size">]; let Args = [UnsignedArgument<"XDim">, UnsignedArgument<"YDim">, diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 30b6789cfd238..4743ddcabe8de 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1620,3 +1620,30 @@ function are loads and stores from objects pointed to by its pointer-typed arguments, with arbitrary offsets. }]; } + +def SwiftDocs : DocumentationCategory<"Controlling Swift Import"> { + let Content = [{ +Clang supports additional attributes for controlling how APIs are imported into Swift. + }]; +} + +def SwiftErrorDocs : Documentation { + let Category = SwiftDocs; + let Heading = "swift_error"; + let Content = [{ +The ``swift_error`` attribute controls whether a particular function (or Objective-C method) is imported into Swift as a throwing function, and if so, the dynamic convention it uses. + +All of these conventions except ``none`` require the function to have an error parameter. Currently, the error parameter is always the last parameter of type ``NSError**`` or ``CFErrorRef*``. Swift will remove the error parameter from the imported API, and dynamically will always pass a valid address initialized to a null pointer. + +* ``swift_error(none)`` means that the function should not be imported as throwing. The error parameter and result type will be left alone. + +* ``swift_error(null_result)`` means that calls to the function should be considered to have thrown if they return a null value. The return type must be a pointer type, and it will be imported into Swift with a non-optional type. This is the default error convention for Objective-C methods that return pointers. + +* ``swift_error(zero_result)`` means that calls to the function should be considered to have thrown if they return a zero result. The return type must be an integral type. If the return type would have been imported as ``Bool``, it is instead imported as ``Void``. This is the default error convention for Objective-C methods that return a type that would be imported as ``Bool``. + +* ``swift_error(nonzero_result)`` means that calls to the function should be considered to have thrown if they return a non-zero result. The return type must be an integral type. If the return type would have been imported as ``Bool``, it is instead imported as ``Void``. + +* ``swift_error(nonnull_error)`` means that calls to the function should be considered to have thrown if they leave a non-null error in the error parameter. The return type is left unmodified. + +}]; +} diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index 42e761a630bd1..dc28200da94a2 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -211,6 +211,11 @@ def note_mt_message : Note<"[rewriter] %0">; def warn_arcmt_nsalloc_realloc : Warning<"[rewriter] call returns pointer to GC managed memory; it will become unmanaged in ARC">; def err_arcmt_nsinvocation_ownership : Error<"NSInvocation's %0 is not safe to be used with an object with ownership other than __unsafe_unretained">; +// API notes +def err_apinotes_message : Error<"%0">; +def warn_apinotes_message : Warning<"%0">; +def note_apinotes_message : Note<"%0">; + // OpenMP def err_omp_more_one_clause : Error< "directive '#pragma omp %0' cannot contain more than one '%1' clause%select{| with '%3' name modifier}2">; diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index 033834bc505da..7dcf697306f3b 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -216,4 +216,8 @@ def err_missing_vfs_overlay_file : Error< "virtual filesystem overlay file '%0' not found">, DefaultFatal; def err_invalid_vfs_overlay : Error< "invalid virtual filesystem overlay file '%0'">, DefaultFatal; + +def err_no_apinotes_cache_path : Error< + "-fapinotes was provided without -fapinotes-cache-path=">, + DefaultFatal; } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index de623ca9b035c..a85c68b16a465 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -649,6 +649,8 @@ def note_suppressed_class_declare : Note< "class with specified objc_requires_property_definitions attribute is declared here">; def err_objc_root_class_subclass : Error< "objc_root_class attribute may only be specified on a root class declaration">; +def err_restricted_superclass_mismatch : Error< + "cannot subclass a class with objc_subclassing_restricted attribute">; def warn_objc_root_class_missing : Warning< "class %0 defined without specifying a base class">, InGroup; @@ -2431,6 +2433,9 @@ def warn_mismatched_availability_override_unavail : Warning< InGroup; def note_overridden_method : Note< "overridden method is here">; +def warn_availability_swift_unavailable_only : Warning< + "only 'unavailable' is supported for Swift availability">, + InGroup; def note_protocol_method : Note< "protocol method is here">; @@ -2765,6 +2770,9 @@ def warn_attribute_nonnull_no_pointers : Warning< def warn_attribute_nonnull_parm_no_args : Warning< "'nonnull' attribute when used on parameters takes no arguments">, InGroup; +def warn_attribute_noescape_non_pointer : Warning< + "'noescape' attribute ignored on parameter of non-pointer type %0">, + InGroup; def warn_attribute_sentinel_named_arguments : Warning< "'sentinel' attribute requires named arguments">, InGroup; @@ -2882,6 +2890,23 @@ def err_objc_bridged_related_known_method : Error< def err_objc_attr_protocol_requires_definition : Error< "attribute %0 can only be applied to @protocol definitions, not forward declarations">; +// Swift attributes +def err_attr_swift_name_decl_kind : Error< + "%0 attribute cannot be applied to this declaration">; +def err_attr_swift_name_identifier : Error< + "parameter of %0 attribute must be an ASCII identifier string">; +def err_attr_swift_name_function : Error< + "parameter of %0 attribute must be a Swift function name string">; +def warn_attr_swift_name_num_params : Warning< + "too %select{few|many}0 parameters in %1 attribute (expected %2; got %3)">, + InGroup>; +def err_attr_swift_error_no_error_parameter : Error< + "%0 attribute can only be applied to a %select{function|method}1 " + "with an error parameter">; +def err_attr_swift_error_return_type : Error< + "%0 attribute with '%1' convention can only be applied to a " + "%select{function|method}2 returning %select{an integral type|a pointer}3">; + // Function Parameter Semantic Analysis. def err_param_with_void_type : Error<"argument may not have 'void' type">; def err_void_only_param : Error< diff --git a/clang/include/clang/Basic/FileSystemOptions.h b/clang/include/clang/Basic/FileSystemOptions.h index 38f1346312489..1beb2e2907210 100644 --- a/clang/include/clang/Basic/FileSystemOptions.h +++ b/clang/include/clang/Basic/FileSystemOptions.h @@ -25,6 +25,9 @@ class FileSystemOptions { /// \brief If set, paths are resolved as if the working directory was /// set to the value of WorkingDir. std::string WorkingDir; + + /// The path to the API notes cache. + std::string APINotesCachePath; }; } // end namespace clang diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 75816e9a2671f..72c260ea5fab3 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -230,6 +230,7 @@ VALUE_LANGOPT(VtorDispMode, 2, 1, "How many vtordisps to insert") LANGOPT(ApplePragmaPack, 1, 0, "Apple gcc-compatible #pragma pack handling") LANGOPT(RetainCommentsFromSystemHeaders, 1, 0, "retain documentation comments from system headers in the AST") +LANGOPT(APINotes, 1, 0, "use external API notes") LANGOPT(SanitizeAddressFieldPadding, 2, 0, "controls how aggressive is ASan " "field padding (0: none, 1:least " diff --git a/clang/include/clang/Basic/SourceMgrAdapter.h b/clang/include/clang/Basic/SourceMgrAdapter.h new file mode 100644 index 0000000000000..6782aebbeff58 --- /dev/null +++ b/clang/include/clang/Basic/SourceMgrAdapter.h @@ -0,0 +1,83 @@ +//=== SourceMgrAdapter.h - SourceMgr to SourceManager Adapter ---*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file provides an adapter that maps diagnostics from llvm::SourceMgr +// to Clang's SourceManager. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SOURCEMGRADAPTER_H +#define LLVM_CLANG_SOURCEMGRADAPTER_H + +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/Support/SourceMgr.h" +#include +#include + +namespace clang { + +class DiagnosticsEngine; +class FileEntry; + +/// An adapter that can be used to translate diagnostics from one or more +/// llvm::SourceMgr instances to a , +class SourceMgrAdapter { + /// Clang source manager. + SourceManager &SrcMgr; + + /// Clang diagnostics engine. + DiagnosticsEngine &Diag; + + /// Diagnostic IDs for errors, warnings, and notes. + unsigned ErrorDiagID, WarningDiagID, NoteDiagID; + + /// The default file to use when mapping buffers. + const FileEntry *DefaultFile; + + /// A mapping from (LLVM source manager, buffer ID) pairs to the + /// corresponding file ID within the Clang source manager. + llvm::DenseMap, FileID> + FileIDMapping; + + /// Diagnostic handler. + static void handleDiag(const llvm::SMDiagnostic &diag, void *context); + +public: + /// Create a new \c SourceMgr adaptor that maps to the given source + /// manager and diagnostics engine. + SourceMgrAdapter(SourceManager &srcMgr, DiagnosticsEngine &diag, + unsigned errorDiagID, unsigned warningDiagID, + unsigned noteDiagID, const FileEntry *defaultFile = nullptr); + + ~SourceMgrAdapter(); + + /// Map a source location in the given LLVM source manager to its + /// corresponding location in the Clang source manager. + SourceLocation mapLocation(const llvm::SourceMgr &llvmSrcMgr,llvm::SMLoc loc); + + /// Map a source range in the given LLVM source manager to its corresponding + /// range in the Clang source manager. + SourceRange mapRange(const llvm::SourceMgr &llvmSrcMgr, llvm::SMRange range); + + /// Handle the given diagnostic from an LLVM source manager. + void handleDiag(const llvm::SMDiagnostic &diag); + + /// Retrieve the diagnostic handler to use with the underlying SourceMgr. + llvm::SourceMgr::DiagHandlerTy getDiagHandler() { return &handleDiag; } + + /// Retrieve the context to use with the diagnostic handler produced by + /// \c getDiagHandler(). + void *getDiagContext() { return this; } +}; + + +} // end namespace clang + +#endif diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 65dd87ce438b0..eb016f358d284 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -464,6 +464,14 @@ def fno_profile_instr_use : Flag<["-"], "fno-profile-instr-use">, def fno_profile_use : Flag<["-"], "fno-profile-use">, Alias; +def fapinotes : Flag<["-"], "fapinotes">, Group, + Flags<[CC1Option]>, HelpText<"Enable external API notes support">; +def fno_apinotes : Flag<["-"], "fno-apinotes">, Group, + Flags<[CC1Option]>, HelpText<"Disable external API notes support">; +def fapinotes_cache_path : Joined<["-"], "fapinotes-cache-path=">, + Group, Flags<[DriverOption, CC1Option]>, MetaVarName<"">, + HelpText<"Specify the API notes cache path">; + def fblocks : Flag<["-"], "fblocks">, Group, Flags<[CC1Option]>, HelpText<"Enable the 'blocks' language feature">; def fbootclasspath_EQ : Joined<["-"], "fbootclasspath=">, Group; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 39b7d8fcb5957..d58052b93a93a 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -24,6 +24,7 @@ #include "clang/AST/NSAPI.h" #include "clang/AST/PrettyPrinter.h" #include "clang/AST/TypeLoc.h" +#include "clang/APINotes/APINotesManager.h" #include "clang/Basic/ExpressionTraits.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/Module.h" @@ -296,6 +297,7 @@ class Sema { ASTConsumer &Consumer; DiagnosticsEngine &Diags; SourceManager &SourceMgr; + api_notes::APINotesManager APINotes; /// \brief Flag indicating whether or not to collect detailed statistics. bool CollectStats; @@ -2115,6 +2117,9 @@ class Sema { unsigned AttrSpellingListIndex); OptimizeNoneAttr *mergeOptimizeNoneAttr(Decl *D, SourceRange Range, unsigned AttrSpellingListIndex); + SwiftNameAttr *mergeSwiftNameAttr(Decl *D, SourceRange Range, + StringRef Name, bool Override, + unsigned AttrSpellingListIndex); void mergeDeclAttributes(NamedDecl *New, Decl *Old, AvailabilityMergeKind AMK = AMK_Redeclaration); @@ -2896,6 +2901,12 @@ class Sema { void checkUnusedDeclAttributes(Declarator &D); + /// Map any API notes provided for this declaration to attributes on the + /// declaration. + /// + /// Triggered by declaration-attribute processing. + void ProcessAPINotes(Decl *D); + /// Determine if type T is a valid subject for a nonnull and similar /// attributes. By default, we look through references (the behavior used by /// nonnull), but if the second parameter is true, then we treat a reference @@ -2950,7 +2961,8 @@ class Sema { /// \returns true if nullability cannot be applied, false otherwise. bool checkNullabilityTypeSpecifier(QualType &type, NullabilityKind nullability, SourceLocation nullabilityLoc, - bool isContextSensitive); + bool isContextSensitive, + bool implicit); /// \brief Stmt attributes - this routine is the top level dispatcher. StmtResult ProcessStmtAttributes(Stmt *Stmt, AttributeList *Attrs, @@ -7485,6 +7497,12 @@ class Sema { RTC_Unknown }; + /// Check whether the declared result type of the given Objective-C + /// method declaration is compatible with the method's class. + ResultTypeCompatibilityKind + checkRelatedResultTypeCompatibility(const ObjCMethodDecl *Method, + const ObjCInterfaceDecl *CurrentClass); + void CheckObjCMethodOverrides(ObjCMethodDecl *ObjCMethod, ObjCInterfaceDecl *CurrentClass, ResultTypeCompatibilityKind RTC); @@ -9061,6 +9079,7 @@ class Sema { /// The struct behind the CFErrorRef pointer. RecordDecl *CFError = nullptr; + bool isCFError(RecordDecl *D); /// Retrieve the identifier "NSError". IdentifierInfo *getNSErrorIdent(); diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h new file mode 100644 index 0000000000000..5462e5a558625 --- /dev/null +++ b/clang/lib/APINotes/APINotesFormat.h @@ -0,0 +1,239 @@ +//===--- APINotesFormat.h - The internals of API notes files ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Contains various constants and helper types to deal with API notes +/// files. +/// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_API_NOTES_FORMAT_H +#define LLVM_CLANG_API_NOTES_FORMAT_H + +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Bitcode/RecordLayout.h" + +namespace clang { +namespace api_notes { + +using namespace llvm; + +/// Magic number for API notes files. +const unsigned char API_NOTES_SIGNATURE[] = { 0xE2, 0x9C, 0xA8, 0x01 }; + +/// API notes file major version number. +/// +const uint16_t VERSION_MAJOR = 0; + +/// API notes file minor version number. +/// +/// When the format changes IN ANY WAY, this number should be incremented. +const uint16_t VERSION_MINOR = 6; + +using IdentifierID = Fixnum<31>; +using IdentifierIDField = BCVBR<16>; + +using SelectorID = Fixnum<31>; +using SelectorIDField = BCVBR<16>; + +using StoredContextID = Fixnum<31>; + +/// The various types of blocks that can occur within a API notes file. +/// +/// These IDs must \em not be renumbered or reordered without incrementing +/// VERSION_MAJOR. +enum BlockID { + /// The control block, which contains all of the information that needs to + /// be validated prior to committing to loading the API notes file. + /// + /// \sa control_block + CONTROL_BLOCK_ID = llvm::bitc::FIRST_APPLICATION_BLOCKID, + + /// The identifier data block, which maps identifier strings to IDs. + IDENTIFIER_BLOCK_ID, + + /// The Objective-C class data block, which maps Objective-C class + /// names to information about the class. + OBJC_CONTEXT_BLOCK_ID, + + /// The Objective-C property data block, which maps Objective-C + /// (class name, property name) pairs to information about the + /// property. + OBJC_PROPERTY_BLOCK_ID, + + /// The Objective-C property data block, which maps Objective-C + /// (class name, selector, is_instance_method) tuples to information + /// about the method. + OBJC_METHOD_BLOCK_ID, + + /// The Objective-C selector data block, which maps Objective-C + /// selector names (# of pieces, identifier IDs) to the selector ID + /// used in other tables. + OBJC_SELECTOR_BLOCK_ID, + + /// The global variables data block, which maps global variable names to + /// information about the global variable. + GLOBAL_VARIABLE_BLOCK_ID, + + /// The (global) functions data block, which maps global function names to + /// information about the global function. + GLOBAL_FUNCTION_BLOCK_ID +}; + +namespace control_block { + // These IDs must \em not be renumbered or reordered without incrementing + // VERSION_MAJOR. + enum { + METADATA = 1, + MODULE_NAME = 2 + }; + + using MetadataLayout = BCRecordLayout< + METADATA, // ID + BCFixed<16>, // Module format major version + BCFixed<16> // Module format minor version + >; + + using ModuleNameLayout = BCRecordLayout< + MODULE_NAME, + BCBlob // Module name + >; +} + +namespace identifier_block { + enum { + IDENTIFIER_DATA = 1, + }; + + using IdentifierDataLayout = BCRecordLayout< + IDENTIFIER_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from identifier strings to decl kinds / decl IDs + >; +} + +namespace objc_context_block { + enum { + OBJC_CONTEXT_DATA = 1, + }; + + using ObjCContextDataLayout = BCRecordLayout< + OBJC_CONTEXT_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from ObjC class names (as IDs) to ObjC class information + >; +} + +namespace objc_property_block { + enum { + OBJC_PROPERTY_DATA = 1, + }; + + using ObjCPropertyDataLayout = BCRecordLayout< + OBJC_PROPERTY_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from ObjC (class name, property name) pairs to ObjC + // property information + >; +} + +namespace objc_method_block { + enum { + OBJC_METHOD_DATA = 1, + }; + + using ObjCMethodDataLayout = BCRecordLayout< + OBJC_METHOD_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from ObjC (class names, selector, + // is-instance-method) tuples to ObjC method information + >; +} + +namespace objc_selector_block { + enum { + OBJC_SELECTOR_DATA = 1, + }; + + using ObjCSelectorDataLayout = BCRecordLayout< + OBJC_SELECTOR_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from (# pieces, identifier IDs) to Objective-C selector ID. + >; +} + +namespace global_variable_block { + enum { + GLOBAL_VARIABLE_DATA = 1 + }; + + using GlobalVariableDataLayout = BCRecordLayout< + GLOBAL_VARIABLE_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from name to global variable information + >; +} + +namespace global_function_block { + enum { + GLOBAL_FUNCTION_DATA = 1 + }; + + using GlobalFunctionDataLayout = BCRecordLayout< + GLOBAL_FUNCTION_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from name to global function information + >; +} + +/// A stored Objective-C selector. +struct StoredObjCSelector { + unsigned NumPieces; + llvm::SmallVector Identifiers; +}; + +} // end namespace api_notes +} // end namespace clang + +namespace llvm { + template<> + struct DenseMapInfo { + typedef DenseMapInfo UnsignedInfo; + + static inline clang::api_notes::StoredObjCSelector getEmptyKey() { + return clang::api_notes::StoredObjCSelector{ + UnsignedInfo::getEmptyKey(), { } }; + } + + static inline clang::api_notes::StoredObjCSelector getTombstoneKey() { + return clang::api_notes::StoredObjCSelector{ + UnsignedInfo::getTombstoneKey(), { } }; + } + + static unsigned getHashValue( + const clang::api_notes::StoredObjCSelector& value) { + auto hash = llvm::hash_value(value.NumPieces); + hash = hash_combine(hash, value.Identifiers.size()); + for (auto piece : value.Identifiers) + hash = hash_combine(hash, static_cast(piece)); + // FIXME: Mix upper/lower 32-bit values together to produce + // unsigned rather than truncating. + return hash; + } + + static bool isEqual(const clang::api_notes::StoredObjCSelector &lhs, + const clang::api_notes::StoredObjCSelector &rhs) { + return lhs.NumPieces == rhs.NumPieces && + lhs.Identifiers == rhs.Identifiers; + } + }; +} + +#endif // LLVM_CLANG_API_NOTES_FORMAT_H diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp new file mode 100644 index 0000000000000..50736939a60c1 --- /dev/null +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -0,0 +1,453 @@ +//===--- APINotesMAnager.cpp - Manage API Notes Files ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the APINotesManager class. +// +//===----------------------------------------------------------------------===// + +#include "clang/APINotes/APINotesManager.h" +#include "clang/APINotes/APINotesReader.h" +#include "clang/APINotes/APINotesYAMLCompiler.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/SourceMgrAdapter.h" +#include "clang/Basic/Version.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/Support/Path.h" +#include + +using namespace clang; +using namespace api_notes; + +#define DEBUG_TYPE "API Notes" +STATISTIC(NumHeaderAPINotes, + "non-framework API notes files loaded"); +STATISTIC(NumPublicFrameworkAPINotes, + "framework public API notes loaded"); +STATISTIC(NumPrivateFrameworkAPINotes, + "framework private API notes loaded"); +STATISTIC(NumFrameworksSearched, + "frameworks searched"); +STATISTIC(NumDirectoriesSearched, + "header directories searched"); +STATISTIC(NumDirectoryCacheHits, + "directory cache hits"); +STATISTIC(NumBinaryCacheHits, + "binary form cache hits"); +STATISTIC(NumBinaryCacheMisses, + "binary form cache misses"); +STATISTIC(NumBinaryCacheRebuilds, + "binary form cache rebuilds"); + +APINotesManager::APINotesManager(SourceManager &SourceMgr) + : SourceMgr(SourceMgr), PrunedCache(false) { } + + +APINotesManager::~APINotesManager() { + // Free the API notes readers. + for (const auto &entry : Readers) { + if (auto reader = entry.second.dyn_cast()) { + delete reader; + } + } +} + +/// \brief Write a new timestamp file with the given path. +static void writeTimestampFile(StringRef TimestampFile) { + std::error_code EC; + llvm::raw_fd_ostream Out(TimestampFile.str(), EC, llvm::sys::fs::F_None); +} + +/// \brief Prune the API notes cache of API notes that haven't been accessed in +/// a long time. +static void pruneAPINotesCache(StringRef APINotesCachePath) { + struct stat StatBuf; + llvm::SmallString<128> TimestampFile; + TimestampFile = APINotesCachePath; + llvm::sys::path::append(TimestampFile, "APINotes.timestamp"); + + // Try to stat() the timestamp file. + if (::stat(TimestampFile.c_str(), &StatBuf)) { + // If the timestamp file wasn't there, create one now. + if (errno == ENOENT) { + llvm::sys::fs::create_directories(APINotesCachePath); + writeTimestampFile(TimestampFile); + } + return; + } + + const unsigned APINotesCachePruneInterval = 7 * 24 * 60 * 60; + const unsigned APINotesCachePruneAfter = 31 * 24 * 60 * 60; + + // Check whether the time stamp is older than our pruning interval. + // If not, do nothing. + time_t TimeStampModTime = StatBuf.st_mtime; + time_t CurrentTime = time(nullptr); + if (CurrentTime - TimeStampModTime <= time_t(APINotesCachePruneInterval)) + return; + + // Write a new timestamp file so that nobody else attempts to prune. + // There is a benign race condition here, if two Clang instances happen to + // notice at the same time that the timestamp is out-of-date. + writeTimestampFile(TimestampFile); + + // Walk the entire API notes cache, looking for unused compiled API notes. + std::error_code EC; + SmallString<128> APINotesCachePathNative; + llvm::sys::path::native(APINotesCachePath, APINotesCachePathNative); + for (llvm::sys::fs::directory_iterator + File(APINotesCachePathNative.str(), EC), DirEnd; + File != DirEnd && !EC; File.increment(EC)) { + StringRef Extension = llvm::sys::path::extension(File->path()); + if (Extension.empty()) + continue; + + if (Extension.substr(1) != BINARY_APINOTES_EXTENSION) + continue; + + // Look at this file. If we can't stat it, there's nothing interesting + // there. + if (::stat(File->path().c_str(), &StatBuf)) + continue; + + // If the file has been used recently enough, leave it there. + time_t FileAccessTime = StatBuf.st_atime; + if (CurrentTime - FileAccessTime <= time_t(APINotesCachePruneAfter)) { + continue; + } + + // Remove the file. + llvm::sys::fs::remove(File->path()); + } +} + +bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir, + const FileEntry *APINotesFile) { + assert(Readers.find(HeaderDir) == Readers.end()); + + FileManager &FileMgr = SourceMgr.getFileManager(); + + // If the API notes file is already in the binary form, load it directly. + StringRef APINotesFileName = APINotesFile->getName(); + StringRef APINotesFileExt = llvm::sys::path::extension(APINotesFileName); + if (!APINotesFileExt.empty() && + APINotesFileExt.substr(1) == BINARY_APINOTES_EXTENSION) { + // Load the file. + auto Buffer = FileMgr.getBufferForFile(APINotesFile); + if (!Buffer) { + Readers[HeaderDir] = nullptr; + return true; + } + + // Load the binary form. + auto Reader = APINotesReader::get(std::move(Buffer.get())); + if (!Reader) { + Readers[HeaderDir] = nullptr; + return true; + } + + // Record the reader. + Readers[HeaderDir] = Reader.release(); + return false; + } + + // If we haven't pruned the API notes cache yet during this execution, do + // so now. + if (!PrunedCache) { + pruneAPINotesCache(FileMgr.getFileSystemOpts().APINotesCachePath); + PrunedCache = true; + } + + // Compute a hash of the API notes file's directory and the Clang version, + // to be used as part of the filename for the cached binary copy. + auto code = llvm::hash_value(StringRef(APINotesFile->getDir()->getName())); + code = hash_combine(code, getClangFullRepositoryVersion()); + + // Determine the file name for the cached binary form. + SmallString<128> CompiledFileName; + CompiledFileName += FileMgr.getFileSystemOpts().APINotesCachePath; + assert(!CompiledFileName.empty() && "No API notes cache path provided?"); + llvm::sys::path::append(CompiledFileName, + (llvm::Twine(llvm::sys::path::stem(APINotesFileName)) + "-" + + llvm::APInt(64, code).toString(36, /*Signed=*/false) + "." + + BINARY_APINOTES_EXTENSION)); + + // Try to open the cached binary form. + if (const FileEntry *CompiledFile = FileMgr.getFile(CompiledFileName, + /*openFile=*/true, + /*cacheFailure=*/false)) { + // Load the file contents. + if (auto Buffer = FileMgr.getBufferForFile(CompiledFile)) { + // Make sure the file is up-to-date. + if (CompiledFile->getModificationTime() + >= APINotesFile->getModificationTime()) { + // Load the file. + if (auto Reader = APINotesReader::get(std::move(Buffer.get()))) { + // Success. + ++NumBinaryCacheHits; + Readers[HeaderDir] = Reader.release(); + return false; + } + } + } + + // The cache entry was somehow broken; delete this one so we can build a + // new one below. + llvm::sys::fs::remove(CompiledFileName.str()); + ++NumBinaryCacheRebuilds; + } else { + ++NumBinaryCacheMisses; + } + + // Open the source file. + auto Buffer = FileMgr.getBufferForFile(APINotesFile); + if (!Buffer) { + Readers[HeaderDir] = nullptr; + return true; + } + + // Compile the API notes source into a buffer. + // FIXME: Either propagate OSType through or, better yet, improve the binary + // APINotes format to maintain complete availability information. + llvm::SmallVector APINotesBuffer; + { + SourceMgrAdapter srcMgrAdapter(SourceMgr, SourceMgr.getDiagnostics(), + diag::err_apinotes_message, + diag::warn_apinotes_message, + diag::note_apinotes_message, + APINotesFile); + llvm::raw_svector_ostream OS(APINotesBuffer); + if (api_notes::compileAPINotes(Buffer.get()->getBuffer(), + OS, + api_notes::OSType::Absent, + srcMgrAdapter.getDiagHandler(), + srcMgrAdapter.getDiagContext())) { + Readers[HeaderDir] = nullptr; + return true; + } + + // Make a copy of the compiled form into the buffer. + Buffer = llvm::MemoryBuffer::getMemBufferCopy( + StringRef(APINotesBuffer.data(), APINotesBuffer.size())); + } + + // Save the binary form into the cache. Perform this operation + // atomically. + SmallString<64> TemporaryBinaryFileName = CompiledFileName.str(); + TemporaryBinaryFileName.erase( + TemporaryBinaryFileName.end() + - llvm::sys::path::extension(TemporaryBinaryFileName).size(), + TemporaryBinaryFileName.end()); + TemporaryBinaryFileName += "-%%%%%%."; + TemporaryBinaryFileName += BINARY_APINOTES_EXTENSION; + + int TemporaryFD; + llvm::sys::fs::create_directories( + FileMgr.getFileSystemOpts().APINotesCachePath); + if (!llvm::sys::fs::createUniqueFile(TemporaryBinaryFileName.str(), + TemporaryFD, TemporaryBinaryFileName)) { + // Write the contents of the buffer. + bool hadError; + { + llvm::raw_fd_ostream Out(TemporaryFD, /*shouldClose=*/true); + Out.write(Buffer.get()->getBufferStart(), Buffer.get()->getBufferSize()); + Out.flush(); + + hadError = Out.has_error(); + } + + if (!hadError) { + // Rename the temporary file to the actual compiled file. + llvm::sys::fs::rename(TemporaryBinaryFileName.str(), + CompiledFileName.str()); + } + } + + // Load the binary form we just compiled. + auto Reader = APINotesReader::get(std::move(*Buffer)); + assert(Reader && "Could not load the API notes we just generated?"); + + // Record the reader. + Readers[HeaderDir] = Reader.release(); + return false; +} + +const DirectoryEntry *APINotesManager::loadFrameworkAPINotes( + llvm::StringRef FrameworkPath, + llvm::StringRef FrameworkName, + bool Public) { + FileManager &FileMgr = SourceMgr.getFileManager(); + + llvm::SmallString<128> Path; + Path += FrameworkPath; + unsigned FrameworkNameLength = Path.size(); + + // Form the path to the APINotes file. + llvm::sys::path::append(Path, "APINotes"); + if (Public) + llvm::sys::path::append(Path, + (llvm::Twine(FrameworkName) + "." + + SOURCE_APINOTES_EXTENSION)); + else + llvm::sys::path::append(Path, + (llvm::Twine(FrameworkName) + "_private." + + SOURCE_APINOTES_EXTENSION)); + + // Try to open the APINotes file. + const FileEntry *APINotesFile = FileMgr.getFile(Path); + if (!APINotesFile) + return nullptr; + + // Form the path to the corresponding header directory. + Path.resize(FrameworkNameLength); + if (Public) + llvm::sys::path::append(Path, "Headers"); + else + llvm::sys::path::append(Path, "PrivateHeaders"); + + // Try to access the header directory. + const DirectoryEntry *HeaderDir = FileMgr.getDirectory(Path); + if (!HeaderDir) + return nullptr; + + // Try to load the API notes. + if (loadAPINotes(HeaderDir, APINotesFile)) + return nullptr; + + // Success: return the header directory. + if (Public) + ++NumPublicFrameworkAPINotes; + else + ++NumPrivateFrameworkAPINotes; + return HeaderDir; +} + +APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) { + // API notes are associated with the expansion location. Retrieve the + // file for this location. + SourceLocation ExpansionLoc = SourceMgr.getExpansionLoc(Loc); + FileID ID = SourceMgr.getFileID(ExpansionLoc); + if (ID.isInvalid()) + return nullptr; + const FileEntry *File = SourceMgr.getFileEntryForID(ID); + if (!File) + return nullptr; + + // Look for API notes in the directory corresponding to this file, or one of + // its its parent directories. + const DirectoryEntry *Dir = File->getDir(); + FileManager &FileMgr = SourceMgr.getFileManager(); + llvm::SetVector, + llvm::SmallPtrSet> DirsVisited; + APINotesReader *Result = nullptr; + do { + // Look for an API notes reader for this header search directory. + auto Known = Readers.find(Dir); + + // If we already know the answer, chase it. + if (Known != Readers.end()) { + ++NumDirectoryCacheHits; + + // We've been redirected to another directory for answers. Follow it. + if (auto OtherDir = Known->second.dyn_cast()) { + DirsVisited.insert(Dir); + Dir = OtherDir; + continue; + } + + // We have the answer. + Result = Known->second.dyn_cast(); + break; + } + + // Look for API notes corresponding to this directory. + StringRef Path = Dir->getName(); + if (llvm::sys::path::extension(Path) == ".framework") { + // If this is a framework directory, check whether there are API notes + // in the APINotes subdirectory. + auto FrameworkName = llvm::sys::path::stem(Path); + ++NumFrameworksSearched; + + // Look for API notes for both the public and private headers. + const DirectoryEntry *PublicDir + = loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/true); + const DirectoryEntry *PrivateDir + = loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/false); + + if (PublicDir || PrivateDir) { + // We found API notes: don't ever look past the framework directory. + Readers[Dir] = nullptr; + + // Pretend we found the result in the public or private directory, + // as appropriate. All headers should be in one of those two places, + // but be defensive here. + if (!DirsVisited.empty()) { + if (DirsVisited.back() == PublicDir) { + DirsVisited.pop_back(); + Dir = PublicDir; + } else if (DirsVisited.back() == PrivateDir) { + DirsVisited.pop_back(); + Dir = PrivateDir; + } + } + + // Grab the result. + Result = Readers[Dir].dyn_cast();; + break; + } + } else { + // Look for an APINotes file in this directory. + llvm::SmallString<128> APINotesPath; + APINotesPath += Dir->getName(); + llvm::sys::path::append(APINotesPath, + (llvm::Twine("APINotes.") + + SOURCE_APINOTES_EXTENSION)); + + // If there is an API notes file here, try to load it. + ++NumDirectoriesSearched; + if (const FileEntry *APINotesFile = FileMgr.getFile(APINotesPath)) { + if (!loadAPINotes(Dir, APINotesFile)) { + ++NumHeaderAPINotes; + Result = Readers[Dir].dyn_cast(); + break; + } + } + } + + // We didn't find anything. Look at the parent directory. + if (!DirsVisited.insert(Dir)) { + Dir = 0; + break; + } + + StringRef ParentPath = llvm::sys::path::parent_path(Path); + while (llvm::sys::path::stem(ParentPath) == "..") { + ParentPath = llvm::sys::path::parent_path(ParentPath); + } + if (ParentPath.empty()) { + Dir = nullptr; + } else { + Dir = FileMgr.getDirectory(ParentPath); + } + } while (Dir); + + // Path compression for all of the directories we visited, redirecting + // them to the directory we ended on. If no API notes were found, the + // resulting directory will be NULL, indicating no API notes. + for (const auto Visited : DirsVisited) { + Readers[Visited] = Dir; + } + + return Result; +} diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp new file mode 100644 index 0000000000000..a84f2d70491fe --- /dev/null +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -0,0 +1,1297 @@ +//===--- APINotesReader.cpp - Side Car Reader --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the \c APINotesReader class that reads source +// API notes data providing additional information about source code as +// a separate input, such as the non-nil/nilable annotations for +// method parameters. +// +//===----------------------------------------------------------------------===// +#include "clang/APINotes/APINotesReader.h" +#include "APINotesFormat.h" +#include "llvm/Bitcode/BitstreamReader.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/OnDiskHashTable.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/StringExtras.h" + +using namespace clang; +using namespace api_notes; +using namespace llvm::support; +using namespace llvm; + +namespace { + /// Read serialized CommonEntityInfo. + void readCommonEntityInfo(const uint8_t *&data, CommonEntityInfo &info) { + info.Unavailable = *data++; + + unsigned msgLength = endian::readNext(data); + info.UnavailableMsg + = std::string(reinterpret_cast(data), + reinterpret_cast(data) + msgLength); + data += msgLength; + } + + /// Used to deserialize the on-disk identifier table. + class IdentifierTableInfo { + public: + using internal_key_type = StringRef; + using external_key_type = StringRef; + using data_type = IdentifierID; + using hash_value_type = uint32_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return llvm::HashString(key); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + return StringRef(reinterpret_cast(data), length); + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + return endian::readNext(data); + } + }; + + /// Used to deserialize the on-disk Objective-C class table. + class ObjCContextTableInfo { + public: + // identifier ID, is-protocol + using internal_key_type = std::pair; + using external_key_type = internal_key_type; + using data_type = std::pair; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return static_cast(llvm::hash_value(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + auto nameID + = endian::readNext(data); + auto isProtocol = endian::readNext(data); + return { nameID, isProtocol }; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + data_type result; + result.first = endian::readNext(data); + readCommonEntityInfo(data, result.second); + if (*data++) { + result.second.setDefaultNullability(static_cast(*data)); + } + ++data; + result.second.setHasDesignatedInits(*data++); + return result; + } + }; + + /// Read serialized VariableInfo. + void readVariableInfo(const uint8_t *&data, VariableInfo &info) { + readCommonEntityInfo(data, info); + if (*data++) { + info.setNullabilityAudited(static_cast(*data)); + } + ++data; + } + + /// Used to deserialize the on-disk Objective-C property table. + class ObjCPropertyTableInfo { + public: + // (context ID, name ID) + using internal_key_type = std::pair; + using external_key_type = internal_key_type; + using data_type = ObjCPropertyInfo; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return static_cast(llvm::hash_value(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + auto classID = endian::readNext(data); + auto nameID = endian::readNext(data); + return { classID, nameID }; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + ObjCPropertyInfo info; + readVariableInfo(data, info); + return info; + } + }; + + /// Read serialized FunctionInfo. + void readFunctionInfo(const uint8_t *&data, FunctionInfo &info) { + readCommonEntityInfo(data, info); + info.NullabilityAudited + = endian::readNext(data); + info.NumAdjustedNullable + = endian::readNext(data); + info.NullabilityPayload + = endian::readNext(data); + } + + /// Used to deserialize the on-disk Objective-C method table. + class ObjCMethodTableInfo { + public: + // (class ID, selector ID, is-instance) + using internal_key_type = std::tuple; + using external_key_type = internal_key_type; + using data_type = ObjCMethodInfo; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return llvm::hash_combine(std::get<0>(key), + std::get<1>(key), + std::get<2>(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + auto classID = endian::readNext(data); + auto selectorID = endian::readNext(data); + auto isInstance = endian::readNext(data); + return internal_key_type{ classID, selectorID, isInstance }; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + ObjCMethodInfo info; + readFunctionInfo(data, info); + info.DesignatedInit = endian::readNext(data); + info.FactoryAsInit = endian::readNext(data); + info.Required = endian::readNext(data); + return info; + } + }; + + /// Used to deserialize the on-disk Objective-C selector table. + class ObjCSelectorTableInfo { + public: + using internal_key_type = StoredObjCSelector; + using external_key_type = internal_key_type; + using data_type = SelectorID; + using hash_value_type = unsigned; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return llvm::DenseMapInfo::getHashValue(key); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return llvm::DenseMapInfo::isEqual(lhs, rhs); + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + internal_key_type key; + key.NumPieces = endian::readNext(data); + unsigned numIdents = (length - sizeof(uint16_t)) / sizeof(IdentifierID); + for (unsigned i = 0; i != numIdents; ++i) { + key.Identifiers.push_back( + endian::readNext(data)); + } + return key; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + return endian::readNext(data); + } + }; + + /// Used to deserialize the on-disk global variable table. + class GlobalVariableTableInfo { + public: + using internal_key_type = unsigned; // name ID + using external_key_type = internal_key_type; + using data_type = GlobalVariableInfo; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return static_cast(llvm::hash_value(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + auto nameID = endian::readNext(data); + return nameID; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + GlobalVariableInfo info; + readVariableInfo(data, info); + return info; + } + }; + + /// Used to deserialize the on-disk global function table. + class GlobalFunctionTableInfo { + public: + using internal_key_type = unsigned; // name ID + using external_key_type = internal_key_type; + using data_type = GlobalFunctionInfo; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return static_cast(llvm::hash_value(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + auto nameID = endian::readNext(data); + return nameID; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + GlobalFunctionInfo info; + readFunctionInfo(data, info); + return info; + } + }; +} // end anonymous namespace + +class APINotesReader::Implementation { +public: + /// The input buffer for the API notes data. + std::unique_ptr InputBuffer; + + /// The reader attached to \c InputBuffer. + llvm::BitstreamReader InputReader; + + /// The name of the module that we read from the control block. + std::string ModuleName; + + using SerializedIdentifierTable = + llvm::OnDiskIterableChainedHashTable; + + /// The identifier table. + std::unique_ptr IdentifierTable; + + using SerializedObjCContextTable = + llvm::OnDiskIterableChainedHashTable; + + /// The Objective-C context table. + std::unique_ptr ObjCContextTable; + + using SerializedObjCPropertyTable = + llvm::OnDiskIterableChainedHashTable; + + /// The Objective-C property table. + std::unique_ptr ObjCPropertyTable; + + using SerializedObjCMethodTable = + llvm::OnDiskIterableChainedHashTable; + + /// The Objective-C method table. + std::unique_ptr ObjCMethodTable; + + using SerializedObjCSelectorTable = + llvm::OnDiskIterableChainedHashTable; + + /// The Objective-C selector table. + std::unique_ptr ObjCSelectorTable; + + using SerializedGlobalVariableTable = + llvm::OnDiskIterableChainedHashTable; + + /// The global variable table. + std::unique_ptr GlobalVariableTable; + + using SerializedGlobalFunctionTable = + llvm::OnDiskIterableChainedHashTable; + + /// The global function table. + std::unique_ptr GlobalFunctionTable; + + /// Retrieve the identifier ID for the given string, or an empty + /// optional if the string is unknown. + Optional getIdentifier(StringRef str); + + /// Retrieve the selector ID for the given selector, or an empty + /// optional if the string is unknown. + Optional getSelector(ObjCSelectorRef selector); + + bool readControlBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); + bool readIdentifierBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); + bool readObjCContextBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); + bool readObjCPropertyBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); + bool readObjCMethodBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); + bool readObjCSelectorBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); + bool readGlobalVariableBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); + bool readGlobalFunctionBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); +}; + +Optional APINotesReader::Implementation::getIdentifier( + StringRef str) { + if (!IdentifierTable) + return None; + + if (str.empty()) + return IdentifierID(0); + + auto known = IdentifierTable->find(str); + if (known == IdentifierTable->end()) + return None; + + return *known; +} + +Optional APINotesReader::Implementation::getSelector( + ObjCSelectorRef selector) { + if (!ObjCSelectorTable || !IdentifierTable) + return None; + + // Translate the identifiers. + StoredObjCSelector key; + key.NumPieces = selector.NumPieces; + for (auto ident : selector.Identifiers) { + if (auto identID = getIdentifier(ident)) { + key.Identifiers.push_back(*identID); + } else { + return None; + } + } + + auto known = ObjCSelectorTable->find(key); + if (known == ObjCSelectorTable->end()) + return None; + + return *known; + +} + +bool APINotesReader::Implementation::readControlBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(CONTROL_BLOCK_ID)) + return true; + + bool sawMetadata = false; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown metadata sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case control_block::METADATA: + // Already saw metadata. + if (sawMetadata) + return true; + + if (scratch[0] != VERSION_MAJOR || scratch[1] != VERSION_MINOR) + return true; + + sawMetadata = true; + break; + + case control_block::MODULE_NAME: + ModuleName = blobData.str(); + break; + + default: + // Unknown metadata record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return !sawMetadata; +} + +bool APINotesReader::Implementation::readIdentifierBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(IDENTIFIER_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case identifier_block::IDENTIFIER_DATA: { + // Already saw identifier table. + if (IdentifierTable) + return true; + + uint32_t tableOffset; + identifier_block::IdentifierDataLayout::readRecord(scratch, tableOffset); + auto base = reinterpret_cast(blobData.data()); + + IdentifierTable.reset( + SerializedIdentifierTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + +bool APINotesReader::Implementation::readObjCContextBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(OBJC_CONTEXT_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case objc_context_block::OBJC_CONTEXT_DATA: { + // Already saw Objective-C class table. + if (ObjCContextTable) + return true; + + uint32_t tableOffset; + objc_context_block::ObjCContextDataLayout::readRecord(scratch, tableOffset); + auto base = reinterpret_cast(blobData.data()); + + ObjCContextTable.reset( + SerializedObjCContextTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + +bool APINotesReader::Implementation::readObjCPropertyBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(OBJC_PROPERTY_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case objc_property_block::OBJC_PROPERTY_DATA: { + // Already saw Objective-C property table. + if (ObjCPropertyTable) + return true; + + uint32_t tableOffset; + objc_property_block::ObjCPropertyDataLayout::readRecord(scratch, + tableOffset); + auto base = reinterpret_cast(blobData.data()); + + ObjCPropertyTable.reset( + SerializedObjCPropertyTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + +bool APINotesReader::Implementation::readObjCMethodBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(OBJC_METHOD_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case objc_method_block::OBJC_METHOD_DATA: { + // Already saw Objective-C method table. + if (ObjCMethodTable) + return true; + + uint32_t tableOffset; + objc_method_block::ObjCMethodDataLayout::readRecord(scratch, tableOffset); + auto base = reinterpret_cast(blobData.data()); + + ObjCMethodTable.reset( + SerializedObjCMethodTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + +bool APINotesReader::Implementation::readObjCSelectorBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(OBJC_SELECTOR_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case objc_selector_block::OBJC_SELECTOR_DATA: { + // Already saw Objective-C selector table. + if (ObjCSelectorTable) + return true; + + uint32_t tableOffset; + objc_selector_block::ObjCSelectorDataLayout::readRecord(scratch, + tableOffset); + auto base = reinterpret_cast(blobData.data()); + + ObjCSelectorTable.reset( + SerializedObjCSelectorTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + +bool APINotesReader::Implementation::readGlobalVariableBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(GLOBAL_VARIABLE_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case global_variable_block::GLOBAL_VARIABLE_DATA: { + // Already saw global variable table. + if (GlobalVariableTable) + return true; + + uint32_t tableOffset; + global_variable_block::GlobalVariableDataLayout::readRecord(scratch, + tableOffset); + auto base = reinterpret_cast(blobData.data()); + + GlobalVariableTable.reset( + SerializedGlobalVariableTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + +bool APINotesReader::Implementation::readGlobalFunctionBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(GLOBAL_FUNCTION_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case global_function_block::GLOBAL_FUNCTION_DATA: { + // Already saw global function table. + if (GlobalFunctionTable) + return true; + + uint32_t tableOffset; + global_function_block::GlobalFunctionDataLayout::readRecord(scratch, + tableOffset); + auto base = reinterpret_cast(blobData.data()); + + GlobalFunctionTable.reset( + SerializedGlobalFunctionTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + +APINotesReader::APINotesReader(std::unique_ptr inputBuffer, + bool &failed) + : Impl(*new Implementation) +{ + failed = false; + + // Initialize the input buffer. + Impl.InputBuffer = std::move(inputBuffer); + Impl.InputReader.init( + reinterpret_cast(Impl.InputBuffer->getBufferStart()), + reinterpret_cast(Impl.InputBuffer->getBufferEnd())); + llvm::BitstreamCursor cursor(Impl.InputReader); + + // Validate signature. + for (auto byte : API_NOTES_SIGNATURE) { + if (cursor.AtEndOfStream() || cursor.Read(8) != byte) { + failed = true; + return; + } + } + + // Look at all of the blocks. + bool hasValidControlBlock = false; + SmallVector scratch; + auto topLevelEntry = cursor.advance(); + while (topLevelEntry.Kind == llvm::BitstreamEntry::SubBlock) { + switch (topLevelEntry.ID) { + case llvm::bitc::BLOCKINFO_BLOCK_ID: + if (cursor.ReadBlockInfoBlock()) { + failed = true; + break; + } + break; + + case CONTROL_BLOCK_ID: + // Only allow a single control block. + if (hasValidControlBlock || Impl.readControlBlock(cursor, scratch)) { + failed = true; + return; + } + + hasValidControlBlock = true; + break; + + case IDENTIFIER_BLOCK_ID: + if (!hasValidControlBlock || Impl.readIdentifierBlock(cursor, scratch)) { + failed = true; + return; + } + break; + + case OBJC_CONTEXT_BLOCK_ID: + if (!hasValidControlBlock || Impl.readObjCContextBlock(cursor, scratch)) { + failed = true; + return; + } + + break; + + case OBJC_PROPERTY_BLOCK_ID: + if (!hasValidControlBlock || + Impl.readObjCPropertyBlock(cursor, scratch)) { + failed = true; + return; + } + break; + + case OBJC_METHOD_BLOCK_ID: + if (!hasValidControlBlock || Impl.readObjCMethodBlock(cursor, scratch)) { + failed = true; + return; + } + break; + + case OBJC_SELECTOR_BLOCK_ID: + if (!hasValidControlBlock || + Impl.readObjCSelectorBlock(cursor, scratch)) { + failed = true; + return; + } + break; + + case GLOBAL_VARIABLE_BLOCK_ID: + if (!hasValidControlBlock || + Impl.readGlobalVariableBlock(cursor, scratch)) { + failed = true; + return; + } + break; + + case GLOBAL_FUNCTION_BLOCK_ID: + if (!hasValidControlBlock || + Impl.readGlobalFunctionBlock(cursor, scratch)) { + failed = true; + return; + } + break; + + default: + // Unknown top-level block, possibly for use by a future version of the + // module format. + if (cursor.SkipBlock()) { + failed = true; + return; + } + break; + } + + topLevelEntry = cursor.advance(llvm::BitstreamCursor::AF_DontPopBlockAtEnd); + } + + if (topLevelEntry.Kind != llvm::BitstreamEntry::EndBlock) { + failed = true; + return; + } +} + +APINotesReader::~APINotesReader() { + delete &Impl; +} + +std::unique_ptr +APINotesReader::get(std::unique_ptr inputBuffer) { + bool failed = false; + std::unique_ptr + reader(new APINotesReader(std::move(inputBuffer), failed)); + if (failed) + return nullptr; + + return reader; +} + +StringRef APINotesReader::getModuleName() const { + return Impl.ModuleName; +} + +auto APINotesReader::lookupObjCClass(StringRef name) + -> Optional> { + if (!Impl.ObjCContextTable) + return None; + + Optional classID = Impl.getIdentifier(name); + if (!classID) + return None; + + auto known = Impl.ObjCContextTable->find({*classID, '\0'}); + if (known == Impl.ObjCContextTable->end()) + return None; + + auto result = *known; + return std::make_pair(ContextID(result.first), result.second); +} + +auto APINotesReader::lookupObjCProtocol(StringRef name) + -> Optional> { + if (!Impl.ObjCContextTable) + return None; + + Optional classID = Impl.getIdentifier(name); + if (!classID) + return None; + + auto known = Impl.ObjCContextTable->find({*classID, '\1'}); + if (known == Impl.ObjCContextTable->end()) + return None; + + auto result = *known; + return std::make_pair(ContextID(result.first), result.second); +} + +Optional APINotesReader::lookupObjCProperty( + ContextID contextID, + StringRef name) { + if (!Impl.ObjCPropertyTable) + return None; + + Optional propertyID = Impl.getIdentifier(name); + if (!propertyID) + return None; + + auto known = Impl.ObjCPropertyTable->find({contextID.Value, *propertyID}); + if (known == Impl.ObjCPropertyTable->end()) + return None; + + return *known; +} + +Optional APINotesReader::lookupObjCMethod( + ContextID contextID, + ObjCSelectorRef selector, + bool isInstanceMethod) { + if (!Impl.ObjCMethodTable) + return None; + + Optional selectorID = Impl.getSelector(selector); + if (!selectorID) + return None; + + auto known = Impl.ObjCMethodTable->find( + ObjCMethodTableInfo::internal_key_type{ + contextID.Value, *selectorID, isInstanceMethod}); + if (known == Impl.ObjCMethodTable->end()) + return None; + + return *known; +} + +Optional APINotesReader::lookupGlobalVariable( + StringRef name) { + if (!Impl.GlobalVariableTable) + return None; + + Optional nameID = Impl.getIdentifier(name); + if (!nameID) + return None; + + auto known = Impl.GlobalVariableTable->find(*nameID); + if (known == Impl.GlobalVariableTable->end()) + return None; + + return *known; +} + +Optional APINotesReader::lookupGlobalFunction( + StringRef name) { + if (!Impl.GlobalFunctionTable) + return None; + + Optional nameID = Impl.getIdentifier(name); + if (!nameID) + return None; + + auto known = Impl.GlobalFunctionTable->find(*nameID); + if (known == Impl.GlobalFunctionTable->end()) + return None; + + return *known; +} +APINotesReader::Visitor::~Visitor() { } + +void APINotesReader::Visitor::visitObjCClass(ContextID contextID, + StringRef name, + const ObjCContextInfo &info) { } + +void APINotesReader::Visitor::visitObjCProtocol(ContextID contextID, + StringRef name, + const ObjCContextInfo &info) { } + +void APINotesReader::Visitor::visitObjCMethod(ContextID contextID, + StringRef selector, + bool isInstanceMethod, + const ObjCMethodInfo &info) { } + +void APINotesReader::Visitor::visitObjCProperty(ContextID contextID, + StringRef name, + const ObjCPropertyInfo &info) { } + +void APINotesReader::Visitor::visitGlobalVariable( + StringRef name, + const GlobalVariableInfo &info) { } + +void APINotesReader::Visitor::visitGlobalFunction( + StringRef name, + const GlobalFunctionInfo &info) { } + +void APINotesReader::visit(Visitor &visitor) { + // FIXME: All of these iterations would be significantly more efficient if we + // could get the keys and data together, but OnDiskIterableHashTable doesn't + // support that. + + // Build an identifier ID -> string mapping, which we'll need when visiting + // any of the tables. + llvm::DenseMap identifiers; + if (Impl.IdentifierTable) { + for (auto key : Impl.IdentifierTable->keys()) { + unsigned ID = *Impl.IdentifierTable->find(key); + assert(identifiers.count(ID) == 0); + identifiers[ID] = key; + } + } + + // Visit classes and protocols. + if (Impl.ObjCContextTable) { + for (auto key : Impl.ObjCContextTable->keys()) { + auto name = identifiers[key.first]; + auto info = *Impl.ObjCContextTable->find(key); + + if (key.second) + visitor.visitObjCProtocol(ContextID(info.first), name, info.second); + else + visitor.visitObjCClass(ContextID(info.first), name, info.second); + } + } + + // Build a selector ID -> stored Objective-C selector mapping, which we need + // when visiting the method tables. + llvm::DenseMap selectors; + if (Impl.ObjCSelectorTable) { + for (auto key : Impl.ObjCSelectorTable->keys()) { + std::string selector; + if (key.NumPieces == 0) + selector = identifiers[key.Identifiers[0]]; + else { + for (auto identID : key.Identifiers) { + selector += identifiers[identID]; + selector += ':'; + } + } + + unsigned selectorID = *Impl.ObjCSelectorTable->find(key); + selectors[selectorID] = selector; + } + } + + // Visit methods. + if (Impl.ObjCMethodTable) { + for (auto key : Impl.ObjCMethodTable->keys()) { + ContextID contextID(std::get<0>(key)); + const auto &selector = selectors[std::get<1>(key)]; + auto info = *Impl.ObjCMethodTable->find(key); + visitor.visitObjCMethod(contextID, selector, std::get<2>(key), info); + } + } + + // Visit properties. + if (Impl.ObjCPropertyTable) { + for (auto key : Impl.ObjCPropertyTable->keys()) { + ContextID contextID(key.first); + auto name = identifiers[key.second]; + auto info = *Impl.ObjCPropertyTable->find(key); + visitor.visitObjCProperty(contextID, name, info); + } + } + + // Visit global functions. + if (Impl.GlobalFunctionTable) { + for (auto key : Impl.GlobalFunctionTable->keys()) { + auto name = identifiers[key]; + auto info = *Impl.GlobalFunctionTable->find(key); + visitor.visitGlobalFunction(name, info); + } + } + + // Visit global variables. + if (Impl.GlobalVariableTable) { + for (auto key : Impl.GlobalVariableTable->keys()) { + auto name = identifiers[key]; + auto info = *Impl.GlobalVariableTable->find(key); + visitor.visitGlobalVariable(name, info); + } + } +} + diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp new file mode 100644 index 0000000000000..400bd08e01393 --- /dev/null +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -0,0 +1,851 @@ +//===--- APINotesWriter.cpp - API Notes Writer --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the \c APINotesWriter class that writes out +// source API notes data providing additional information about source +// code as a separate input, such as the non-nil/nilable annotations +// for method parameters. +// +//===----------------------------------------------------------------------===// +#include "clang/APINotes/APINotesWriter.h" +#include "APINotesFormat.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/OnDiskHashTable.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +using namespace clang; +using namespace api_notes; +using namespace llvm::support; + +class APINotesWriter::Implementation { + /// Mapping from strings to identifier IDs. + llvm::StringMap IdentifierIDs; + + /// Mapping from selectors to selector ID. + llvm::DenseMap SelectorIDs; + + /// Scratch space for bitstream writing. + SmallVector ScratchRecord; + +public: + /// The name of the module + std::string ModuleName; + + /// Information about Objective-C contexts (classes or protocols). + /// + /// Indexed by the identifier ID and a bit indication whether we're looking + /// for a class (0) or protocol (1) and provides both the context ID and + /// information describing the context within that module. + llvm::DenseMap, + std::pair> ObjCContexts; + + /// Mapping from context IDs to the identifier ID holding the name. + llvm::DenseMap ObjCContextNames; + + /// Information about Objective-C properties. + /// + /// Indexed by the context ID and property name. + llvm::DenseMap, ObjCPropertyInfo> + ObjCProperties; + + /// Information about Objective-C methods. + /// + /// Indexed by the context ID, selector ID, and Boolean (stored as a + /// char) indicating whether this is a class or instance method. + llvm::DenseMap, ObjCMethodInfo> + ObjCMethods; + + /// Information about global variables. + /// + /// Indexed by the identifier ID. + llvm::DenseMap GlobalVariables; + + /// Information about global functions. + /// + /// Indexed by the identifier ID. + llvm::DenseMap GlobalFunctions; + + /// Retrieve the ID for the given identifier. + IdentifierID getIdentifier(StringRef identifier) { + if (identifier.empty()) + return 0; + + auto known = IdentifierIDs.find(identifier); + if (known != IdentifierIDs.end()) + return known->second; + + // Add to the identifier table. + known = IdentifierIDs.insert({identifier, IdentifierIDs.size() + 1}).first; + return known->second; + } + + /// Retrieve the ID for the given selector. + SelectorID getSelector(ObjCSelectorRef selectorRef) { + // Translate the selector reference into a stored selector. + StoredObjCSelector selector; + selector.NumPieces = selectorRef.NumPieces; + selector.Identifiers.reserve(selectorRef.Identifiers.size()); + for (auto piece : selectorRef.Identifiers) { + selector.Identifiers.push_back(getIdentifier(piece)); + } + + // Look for the stored selector. + auto known = SelectorIDs.find(selector); + if (known != SelectorIDs.end()) + return known->second; + + // Add to the selector table. + known = SelectorIDs.insert({selector, SelectorIDs.size()}).first; + return known->second; + } + + void writeToStream(llvm::raw_ostream &os); + +private: + void writeBlockInfoBlock(llvm::BitstreamWriter &writer); + void writeControlBlock(llvm::BitstreamWriter &writer); + void writeIdentifierBlock(llvm::BitstreamWriter &writer); + void writeObjCContextBlock(llvm::BitstreamWriter &writer); + void writeObjCPropertyBlock(llvm::BitstreamWriter &writer); + void writeObjCMethodBlock(llvm::BitstreamWriter &writer); + void writeObjCSelectorBlock(llvm::BitstreamWriter &writer); + void writeGlobalVariableBlock(llvm::BitstreamWriter &writer); + void writeGlobalFunctionBlock(llvm::BitstreamWriter &writer); +}; + +/// Record the name of a block. +static void emitBlockID(llvm::BitstreamWriter &out, unsigned ID, + StringRef name, + SmallVectorImpl &nameBuffer) { + SmallVector idBuffer; + idBuffer.push_back(ID); + out.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETBID, idBuffer); + + // Emit the block name if present. + if (name.empty()) + return; + nameBuffer.resize(name.size()); + memcpy(nameBuffer.data(), name.data(), name.size()); + out.EmitRecord(llvm::bitc::BLOCKINFO_CODE_BLOCKNAME, nameBuffer); +} + +/// Record the name of a record within a block. +static void emitRecordID(llvm::BitstreamWriter &out, unsigned ID, + StringRef name, + SmallVectorImpl &nameBuffer) { + assert(ID < 256 && "can't fit record ID in next to name"); + nameBuffer.resize(name.size()+1); + nameBuffer[0] = ID; + memcpy(nameBuffer.data()+1, name.data(), name.size()); + out.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETRECORDNAME, nameBuffer); +} + +void APINotesWriter::Implementation::writeBlockInfoBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, llvm::bitc::BLOCKINFO_BLOCK_ID, 2); + + SmallVector nameBuffer; +#define BLOCK(X) emitBlockID(writer, X ## _ID, #X, nameBuffer) +#define BLOCK_RECORD(K, X) emitRecordID(writer, K::X, #X, nameBuffer) + + BLOCK(CONTROL_BLOCK); + BLOCK_RECORD(control_block, METADATA); + BLOCK_RECORD(control_block, MODULE_NAME); + + BLOCK(IDENTIFIER_BLOCK); + BLOCK_RECORD(identifier_block, IDENTIFIER_DATA); + + BLOCK(OBJC_CONTEXT_BLOCK); + BLOCK_RECORD(objc_context_block, OBJC_CONTEXT_DATA); + + BLOCK(OBJC_PROPERTY_BLOCK); + BLOCK_RECORD(objc_property_block, OBJC_PROPERTY_DATA); + + BLOCK(OBJC_METHOD_BLOCK); + BLOCK_RECORD(objc_method_block, OBJC_METHOD_DATA); + + BLOCK(OBJC_SELECTOR_BLOCK); + BLOCK_RECORD(objc_selector_block, OBJC_SELECTOR_DATA); + + BLOCK(GLOBAL_VARIABLE_BLOCK); + BLOCK_RECORD(global_variable_block, GLOBAL_VARIABLE_DATA); + + BLOCK(GLOBAL_FUNCTION_BLOCK); + BLOCK_RECORD(global_function_block, GLOBAL_FUNCTION_DATA); +#undef BLOCK +#undef BLOCK_RECORD +} + +void APINotesWriter::Implementation::writeControlBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, CONTROL_BLOCK_ID, 3); + control_block::MetadataLayout metadata(writer); + metadata.emit(ScratchRecord, VERSION_MAJOR, VERSION_MINOR); + + control_block::ModuleNameLayout moduleName(writer); + moduleName.emit(ScratchRecord, ModuleName); +} + +namespace { + /// Used to serialize the on-disk identifier table. + class IdentifierTableInfo { + public: + using key_type = StringRef; + using key_type_ref = key_type; + using data_type = IdentifierID; + using data_type_ref = const data_type &; + using hash_value_type = uint32_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return llvm::HashString(key); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = key.size(); + uint32_t dataLength = sizeof(uint32_t); + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + out << key; + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + static_assert(sizeof(IdentifierID) <= 4, "DeclID too large"); + endian::Writer writer(out); + writer.write(data); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeIdentifierBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, IDENTIFIER_BLOCK_ID, 3); + + if (IdentifierIDs.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : IdentifierIDs) + generator.insert(entry.first(), entry.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + identifier_block::IdentifierDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + +namespace { + /// Retrieve the serialized size of the given CommonEntityInfo, for use in + /// on-disk hash tables. + static unsigned getCommonEntityInfoSize(const CommonEntityInfo &info) { + return 3 + info.UnavailableMsg.size(); + } + + /// Emit a serialized representation of the common entity information. + static void emitCommonEntityInfo(raw_ostream &out, + const CommonEntityInfo &info) { + endian::Writer writer(out); + writer.write(info.Unavailable); + writer.write(info.UnavailableMsg.size()); + out.write(info.UnavailableMsg.c_str(), info.UnavailableMsg.size()); + } + + /// Used to serialize the on-disk Objective-C context table. + class ObjCContextTableInfo { + public: + using key_type = std::pair; // identifier ID, is-protocol + using key_type_ref = key_type; + using data_type = std::pair; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + /// The number of bytes in a data entry. + static const unsigned dataBytes = 3; + + hash_value_type ComputeHash(key_type_ref key) { + return static_cast(llvm::hash_value(key)); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(IdentifierID) + 1; + uint32_t dataLength = sizeof(ContextID) + + getCommonEntityInfoSize(data.second) + + dataBytes; + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key.first); + writer.write(key.second); + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + endian::Writer writer(out); + writer.write(data.first); + + emitCommonEntityInfo(out, data.second); + + // FIXME: Inefficient representation. + uint8_t bytes[dataBytes] = { 0, 0, 0 }; + if (auto nullable = data.second.getDefaultNullability()) { + bytes[0] = 1; + bytes[1] = static_cast(*nullable); + } else { + // Nothing to do. + } + bytes[2] = data.second.hasDesignatedInits(); + + out.write(reinterpret_cast(bytes), dataBytes); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeObjCContextBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, OBJC_CONTEXT_BLOCK_ID, 3); + + if (ObjCContexts.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : ObjCContexts) + generator.insert(entry.first, entry.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + objc_context_block::ObjCContextDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + +namespace { + /// Retrieve the serialized size of the given VariableInfo, for use in + /// on-disk hash tables. + static unsigned getVariableInfoSize(const VariableInfo &info) { + return 2 + getCommonEntityInfoSize(info); + } + + /// Emit a serialized representation of the variable information. + static void emitVariableInfo(raw_ostream &out, const VariableInfo &info) { + emitCommonEntityInfo(out, info); + + uint8_t bytes[2] = { 0, 0 }; + if (auto nullable = info.getNullability()) { + bytes[0] = 1; + bytes[1] = static_cast(*nullable); + } else { + // Nothing to do. + } + + out.write(reinterpret_cast(bytes), 2); + } + + /// Used to serialize the on-disk Objective-C property table. + class ObjCPropertyTableInfo { + public: + using key_type = std::pair; // (class ID, name ID) + using key_type_ref = key_type; + using data_type = ObjCPropertyInfo; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return static_cast(llvm::hash_value(key)); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(IdentifierID) + sizeof(IdentifierID); + uint32_t dataLength = getVariableInfoSize(data); + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key.first); + writer.write(key.second); + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + emitVariableInfo(out, data); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeObjCPropertyBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, OBJC_PROPERTY_BLOCK_ID, 3); + + if (ObjCProperties.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : ObjCProperties) + generator.insert(entry.first, entry.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + objc_property_block::ObjCPropertyDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + +namespace { + /// Retrieve the serialized size of the given FunctionInfo, for use in + /// on-disk hash tables. + static unsigned getFunctionInfoSize(const FunctionInfo &info) { + return 2 + sizeof(uint64_t) + getCommonEntityInfoSize(info); + } + + /// Emit a serialized representation of the function information. + static void emitFunctionInfo(raw_ostream &out, const FunctionInfo &info) { + emitCommonEntityInfo(out, info); + + endian::Writer writer(out); + writer.write(info.NullabilityAudited); + writer.write(info.NumAdjustedNullable); + writer.write(info.NullabilityPayload); + } + + /// Used to serialize the on-disk Objective-C method table. + class ObjCMethodTableInfo { + public: + // (class ID, selector ID, is-instance) + using key_type = std::tuple; + using key_type_ref = key_type; + using data_type = ObjCMethodInfo; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return llvm::hash_combine(std::get<0>(key), + std::get<1>(key), + std::get<2>(key)); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(IdentifierID) + sizeof(SelectorID) + 1; + uint32_t dataLength = getFunctionInfoSize(data) + 3; + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(std::get<0>(key)); + writer.write(std::get<1>(key)); + writer.write(std::get<2>(key)); + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + emitFunctionInfo(out, data); + + endian::Writer writer(out); + + // FIXME: Inefficient representation + writer.write(data.DesignatedInit); + writer.write(data.FactoryAsInit); + writer.write(data.Required); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeObjCMethodBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, OBJC_METHOD_BLOCK_ID, 3); + + if (ObjCMethods.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : ObjCMethods) { + generator.insert(entry.first, entry.second); + } + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + objc_method_block::ObjCMethodDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + +namespace { + /// Used to serialize the on-disk Objective-C selector table. + class ObjCSelectorTableInfo { + public: + using key_type = StoredObjCSelector; + using key_type_ref = const key_type &; + using data_type = SelectorID; + using data_type_ref = data_type; + using hash_value_type = unsigned; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return llvm::DenseMapInfo::getHashValue(key); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(uint16_t) + + sizeof(IdentifierID) * key.Identifiers.size(); + uint32_t dataLength = sizeof(SelectorID); + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key.NumPieces); + for (auto piece : key.Identifiers) { + writer.write(piece); + } + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + endian::Writer writer(out); + writer.write(data); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeObjCSelectorBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, OBJC_SELECTOR_BLOCK_ID, 3); + + if (SelectorIDs.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : SelectorIDs) + generator.insert(entry.first, entry.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + objc_selector_block::ObjCSelectorDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + +namespace { + /// Used to serialize the on-disk global variable table. + class GlobalVariableTableInfo { + public: + using key_type = unsigned; // name ID + using key_type_ref = key_type; + using data_type = GlobalVariableInfo; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return static_cast(llvm::hash_value(key)); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(IdentifierID); + uint32_t dataLength = getVariableInfoSize(data); + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key); + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + emitVariableInfo(out, data); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeGlobalVariableBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, GLOBAL_VARIABLE_BLOCK_ID, 3); + + if (GlobalVariables.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : GlobalVariables) + generator.insert(entry.first, entry.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + global_variable_block::GlobalVariableDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + +namespace { + /// Used to serialize the on-disk global function table. + class GlobalFunctionTableInfo { + public: + using key_type = unsigned; // name ID + using key_type_ref = key_type; + using data_type = GlobalFunctionInfo; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return llvm::hash_value(key); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(IdentifierID); + uint32_t dataLength = getFunctionInfoSize(data); + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key); + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + emitFunctionInfo(out, data); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeGlobalFunctionBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, GLOBAL_FUNCTION_BLOCK_ID, 3); + + if (GlobalFunctions.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : GlobalFunctions) { + generator.insert(entry.first, entry.second); + } + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + global_function_block::GlobalFunctionDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + +void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &os) { + // Write the API notes file into a buffer. + SmallVector buffer; + { + llvm::BitstreamWriter writer(buffer); + + // Emit the signature. + for (unsigned char byte : API_NOTES_SIGNATURE) + writer.Emit(byte, 8); + + // Emit the blocks. + writeBlockInfoBlock(writer); + writeControlBlock(writer); + writeIdentifierBlock(writer); + writeObjCContextBlock(writer); + writeObjCPropertyBlock(writer); + writeObjCMethodBlock(writer); + writeObjCSelectorBlock(writer); + writeGlobalVariableBlock(writer); + writeGlobalFunctionBlock(writer); + } + + // Write the buffer to the stream. + os.write(buffer.data(), buffer.size()); + os.flush(); +} + +APINotesWriter::APINotesWriter(StringRef moduleName) + : Impl(*new Implementation) +{ + Impl.ModuleName = moduleName; +} + +APINotesWriter::~APINotesWriter() { + delete &Impl; +} + + +void APINotesWriter::writeToStream(raw_ostream &os) { + Impl.writeToStream(os); +} + +ContextID APINotesWriter::addObjCClass(StringRef name, + const ObjCContextInfo &info) { + IdentifierID classID = Impl.getIdentifier(name); + + std::pair key(classID, 0); + auto known = Impl.ObjCContexts.find(key); + if (known != Impl.ObjCContexts.end()) { + known->second.second |= info; + } else { + unsigned nextID = Impl.ObjCContexts.size() + 1; + + known = Impl.ObjCContexts.insert( + std::make_pair(key, std::make_pair(nextID, info))) + .first; + + Impl.ObjCContextNames[nextID] = classID; + } + + return ContextID(known->second.first); +} + +ContextID APINotesWriter::addObjCProtocol(StringRef name, + const ObjCContextInfo &info) { + IdentifierID protocolID = Impl.getIdentifier(name); + + std::pair key(protocolID, 1); + auto known = Impl.ObjCContexts.find(key); + if (known != Impl.ObjCContexts.end()) { + known->second.second |= info; + } else { + unsigned nextID = Impl.ObjCContexts.size() + 1; + + known = Impl.ObjCContexts.insert( + std::make_pair(key, std::make_pair(nextID, info))) + .first; + + Impl.ObjCContextNames[nextID] = protocolID; + } + + return ContextID(known->second.first); +} +void APINotesWriter::addObjCProperty(ContextID contextID, StringRef name, + const ObjCPropertyInfo &info) { + IdentifierID nameID = Impl.getIdentifier(name); + assert(!Impl.ObjCProperties.count({contextID.Value, nameID})); + Impl.ObjCProperties[{contextID.Value, nameID}] = info; +} + +void APINotesWriter::addObjCMethod(ContextID contextID, + ObjCSelectorRef selector, + bool isInstanceMethod, + const ObjCMethodInfo &info) { + SelectorID selectorID = Impl.getSelector(selector); + auto key = std::tuple{ + contextID.Value, selectorID, isInstanceMethod}; + assert(!Impl.ObjCMethods.count(key)); + Impl.ObjCMethods[key] = info; + + // If this method is a designated initializer, update the class to note that + // it has designated initializers. + if (info.DesignatedInit) { + assert(Impl.ObjCContexts.count({Impl.ObjCContextNames[contextID.Value], + (char)0})); + Impl.ObjCContexts[{Impl.ObjCContextNames[contextID.Value], (char)0}] + .second.setHasDesignatedInits(true); + } +} + +void APINotesWriter::addGlobalVariable(llvm::StringRef name, + const GlobalVariableInfo &info) { + IdentifierID variableID = Impl.getIdentifier(name); + assert(!Impl.GlobalVariables.count(variableID)); + Impl.GlobalVariables[variableID] = info; +} + +void APINotesWriter::addGlobalFunction(llvm::StringRef name, + const GlobalFunctionInfo &info) { + IdentifierID nameID = Impl.getIdentifier(name); + assert(!Impl.GlobalFunctions.count(nameID)); + Impl.GlobalFunctions[nameID] = info; +} diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp new file mode 100644 index 0000000000000..1f299873ce31c --- /dev/null +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -0,0 +1,884 @@ +//===--- APINotesYAMLCompiler.cpp - API Notes YAML format reader *- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file reads API notes specified in YAML format. +// +//===----------------------------------------------------------------------===// +#include "clang/APINotes/APINotesYAMLCompiler.h" +#include "clang/APINotes/APINotesReader.h" +#include "clang/APINotes/Types.h" +#include "clang/APINotes/APINotesWriter.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" +#include + +/* + + YAML Format specification. + + Nullability should be expressed using one of the following values: + O - Optional (or Nullable) + N - Not Optional + S - Scalar + U - Unknown + Note, the API is considered 'audited' when at least the return value or a + parameter has a nullability value. For 'audited' APIs, we assume the default + nullability for any underspecified type. + + FactoryAsInit can have the following values: + C - Treat as class method. + I - Treat as initializer. + A - Automatically infer based on the name and type (default). + +--- + Name: AppKit # The name of the framework + + Availability: OSX # Optional: Specifies which platform the API is + # available on. [OSX / iOS / none/ + # available] + + AvailabilityMsg: "" # Optional: Custom availability message to display to + # the user, when API is not available. + + Classes: # List of classes + ... + Protocols: # List of protocols + ... + Functions: # List of functions + ... + Globals: # List of globals + ... + + Each class and protocol is defined as following: + + - Name: NSView # The name of the class + + AuditedForNullability: false # Optional: Specifies if the whole class + # has been audited for nullability. + # If yes, we assume all the methods and + # properties of the class have default + # nullability unless it is overwritten by + # a method/property specific info below. + # This applies to all classes, extensions, + # and categories of the class defined in + # the current framework/module. + # (false/true) + + Availability: OSX + + AvailabilityMsg: "" + + Methods: + - Selector: "setSubviews:" # Full name + + MethodKind: Instance # [Class/Instance] + + Nullability: [N, N, O, S] # The nullability of parameters in + # the signature. + + NullabilityOfRet: O # The nullability of the return value. + + Availability: OSX + + AvailabilityMsg: "" + + FactoryAsInit: C # Optional: Specifies if this method is a + # factory initializer (false/true) + DesignatedInit: false # Optional: Specifies if this method is a + # designated initializer (false/true) + + Required: false # Optional: Specifies if this method is a + # required initializer (false/true) + + Properties: + - Name: window + + Nullability: O + + Availability: OSX + + AvailabilityMsg: "" + + The protocol definition format is the same as the class definition. + + Each function definition is of the following form: + + - Name: "myGlobalFunction" # Full name + + Nullability: [N, N, O, S] # The nullability of parameters in + # the signature. + + NullabilityOfRet: O # The nullability of the return value. + + Availability: OSX + + AvailabilityMsg: "" + +Each global variable definition is of the following form: + + - Name: MyGlobalVar + + Nullability: O + + Availability: OSX + + AvailabilityMsg: "" + +*/ + +using llvm::StringRef; +using namespace clang; +namespace { + enum class APIAvailability { + Available = 0, + OSX, + IOS, + None, + }; + + enum class MethodKind { + Class, + Instance, + }; + + struct AvailabilityItem { + APIAvailability Mode = APIAvailability::Available; + StringRef Msg; + AvailabilityItem() : Mode(APIAvailability::Available), Msg("") {} + }; + + static llvm::Optional AbsentNullability = llvm::None; + static llvm::Optional DefaultNullability = + NullabilityKind::NonNull; + typedef std::vector NullabilitySeq; + + struct Method { + StringRef Selector; + MethodKind Kind; + NullabilitySeq Nullability; + llvm::Optional NullabilityOfRet; + AvailabilityItem Availability; + api_notes::FactoryAsInitKind FactoryAsInit + = api_notes::FactoryAsInitKind::Infer; + bool DesignatedInit = false; + bool Required = false; + }; + typedef std::vector MethodsSeq; + + struct Property { + StringRef Name; + llvm::Optional Nullability; + AvailabilityItem Availability; + }; + typedef std::vector PropertiesSeq; + + struct Class { + StringRef Name; + bool AuditedForNullability = false; + AvailabilityItem Availability; + MethodsSeq Methods; + PropertiesSeq Properties; + }; + typedef std::vector ClassesSeq; + + struct Function { + StringRef Name; + NullabilitySeq Nullability; + llvm::Optional NullabilityOfRet; + AvailabilityItem Availability; + }; + typedef std::vector FunctionsSeq; + + struct GlobalVariable { + StringRef Name; + llvm::Optional Nullability; + AvailabilityItem Availability; + }; + typedef std::vector GlobalVariablesSeq; + + struct Module { + StringRef Name; + AvailabilityItem Availability; + ClassesSeq Classes; + ClassesSeq Protocols; + FunctionsSeq Functions; + GlobalVariablesSeq Globals; + + LLVM_ATTRIBUTE_DEPRECATED( + void dump() LLVM_ATTRIBUTE_USED, + "only for use within the debugger"); + }; +} + +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(clang::NullabilityKind) +LLVM_YAML_IS_SEQUENCE_VECTOR(Method) +LLVM_YAML_IS_SEQUENCE_VECTOR(Property) +LLVM_YAML_IS_SEQUENCE_VECTOR(Class) +LLVM_YAML_IS_SEQUENCE_VECTOR(Function) +LLVM_YAML_IS_SEQUENCE_VECTOR(GlobalVariable) + +namespace llvm { + namespace yaml { + + template <> + struct ScalarEnumerationTraits { + static void enumeration(IO &io, NullabilityKind &value) { + io.enumCase(value, "N", NullabilityKind::NonNull); + io.enumCase(value, "O", NullabilityKind::Nullable); + io.enumCase(value, "U", NullabilityKind::Unspecified); + // TODO: Mapping this to it's own value would allow for better cross + // checking. Also the default should be Unknown. + io.enumCase(value, "S", NullabilityKind::Unspecified); + } + }; + + template <> + struct ScalarEnumerationTraits { + static void enumeration(IO &io, api_notes::FactoryAsInitKind &value) { + io.enumCase(value, "A", api_notes::FactoryAsInitKind::Infer); + io.enumCase(value, "C", api_notes::FactoryAsInitKind::AsClassMethod); + io.enumCase(value, "I", api_notes::FactoryAsInitKind::AsInitializer); + } + }; + + template <> + struct ScalarEnumerationTraits { + static void enumeration(IO &io, MethodKind &value) { + io.enumCase(value, "Class", MethodKind::Class); + io.enumCase(value, "Instance", MethodKind::Instance); + } + }; + + template <> + struct ScalarEnumerationTraits { + static void enumeration(IO &io, APIAvailability &value) { + io.enumCase(value, "OSX", APIAvailability::OSX); + io.enumCase(value, "iOS", APIAvailability::IOS); + io.enumCase(value, "none", APIAvailability::None); + io.enumCase(value, "available", APIAvailability::Available); + } + }; + + template <> + struct MappingTraits { + static void mapping(IO &io, Property& p) { + io.mapRequired("Name", p.Name); + io.mapOptional("Nullability", p.Nullability, + AbsentNullability); + io.mapOptional("Availability", p.Availability.Mode); + io.mapOptional("AvailabilityMsg", p.Availability.Msg); + } + }; + + template <> + struct MappingTraits { + static void mapping(IO &io, Method& m) { + io.mapRequired("Selector", m.Selector); + io.mapRequired("MethodKind", m.Kind); + io.mapOptional("Nullability", m.Nullability); + io.mapOptional("NullabilityOfRet", m.NullabilityOfRet, + AbsentNullability); + io.mapOptional("Availability", m.Availability.Mode); + io.mapOptional("AvailabilityMsg", m.Availability.Msg); + io.mapOptional("FactoryAsInit", m.FactoryAsInit, + api_notes::FactoryAsInitKind::Infer); + io.mapOptional("DesignatedInit", m.DesignatedInit, false); + io.mapOptional("Required", m.Required, false); + } + }; + + template <> + struct MappingTraits { + static void mapping(IO &io, Class& c) { + io.mapRequired("Name", c.Name); + io.mapOptional("AuditedForNullability", c.AuditedForNullability, false); + io.mapOptional("Availability", c.Availability.Mode); + io.mapOptional("AvailabilityMsg", c.Availability.Msg); + io.mapOptional("Methods", c.Methods); + io.mapOptional("Properties", c.Properties); + } + }; + + template <> + struct MappingTraits { + static void mapping(IO &io, Function& f) { + io.mapRequired("Name", f.Name); + io.mapOptional("Nullability", f.Nullability); + io.mapOptional("NullabilityOfRet", f.NullabilityOfRet, + AbsentNullability); + io.mapOptional("Availability", f.Availability.Mode); + io.mapOptional("AvailabilityMsg", f.Availability.Msg); + } + }; + + template <> + struct MappingTraits { + static void mapping(IO &io, GlobalVariable& v) { + io.mapRequired("Name", v.Name); + io.mapOptional("Nullability", v.Nullability, + AbsentNullability); + io.mapOptional("Availability", v.Availability.Mode); + io.mapOptional("AvailabilityMsg", v.Availability.Msg); + } + }; + + template <> + struct MappingTraits { + static void mapping(IO &io, Module& m) { + io.mapRequired("Name", m.Name); + io.mapOptional("Availability", m.Availability.Mode); + io.mapOptional("AvailabilityMsg", m.Availability.Msg); + io.mapOptional("Classes", m.Classes); + io.mapOptional("Protocols", m.Protocols); + io.mapOptional("Functions", m.Functions); + io.mapOptional("Globals", m.Globals); + } + }; + } +} + +using llvm::yaml::Input; +using llvm::yaml::Output; + +void Module::dump() { + Output yout(llvm::errs()); + yout << *this; +} + +static bool parseAPINotes(StringRef yamlInput, Module &module, + llvm::SourceMgr::DiagHandlerTy diagHandler, + void *diagHandlerCtxt) { + Input yin(yamlInput, nullptr, diagHandler, diagHandlerCtxt); + yin >> module; + + return static_cast(yin.error()); +} + +static bool compile(const Module &module, + llvm::raw_ostream &os, + api_notes::OSType targetOS, + llvm::SourceMgr::DiagHandlerTy diagHandler, + void *diagHandlerCtxt){ + using namespace api_notes; + + class YAMLConverter { + const Module &TheModule; + APINotesWriter *Writer; + OSType TargetOS; + llvm::raw_ostream &OS; + llvm::SourceMgr::DiagHandlerTy DiagHandler; + void *DiagHandlerCtxt; + bool ErrorOccured; + + /// Emit a diagnostic + bool emitError(llvm::Twine message) { + DiagHandler(llvm::SMDiagnostic("", llvm::SourceMgr::DK_Error, + message.str()), + DiagHandlerCtxt); + ErrorOccured = true; + return true; + } + + public: + YAMLConverter(const Module &module, + OSType targetOS, + llvm::raw_ostream &os, + llvm::SourceMgr::DiagHandlerTy diagHandler, + void *diagHandlerCtxt) : + TheModule(module), Writer(0), TargetOS(targetOS), OS(os), + DiagHandler(diagHandler), DiagHandlerCtxt(diagHandlerCtxt), + ErrorOccured(false) {} + + bool isAvailable(const AvailabilityItem &in) { + // Check if the API is available on the OS for which we are building. + if (in.Mode == APIAvailability::OSX && TargetOS != OSType::OSX) + return false; + if (in.Mode == APIAvailability::IOS && TargetOS != OSType::IOS) + return false; + return true; + } + + bool convertAvailability(const AvailabilityItem &in, + CommonEntityInfo &outInfo, + llvm::StringRef apiName) { + // Populate the 'Unavailable' information. + outInfo.Unavailable = (in.Mode == APIAvailability::None); + if (outInfo.Unavailable) { + outInfo.UnavailableMsg = in.Msg; + } else { + if (!in.Msg.empty()) { + emitError("availability message for available class '" + + apiName + "' will not be used"); + } + } + return false; + } + + void convertNullability(const NullabilitySeq &nullability, + Optional nullabilityOfRet, + FunctionInfo &outInfo, + llvm::StringRef apiName) { + if (nullability.size() > FunctionInfo::getMaxNullabilityIndex()) { + emitError("nullability info for " + apiName + " does not fit"); + return; + } + + bool audited = false; + unsigned int idx = 1; + for (auto i = nullability.begin(), + e = nullability.end(); i != e; ++i, ++idx){ + outInfo.addTypeInfo(idx, *i); + audited = true; + } + if (nullabilityOfRet) { + outInfo.addTypeInfo(0, *nullabilityOfRet); + audited = true; + } else if (audited) { + outInfo.addTypeInfo(0, *DefaultNullability); + } + if (audited) { + outInfo.NullabilityAudited = audited; + outInfo.NumAdjustedNullable = idx; + } + } + + // Translate from Method into ObjCMethodInfo and write it out. + void convertMethod(const Method &meth, + ContextID classID, StringRef className) { + ObjCMethodInfo mInfo; + + if (!isAvailable(meth.Availability)) + return; + + convertAvailability(meth.Availability, mInfo, meth.Selector); + + // Check if the selector ends with ':' to determine if it takes arguments. + bool takesArguments = meth.Selector.endswith(":"); + + // Split the selector into pieces. + llvm::SmallVector a; + meth.Selector.split(a, ":", /*MaxSplit*/ -1, /*KeepEmpty*/ false); + if (!takesArguments && a.size() > 1 ) { + emitError("selector " + meth.Selector + "is missing a ':' at the end"); + return; + } + + // Construct ObjCSelectorRef. + api_notes::ObjCSelectorRef selectorRef; + selectorRef.NumPieces = !takesArguments ? 0 : a.size(); + selectorRef.Identifiers = a; + + // Translate the initializer info. + mInfo.DesignatedInit = meth.DesignatedInit; + mInfo.Required = meth.Required; + if (meth.FactoryAsInit != FactoryAsInitKind::Infer) + mInfo.setFactoryAsInitKind(meth.FactoryAsInit); + + // Translate nullability info. + convertNullability(meth.Nullability, meth.NullabilityOfRet, + mInfo, meth.Selector); + + // Write it. + Writer->addObjCMethod(classID, selectorRef, + meth.Kind == MethodKind::Instance, + mInfo); + } + + void convertContext(const Class &cl, bool isClass) { + // Write the class. + ObjCContextInfo cInfo; + + // First, translate and check availability info. + if (!isAvailable(cl.Availability)) + return; + + convertAvailability(cl.Availability, cInfo, cl.Name); + + if (cl.AuditedForNullability) + cInfo.setDefaultNullability(*DefaultNullability); + + ContextID clID = isClass ? Writer->addObjCClass(cl.Name, cInfo) : + Writer->addObjCProtocol(cl.Name, cInfo); + + // Write all methods. + llvm::StringMap> knownMethods; + for (const auto &method : cl.Methods) { + // Check for duplicate method definitions. + bool isInstanceMethod = method.Kind == MethodKind::Instance; + bool &known = isInstanceMethod ? knownMethods[method.Selector].first + : knownMethods[method.Selector].second; + if (known) { + emitError(llvm::Twine("duplicate definition of method '") + + (isInstanceMethod? "-" : "+") + "[" + cl.Name + " " + + method.Selector + "]'"); + continue; + } + known = true; + + convertMethod(method, clID, cl.Name); + } + + // Write all properties. + llvm::StringSet<> knownProperties; + for (const auto &prop : cl.Properties) { + // Check for duplicate property definitions. + if (!knownProperties.insert(prop.Name).second) { + emitError("duplicate definition of property '" + cl.Name + "." + + prop.Name + "'"); + continue; + } + + // Translate from Property into ObjCPropertyInfo. + ObjCPropertyInfo pInfo; + if (!isAvailable(prop.Availability)) + continue; + convertAvailability(prop.Availability, pInfo, prop.Name); + if (prop.Nullability) + pInfo.setNullabilityAudited(*prop.Nullability); + Writer->addObjCProperty(clID, prop.Name, pInfo); + } + } + + bool convertModule() { + if (!isAvailable(TheModule.Availability)) + return false; + + // Set up the writer. + // FIXME: This is kindof ugly. + APINotesWriter writer(TheModule.Name); + Writer = &writer; + + // Write all classes. + llvm::StringSet<> knownClasses; + for (const auto &cl : TheModule.Classes) { + // Check for duplicate class definitions. + if (!knownClasses.insert(cl.Name).second) { + emitError("multiple definitions of class '" + cl.Name + "'"); + continue; + } + + convertContext(cl, /*isClass*/ true); + } + + // Write all protocols. + llvm::StringSet<> knownProtocols; + for (const auto &pr : TheModule.Protocols) { + // Check for duplicate protocol definitions. + if (!knownProtocols.insert(pr.Name).second) { + emitError("multiple definitions of protocol '" + pr.Name + "'"); + continue; + } + + convertContext(pr, /*isClass*/ false); + } + + // Write all global variables. + llvm::StringSet<> knownGlobals; + for (const auto &global : TheModule.Globals) { + // Check for duplicate global variables. + if (!knownGlobals.insert(global.Name).second) { + emitError("multiple definitions of global variable '" + + global.Name + "'"); + continue; + } + + GlobalVariableInfo info; + if (!isAvailable(global.Availability)) + continue; + convertAvailability(global.Availability, info, global.Name); + if (global.Nullability) + info.setNullabilityAudited(*global.Nullability); + Writer->addGlobalVariable(global.Name, info); + } + + // Write all global functions. + llvm::StringSet<> knownFunctions; + for (const auto &function : TheModule.Functions) { + // Check for duplicate global functions. + if (!knownFunctions.insert(function.Name).second) { + emitError("multiple definitions of global function '" + + function.Name + "'"); + continue; + } + + GlobalFunctionInfo info; + if (!isAvailable(function.Availability)) + continue; + convertAvailability(function.Availability, info, function.Name); + convertNullability(function.Nullability, + function.NullabilityOfRet, + info, function.Name); + + Writer->addGlobalFunction(function.Name, info); + } + + if (!ErrorOccured) + Writer->writeToStream(OS); + + return ErrorOccured; + } + }; + + YAMLConverter c(module, targetOS, os, diagHandler, diagHandlerCtxt); + return c.convertModule(); +} + +bool api_notes::parseAndDumpAPINotes(StringRef yamlInput) { + Module module; + + if (parseAPINotes(yamlInput, module, nullptr, nullptr)) + return true; + + Output yout(llvm::outs()); + yout << module; + + return false; +} + +/// Simple diagnostic handler that prints diagnostics to standard error. +static void printDiagnostic(const llvm::SMDiagnostic &diag, void *context) { + diag.print(nullptr, llvm::errs()); +} + +bool api_notes::compileAPINotes(StringRef yamlInput, + llvm::raw_ostream &os, + OSType targetOS, + llvm::SourceMgr::DiagHandlerTy diagHandler, + void *diagHandlerCtxt) { + Module module; + + if (!diagHandler) { + diagHandler = &printDiagnostic; + } + + if (parseAPINotes(yamlInput, module, diagHandler, diagHandlerCtxt)) + return true; + + return compile(module, os, targetOS, diagHandler, diagHandlerCtxt); +} + +bool api_notes::decompileAPINotes(std::unique_ptr input, + llvm::raw_ostream &os) { + // Try to read the file. + auto reader = APINotesReader::get(std::move(input)); + if (!reader) { + llvm::errs() << "not a well-formed API notes binary file\n"; + return true; + } + + // Deserialize the API notes file into a module. + class DecompileVisitor : public APINotesReader::Visitor { + /// Allocator used to clone those strings that need it. + llvm::BumpPtrAllocator Allocator; + + /// The module we're building. + Module TheModule; + + /// A mapping from context ID to a pair (index, is-protocol) that indicates + /// the index of that class or protocol in the global "classes" or + /// "protocols" list. + llvm::DenseMap> knownContexts; + + /// Copy a string into allocated memory so it does disappear on us. + StringRef copyString(StringRef string) { + if (string.empty()) return StringRef(); + + void *ptr = Allocator.Allocate(string.size(), 1); + memcpy(ptr, string.data(), string.size()); + return StringRef(reinterpret_cast(ptr), string.size()); + } + + /// Map Objective-C context info. + void handleObjCContext(Class &record, StringRef name, + const ObjCContextInfo &info) { + record.Name = name; + + // Handle class information. + handleAvailability(record.Availability, info); + if (info.getDefaultNullability()) { + record.AuditedForNullability = true; + } + } + + /// Map availability information, if present. + void handleAvailability(AvailabilityItem &availability, + const CommonEntityInfo &info) { + if (info.Unavailable) { + availability.Mode = APIAvailability::None; + availability.Msg = copyString(info.UnavailableMsg); + } + } + + /// Map nullability information for a function. + void handleNullability(NullabilitySeq &nullability, + llvm::Optional &nullabilityOfRet, + const FunctionInfo &info, + unsigned numParams) { + if (info.NullabilityAudited) { + nullabilityOfRet = info.getReturnTypeInfo(); + + // Figure out the number of parameters from the selector. + for (unsigned i = 0; i != numParams; ++i) + nullability.push_back(info.getParamTypeInfo(i)); + } + } + + public: + virtual void visitObjCClass(ContextID contextID, StringRef name, + const ObjCContextInfo &info) { + // Record this known context. + knownContexts[contextID.Value] = { TheModule.Classes.size(), false }; + + // Add the class. + TheModule.Classes.push_back(Class()); + handleObjCContext(TheModule.Classes.back(), name, info); + } + + virtual void visitObjCProtocol(ContextID contextID, StringRef name, + const ObjCContextInfo &info) { + // Record this known context. + knownContexts[contextID.Value] = { TheModule.Protocols.size(), true }; + + // Add the protocol. + TheModule.Protocols.push_back(Class()); + handleObjCContext(TheModule.Protocols.back(), name, info); + } + + virtual void visitObjCMethod(ContextID contextID, StringRef selector, + bool isInstanceMethod, + const ObjCMethodInfo &info) { + Method method; + method.Selector = copyString(selector); + method.Kind = isInstanceMethod ? MethodKind::Instance : MethodKind::Class; + + handleNullability(method.Nullability, method.NullabilityOfRet, info, + selector.count(':')); + handleAvailability(method.Availability, info); + method.FactoryAsInit = info.getFactoryAsInitKind(); + method.DesignatedInit = info.DesignatedInit; + method.Required = info.Required; + + auto known = knownContexts[contextID.Value]; + if (known.second) + TheModule.Protocols[known.first].Methods.push_back(method); + else + TheModule.Classes[known.first].Methods.push_back(method); + } + + virtual void visitObjCProperty(ContextID contextID, StringRef name, + const ObjCPropertyInfo &info) { + Property property; + property.Name = name; + handleAvailability(property.Availability, info); + + // FIXME: No way to represent "not audited for nullability". + if (auto nullability = info.getNullability()) { + property.Nullability = *nullability; + } + + auto known = knownContexts[contextID.Value]; + if (known.second) + TheModule.Protocols[known.first].Properties.push_back(property); + else + TheModule.Classes[known.first].Properties.push_back(property); + } + + virtual void visitGlobalFunction(StringRef name, + const GlobalFunctionInfo &info) { + Function function; + function.Name = name; + handleAvailability(function.Availability, info); + if (info.NumAdjustedNullable > 0) + handleNullability(function.Nullability, function.NullabilityOfRet, + info, info.NumAdjustedNullable-1); + + TheModule.Functions.push_back(function); + } + + virtual void visitGlobalVariable(StringRef name, + const GlobalVariableInfo &info) { + GlobalVariable global; + global.Name = name; + handleAvailability(global.Availability, info); + + // FIXME: No way to represent "not audited for nullability". + if (auto nullability = info.getNullability()) { + global.Nullability = *nullability; + } + + TheModule.Globals.push_back(global); + } + + /// Retrieve the module. + Module &getModule() { return TheModule; } + } decompileVisitor; + + reader->visit(decompileVisitor); + + // Sort the data in the module, because the API notes reader doesn't preserve + // order. + auto &module = decompileVisitor.getModule(); + + // Set module name. + module.Name = reader->getModuleName(); + + // Sort classes. + std::sort(module.Classes.begin(), module.Classes.end(), + [](const Class &lhs, const Class &rhs) -> bool { + return lhs.Name < rhs.Name; + }); + + // Sort protocols. + std::sort(module.Protocols.begin(), module.Protocols.end(), + [](const Class &lhs, const Class &rhs) -> bool { + return lhs.Name < rhs.Name; + }); + + // Sort methods and properties within each class and protocol. + auto sortMembers = [](Class &record) { + // Sort properties. + std::sort(record.Properties.begin(), record.Properties.end(), + [](const Property &lhs, const Property &rhs) -> bool { + return lhs.Name < rhs.Name; + }); + + // Sort methods. + std::sort(record.Methods.begin(), record.Methods.end(), + [](const Method &lhs, const Method &rhs) -> bool { + return lhs.Selector < rhs.Selector || + (lhs.Selector == rhs.Selector && + static_cast(lhs.Kind) + < static_cast(rhs.Kind)); + }); + }; + std::for_each(module.Classes.begin(), module.Classes.end(), sortMembers); + std::for_each(module.Protocols.begin(), module.Protocols.end(), sortMembers); + + // Sort functions. + std::sort(module.Functions.begin(), module.Functions.end(), + [](const Function &lhs, const Function &rhs) -> bool { + return lhs.Name < rhs.Name; + }); + + // Sort global variables. + std::sort(module.Globals.begin(), module.Globals.end(), + [](const GlobalVariable &lhs, const GlobalVariable &rhs) -> bool { + return lhs.Name < rhs.Name; + }); + + // Output the YAML representation. + Output yout(os); + yout << module; + + return false; +} + diff --git a/clang/lib/APINotes/CMakeLists.txt b/clang/lib/APINotes/CMakeLists.txt new file mode 100644 index 0000000000000..da9d0d1e55078 --- /dev/null +++ b/clang/lib/APINotes/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS + BitReader + Support + ) + +add_clang_library(clangAPINotes + APINotesManager.cpp + APINotesWriter.cpp + APINotesReader.cpp + APINotesYAMLCompiler.cpp + Types.cpp + + LINK_LIBS + clangBasic +) diff --git a/clang/lib/APINotes/Makefile b/clang/lib/APINotes/Makefile new file mode 100644 index 0000000000000..69ddcd45b3198 --- /dev/null +++ b/clang/lib/APINotes/Makefile @@ -0,0 +1,18 @@ +##===- clang/lib/APINotes/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 the APINotes library for the C-Language front-end. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../.. +LIBRARYNAME := clangAPINotes + +include $(CLANG_LEVEL)/Makefile + diff --git a/clang/lib/APINotes/Types.cpp b/clang/lib/APINotes/Types.cpp new file mode 100644 index 0000000000000..963780fb6f32c --- /dev/null +++ b/clang/lib/APINotes/Types.cpp @@ -0,0 +1,55 @@ +//===--- Types.cpp - API Notes Data Types ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines data types used in the representation of API notes data. +// +//===----------------------------------------------------------------------===// +#include "clang/APINotes/Types.h" +#include "llvm/Support/raw_ostream.h" + +void clang::api_notes::ObjCMethodInfo::dump(llvm::raw_ostream &os) { + os << DesignatedInit << " " << FactoryAsInit << " " << Unavailable << " " + << NullabilityAudited << " " << NumAdjustedNullable << " " + << NullabilityPayload << " " << UnavailableMsg << "\n"; +} + +void clang::api_notes::ObjCContextInfo::dump(llvm::raw_ostream &os) { + os << HasDefaultNullability << " " << DefaultNullability << " " + << HasDesignatedInits << "\n"; +} + +void clang::api_notes::ObjCMethodInfo::mergePropInfoIntoSetter( + const ObjCPropertyInfo &pInfo) { + // Set the type of the first argument of the the setter or check that the + // value we have is consistent with the property. + // TODO: Can we provide proper error handling here? + if (auto pNullability = pInfo.getNullability()) { + if (!NullabilityAudited) { + addParamTypeInfo(0, *pNullability); + assert(NumAdjustedNullable == 2); + } else { + assert(getParamTypeInfo(0) == *pNullability); + } + } +} + +void clang::api_notes::ObjCMethodInfo::mergePropInfoIntoGetter( + const ObjCPropertyInfo &pInfo) { + // Set the return type of the getter or check that the value we have is + // consistent with the property. + // TODO: Can we provide proper error handling here? + if (auto pNullability = pInfo.getNullability()) { + if (!NullabilityAudited) { + addReturnTypeInfo(*pNullability); + assert(NumAdjustedNullable == 1); + } else { + assert(getReturnTypeInfo() == *pNullability); + } + } +} diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp index fd6df024bb850..3da2546a7d8c8 100644 --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -371,11 +371,14 @@ bool Decl::isReferenced() const { /// /// FIXME: Make these strings localizable, since they end up in /// diagnostics. -static AvailabilityResult CheckAvailability(ASTContext &Context, - const AvailabilityAttr *A, - std::string *Message) { - VersionTuple TargetMinVersion = - Context.getTargetInfo().getPlatformMinVersion(); +static AvailabilityResult +checkAvailability(ASTContext &Context, const AvailabilityAttr *A, + Optional Version, std::string *Message) { + VersionTuple TargetMinVersion; + if (Version.hasValue()) + TargetMinVersion = Version.getValue(); + else + TargetMinVersion = Context.getTargetInfo().getPlatformMinVersion(); if (TargetMinVersion.empty()) return AR_Available; @@ -466,7 +469,8 @@ static AvailabilityResult CheckAvailability(ASTContext &Context, return AR_Available; } -AvailabilityResult Decl::getAvailability(std::string *Message) const { +AvailabilityResult Decl::getAvailability(std::string *Message, + Optional Version) const { AvailabilityResult Result = AR_Available; std::string ResultMessage; @@ -489,8 +493,8 @@ AvailabilityResult Decl::getAvailability(std::string *Message) const { } if (const auto *Availability = dyn_cast(A)) { - AvailabilityResult AR = CheckAvailability(getASTContext(), Availability, - Message); + AvailabilityResult AR = checkAvailability(getASTContext(), Availability, + Version, Message); if (AR == AR_Unavailable) return AR_Unavailable; @@ -549,7 +553,7 @@ bool Decl::isWeakImported() const { return true; if (const auto *Availability = dyn_cast(A)) { - if (CheckAvailability(getASTContext(), Availability, + if (checkAvailability(getASTContext(), Availability, None, nullptr) == AR_NotYetIntroduced) return true; } diff --git a/clang/lib/Basic/CMakeLists.txt b/clang/lib/Basic/CMakeLists.txt index cfad8c3649edd..f5e7f74c56c49 100644 --- a/clang/lib/Basic/CMakeLists.txt +++ b/clang/lib/Basic/CMakeLists.txt @@ -74,6 +74,7 @@ add_clang_library(clangBasic Sanitizers.cpp SourceLocation.cpp SourceManager.cpp + SourceMgrAdapter.cpp TargetInfo.cpp Targets.cpp TokenKinds.cpp diff --git a/clang/lib/Basic/SourceMgrAdapter.cpp b/clang/lib/Basic/SourceMgrAdapter.cpp new file mode 100644 index 0000000000000..1d52a2438594b --- /dev/null +++ b/clang/lib/Basic/SourceMgrAdapter.cpp @@ -0,0 +1,137 @@ +//=== SourceMgrAdapter.cpp - SourceMgr to SourceManager Adapter -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the adapter that maps diagnostics from llvm::SourceMgr +// to Clang's SourceManager. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/SourceMgrAdapter.h" +#include "clang/Basic/Diagnostic.h" + +using namespace clang; + +void SourceMgrAdapter::handleDiag(const llvm::SMDiagnostic &diag, + void *context) { + static_cast(context)->handleDiag(diag); +} + +SourceMgrAdapter::SourceMgrAdapter(SourceManager &srcMgr, + DiagnosticsEngine &diag, + unsigned errorDiagID, + unsigned warningDiagID, + unsigned noteDiagID, + const FileEntry *defaultFile) + : SrcMgr(srcMgr), Diag(diag), ErrorDiagID(errorDiagID), + WarningDiagID(warningDiagID), NoteDiagID(noteDiagID), + DefaultFile(defaultFile) { } + +SourceMgrAdapter::~SourceMgrAdapter() { } + +SourceLocation SourceMgrAdapter::mapLocation(const llvm::SourceMgr &llvmSrcMgr, + llvm::SMLoc loc) { + // Map invalid locations. + if (!loc.isValid()) + return SourceLocation(); + + // Find the buffer containing the location. + unsigned bufferID = llvmSrcMgr.FindBufferContainingLoc(loc); + if (!bufferID) + return SourceLocation(); + + + // If we haven't seen this buffer before, copy it over. + auto buffer = llvmSrcMgr.getMemoryBuffer(bufferID); + auto knownBuffer = FileIDMapping.find(std::make_pair(&llvmSrcMgr, bufferID)); + if (knownBuffer == FileIDMapping.end()) { + FileID fileID; + if (DefaultFile) { + // Map to the default file. + fileID = SrcMgr.createFileID(DefaultFile, SourceLocation(), + SrcMgr::C_User); + + // Only do this once. + DefaultFile = nullptr; + } else { + // Make a copy of the memory buffer. + StringRef bufferName = buffer->getBufferIdentifier(); + auto bufferCopy + = std::unique_ptr( + llvm::MemoryBuffer::getMemBufferCopy(buffer->getBuffer(), + bufferName)); + + // Add this memory buffer to the Clang source manager. + fileID = SrcMgr.createFileID(std::move(bufferCopy)); + } + + // Save the mapping. + knownBuffer = FileIDMapping.insert( + std::make_pair(std::make_pair(&llvmSrcMgr, bufferID), + fileID)).first; + } + + // Translate the offset into the file. + unsigned offset = loc.getPointer() - buffer->getBufferStart(); + return SrcMgr.getLocForStartOfFile(knownBuffer->second) + .getLocWithOffset(offset); +} + +SourceRange SourceMgrAdapter::mapRange(const llvm::SourceMgr &llvmSrcMgr, + llvm::SMRange range) { + if (!range.isValid()) + return SourceRange(); + + SourceLocation start = mapLocation(llvmSrcMgr, range.Start); + SourceLocation end = mapLocation(llvmSrcMgr, range.End); + return SourceRange(start, end); +} + +void SourceMgrAdapter::handleDiag(const llvm::SMDiagnostic &diag) { + // Map the location. + SourceLocation loc; + if (auto *llvmSrcMgr = diag.getSourceMgr()) + loc = mapLocation(*llvmSrcMgr, diag.getLoc()); + + // Extract the message. + StringRef message = diag.getMessage(); + + // Map the diagnostic kind. + unsigned diagID; + switch (diag.getKind()) { + case llvm::SourceMgr::DK_Error: + diagID = ErrorDiagID; + break; + + case llvm::SourceMgr::DK_Warning: + diagID = WarningDiagID; + break; + + case llvm::SourceMgr::DK_Note: + diagID = NoteDiagID; + break; + } + + // Report the diagnostic. + DiagnosticBuilder builder = Diag.Report(loc, diagID) << message; + + if (auto *llvmSrcMgr = diag.getSourceMgr()) { + // Translate ranges. + SourceLocation startOfLine = loc.getLocWithOffset(-diag.getColumnNo()); + for (auto range : diag.getRanges()) { + builder << SourceRange(startOfLine.getLocWithOffset(range.first), + startOfLine.getLocWithOffset(range.second)); + } + + // Translate Fix-Its. + for (const llvm::SMFixIt &fixIt : diag.getFixIts()) { + CharSourceRange range(mapRange(*llvmSrcMgr, fixIt.getRange()), false); + builder << FixItHint::CreateReplacement(range, fixIt.getText()); + } + } +} diff --git a/clang/lib/CMakeLists.txt b/clang/lib/CMakeLists.txt index dfd819a407e98..574c511747f40 100644 --- a/clang/lib/CMakeLists.txt +++ b/clang/lib/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(Headers) add_subdirectory(Basic) +add_subdirectory(APINotes) add_subdirectory(Lex) add_subdirectory(Parse) add_subdirectory(AST) diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 1e5db24042987..c9588e679ef88 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -569,8 +569,9 @@ class CGObjCGNU : public CGObjCRuntime { return NULLPtr; } - llvm::GlobalVariable *GetClassGlobal(const std::string &Name, - bool Weak = false) override { + llvm::Constant *GetClassGlobal(const std::string &Name, + bool ForDefinition, + bool Weak) override { return nullptr; } }; diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp index 4eaa2e51f25db..cff3a77207f30 100644 --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -1255,8 +1255,9 @@ class CGObjCMac : public CGObjCCommonMac { /// GetClassGlobal - Return the global variable for the Objective-C /// class of the given name. - llvm::GlobalVariable *GetClassGlobal(const std::string &Name, - bool Weak = false) override { + llvm::Constant *GetClassGlobal(const std::string &Name, + bool ForDefinition, + bool Weak) override { llvm_unreachable("CGObjCMac::GetClassGlobal"); } }; @@ -1357,8 +1358,9 @@ class CGObjCNonFragileABIMac : public CGObjCCommonMac { /// GetClassGlobal - Return the global variable for the Objective-C /// class of the given name. - llvm::GlobalVariable *GetClassGlobal(const std::string &Name, - bool Weak = false) override; + llvm::Constant *GetClassGlobal(const std::string &Name, + bool ForDefinition, + bool Weak) override; /// EmitClassRef - Return a Value*, of type ObjCTypes.ClassPtrTy, /// for the given class reference. @@ -5857,7 +5859,12 @@ llvm::GlobalVariable *CGObjCNonFragileABIMac::BuildClassMetaData( llvm::PointerType::getUnqual(ObjCTypes.ImpnfABITy)); llvm::Constant *Init = llvm::ConstantStruct::get(ObjCTypes.ClassnfABITy, Values); - llvm::GlobalVariable *GV = GetClassGlobal(ClassName, Weak); + llvm::GlobalVariable *GV = cast( + GetClassGlobal(ClassName, + /*ForDefinition=*/true, + Weak)); + if (Init->getType() != GV->getValueType()) + Init = llvm::ConstantExpr::getBitCast(Init, GV->getValueType()); GV->setInitializer(Init); GV->setSection("__DATA, __objc_data"); GV->setAlignment( @@ -5926,7 +5933,7 @@ void CGObjCNonFragileABIMac::GenerateClass(const ObjCImplementationDecl *ID) { llvm::SmallString<64> ObjCClassName(getClassSymbolPrefix()); llvm::SmallString<64> TClassName; - llvm::GlobalVariable *SuperClassGV, *IsAGV; + llvm::Constant *SuperClassGV, *IsAGV; // Build the flags for the metaclass. bool classIsHidden = @@ -5948,10 +5955,12 @@ void CGObjCNonFragileABIMac::GenerateClass(const ObjCImplementationDecl *ID) { TClassName = ObjCClassName; TClassName += ClassName; SuperClassGV = GetClassGlobal(TClassName.str(), + /*ForDefinition=*/false, ID->getClassInterface()->isWeakImported()); TClassName = ObjCMetaClassName; TClassName += ClassName; IsAGV = GetClassGlobal(TClassName.str(), + /*ForDefinition=*/false, ID->getClassInterface()->isWeakImported()); } else { // Has a root. Current class is not a root. @@ -5961,6 +5970,7 @@ void CGObjCNonFragileABIMac::GenerateClass(const ObjCImplementationDecl *ID) { TClassName = ObjCMetaClassName ; TClassName += Root->getObjCRuntimeNameAsString(); IsAGV = GetClassGlobal(TClassName.str(), + /*ForDefinition=*/false, Root->isWeakImported()); // work on super class metadata symbol. @@ -5968,6 +5978,7 @@ void CGObjCNonFragileABIMac::GenerateClass(const ObjCImplementationDecl *ID) { TClassName += ID->getClassInterface()->getSuperClass()->getObjCRuntimeNameAsString(); SuperClassGV = GetClassGlobal( TClassName.str(), + /*ForDefinition=*/false, ID->getClassInterface()->getSuperClass()->isWeakImported()); } llvm::GlobalVariable *CLASS_RO_GV = BuildClassRoTInitializer(flags, @@ -6010,6 +6021,7 @@ void CGObjCNonFragileABIMac::GenerateClass(const ObjCImplementationDecl *ID) { TClassName += ID->getClassInterface()->getSuperClass()->getObjCRuntimeNameAsString(); SuperClassGV = GetClassGlobal( TClassName.str(), + /*ForDefinition=*/false, ID->getClassInterface()->getSuperClass()->isWeakImported()); } GetClassSizeInfo(ID, InstanceStart, InstanceSize); @@ -6102,8 +6114,9 @@ void CGObjCNonFragileABIMac::GenerateCategory(const ObjCCategoryImplDecl *OCD) { llvm::Constant *Values[6]; Values[0] = GetClassName(OCD->getIdentifier()->getName()); // meta-class entry symbol - llvm::GlobalVariable *ClassGV = - GetClassGlobal(ExtClassName.str(), Interface->isWeakImported()); + llvm::Constant *ClassGV = GetClassGlobal(ExtClassName.str(), + /*ForDefinition=*/false, + Interface->isWeakImported()); Values[1] = ClassGV; std::vector Methods; @@ -6774,8 +6787,10 @@ CGObjCNonFragileABIMac::GenerateMessageSend(CodeGen::CodeGenFunction &CGF, false, CallArgs, Method, Class, ObjCTypes); } -llvm::GlobalVariable * -CGObjCNonFragileABIMac::GetClassGlobal(const std::string &Name, bool Weak) { +llvm::Constant * +CGObjCNonFragileABIMac::GetClassGlobal(const std::string &Name, + bool ForDefinition, + bool Weak) { llvm::GlobalValue::LinkageTypes L = Weak ? llvm::GlobalValue::ExternalWeakLinkage : llvm::GlobalValue::ExternalLinkage; @@ -6787,7 +6802,12 @@ CGObjCNonFragileABIMac::GetClassGlobal(const std::string &Name, bool Weak) { false, L, nullptr, Name); assert(GV->getLinkage() == L); - return GV; + + if (ForDefinition || + GV->getValueType() == ObjCTypes.ClassnfABITy) + return GV; + + return llvm::ConstantExpr::getBitCast(GV, ObjCTypes.ClassnfABIPtrTy); } llvm::Value *CGObjCNonFragileABIMac::EmitClassRefFromId(CodeGenFunction &CGF, @@ -6801,7 +6821,9 @@ llvm::Value *CGObjCNonFragileABIMac::EmitClassRefFromId(CodeGenFunction &CGF, std::string ClassName( getClassSymbolPrefix() + (ID ? ID->getObjCRuntimeNameAsString() : II->getName()).str()); - llvm::GlobalVariable *ClassGV = GetClassGlobal(ClassName, Weak); + llvm::Constant *ClassGV = GetClassGlobal(ClassName, + /*ForDefinition=*/false, + Weak); Entry = new llvm::GlobalVariable(CGM.getModule(), ObjCTypes.ClassnfABIPtrTy, false, llvm::GlobalValue::PrivateLinkage, ClassGV, "OBJC_CLASSLIST_REFERENCES_$_"); @@ -6832,8 +6854,9 @@ CGObjCNonFragileABIMac::EmitSuperClassRef(CodeGenFunction &CGF, if (!Entry) { llvm::SmallString<64> ClassName(getClassSymbolPrefix()); ClassName += ID->getObjCRuntimeNameAsString(); - llvm::GlobalVariable *ClassGV = GetClassGlobal(ClassName.str(), - ID->isWeakImported()); + llvm::Constant *ClassGV = GetClassGlobal(ClassName.str(), + /*ForDefinition=*/false, + ID->isWeakImported()); Entry = new llvm::GlobalVariable(CGM.getModule(), ObjCTypes.ClassnfABIPtrTy, false, llvm::GlobalValue::PrivateLinkage, ClassGV, "OBJC_CLASSLIST_SUP_REFS_$_"); @@ -6855,8 +6878,9 @@ llvm::Value *CGObjCNonFragileABIMac::EmitMetaClassRef(CodeGenFunction &CGF, if (!Entry) { llvm::SmallString<64> MetaClassName(getMetaclassSymbolPrefix()); MetaClassName += ID->getObjCRuntimeNameAsString(); - llvm::GlobalVariable *MetaClassGV = - GetClassGlobal(MetaClassName.str(), Weak); + llvm::Constant *MetaClassGV = GetClassGlobal(MetaClassName.str(), + /*ForDefinition=*/false, + Weak); Entry = new llvm::GlobalVariable(CGM.getModule(), ObjCTypes.ClassnfABIPtrTy, false, llvm::GlobalValue::PrivateLinkage, @@ -6877,7 +6901,10 @@ llvm::Value *CGObjCNonFragileABIMac::GetClass(CodeGenFunction &CGF, if (ID->isWeakImported()) { llvm::SmallString<64> ClassName(getClassSymbolPrefix()); ClassName += ID->getObjCRuntimeNameAsString(); - llvm::GlobalVariable *ClassGV = GetClassGlobal(ClassName.str(), true); + llvm::GlobalVariable *ClassGV = cast( + GetClassGlobal(ClassName.str(), + /*ForDefinition=*/true, + /*Weak=*/true)); (void)ClassGV; assert(ClassGV->hasExternalWeakLinkage()); } @@ -7179,11 +7206,15 @@ CGObjCNonFragileABIMac::GetInterfaceEHType(const ObjCInterfaceDecl *ID, llvm::Value *VTableIdx = llvm::ConstantInt::get(CGM.Int32Ty, 2); + llvm::Constant *ClassGV = GetClassGlobal(ClassName.str(), + /*ForDefinition=*/false, + /*Weak=*/false); + llvm::Constant *Values[] = { llvm::ConstantExpr::getGetElementPtr(VTableGV->getValueType(), VTableGV, VTableIdx), GetClassName(ID->getObjCRuntimeNameAsString()), - GetClassGlobal(ClassName.str())}; + ClassGV}; llvm::Constant *Init = llvm::ConstantStruct::get(ObjCTypes.EHTypeTy, Values); diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h index 9b0706770aa90..2c81f49ecefd3 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.h +++ b/clang/lib/CodeGen/CGObjCRuntime.h @@ -277,8 +277,9 @@ class CGObjCRuntime { const CodeGen::CGBlockInfo &blockInfo) = 0; virtual llvm::Constant *BuildByrefLayout(CodeGen::CodeGenModule &CGM, QualType T) = 0; - virtual llvm::GlobalVariable *GetClassGlobal(const std::string &Name, - bool Weak = false) = 0; + virtual llvm::Constant *GetClassGlobal(const std::string &Name, + bool ForDefinition, + bool Weak) = 0; struct MessageSendInfo { const CGFunctionInfo &CallInfo; diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 911953437bd15..a607a4ce63f93 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -2904,7 +2904,9 @@ CodeGenModule::GetAddrOfConstantString(const StringLiteral *Literal) { std::string str = StringClass.empty() ? "OBJC_CLASS_$_NSConstantString" : "OBJC_CLASS_$_" + StringClass; - GV = getObjCRuntime().GetClassGlobal(str); + GV = getObjCRuntime().GetClassGlobal(str, + /*ForDefinition=*/false, + /*Weak=*/false); // Make sure the result is of the correct type. llvm::Type *PTy = llvm::PointerType::getUnqual(Ty); V = llvm::ConstantExpr::getBitCast(GV, PTy); diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp index 22904e5398a0b..25ce1a82e35f9 100644 --- a/clang/lib/Driver/Job.cpp +++ b/clang/lib/Driver/Job.cpp @@ -72,7 +72,8 @@ static int skipArgs(const char *Flag, bool HaveCrashVFS) { // These flags are treated as a single argument (e.g., -F). StringRef FlagRef(Flag); if (FlagRef.startswith("-F") || FlagRef.startswith("-I") || - FlagRef.startswith("-fmodules-cache-path=")) + FlagRef.startswith("-fmodules-cache-path=") || + FlagRef.startswith("-fapinotes-cache-path=")) return 1; return 0; diff --git a/clang/lib/Driver/Tools.cpp b/clang/lib/Driver/Tools.cpp index 03d4c6be61206..98904bec70e06 100644 --- a/clang/lib/Driver/Tools.cpp +++ b/clang/lib/Driver/Tools.cpp @@ -4479,6 +4479,33 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, options::OPT_fno_assume_sane_operator_new)) CmdArgs.push_back("-fno-assume-sane-operator-new"); + if (Args.hasFlag(options::OPT_fapinotes, options::OPT_fno_apinotes, + false)) { + CmdArgs.push_back("-fapinotes"); + + SmallString<128> APINotesCachePath; + if (Arg *A = Args.getLastArg(options::OPT_fapinotes_cache_path)) { + APINotesCachePath = A->getValue(); + } + + if (C.isForDiagnostics()) { + // When generating crash reports, we want to emit the API notes along with + // the reproduction sources, so we ignore any provided API notes path. + APINotesCachePath = Output.getFilename(); + llvm::sys::path::replace_extension(APINotesCachePath, ".cache"); + llvm::sys::path::append(APINotesCachePath, "apinotes"); + } else if (APINotesCachePath.empty()) { + // No API notes path was provided: use the default. + llvm::sys::path::system_temp_directory(/*erasedOnReboot=*/false, + APINotesCachePath); + llvm::sys::path::append(APINotesCachePath, "org.llvm.clang"); + llvm::sys::path::append(APINotesCachePath, "APINotesCache"); + } + const char Arg[] = "-fapinotes-cache-path="; + APINotesCachePath.insert(APINotesCachePath.begin(), Arg, Arg + strlen(Arg)); + CmdArgs.push_back(Args.MakeArgString(APINotesCachePath)); + } + // -fblocks=0 is default. if (Args.hasFlag(options::OPT_fblocks, options::OPT_fno_blocks, getToolChain().IsBlocksDefault()) || diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 7844b100d9160..d78d99da708c7 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -832,6 +832,7 @@ bool clang::ParseDiagnosticArgs(DiagnosticOptions &Opts, ArgList &Args, static void ParseFileSystemArgs(FileSystemOptions &Opts, ArgList &Args) { Opts.WorkingDir = Args.getLastArgValue(OPT_working_directory); + Opts.APINotesCachePath = Args.getLastArgValue(OPT_fapinotes_cache_path); } /// Parse the argument to the -ftest-module-file-extension @@ -1682,6 +1683,7 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK, std::sort(Opts.ModuleFeatures.begin(), Opts.ModuleFeatures.end()); Opts.NativeHalfType |= Args.hasArg(OPT_fnative_half_type); Opts.HalfArgsAndReturns = Args.hasArg(OPT_fallow_half_arguments_and_returns); + Opts.APINotes = Args.hasArg(OPT_fapinotes); Opts.GNUAsm = !Args.hasArg(OPT_fno_gnu_inline_asm); // __declspec is enabled by default for the PS4 by the driver, and also @@ -1999,6 +2001,13 @@ bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Res, ParseLangArgs(*Res.getLangOpts(), Args, DashX, Diags); if (Res.getFrontendOpts().ProgramAction == frontend::RewriteObjC) Res.getLangOpts()->ObjCExceptions = 1; + + // -fapinotes requires -fapinotes-cache-path=. + if (Res.getLangOpts()->APINotes && + Res.getFileSystemOpts().APINotesCachePath.empty()) { + Diags.Report(diag::err_no_apinotes_cache_path); + Success = false; + } } // FIXME: ParsePreprocessorArgs uses the FileManager to read the contents of // PCH file and find the original header name. Remove the need to do that in diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp index 2497e5ec38bca..a1b99545dd074 100644 --- a/clang/lib/Lex/PPMacroExpansion.cpp +++ b/clang/lib/Lex/PPMacroExpansion.cpp @@ -1064,6 +1064,7 @@ static bool HasFeature(const Preprocessor &PP, const IdentifierInfo *II) { .Case("attribute_availability_with_version_underscores", true) .Case("attribute_availability_tvos", true) .Case("attribute_availability_watchos", true) + .Case("attribute_availability_swift", true) .Case("attribute_cf_returns_not_retained", true) .Case("attribute_cf_returns_retained", true) .Case("attribute_cf_returns_on_parameters", true) diff --git a/clang/lib/Makefile b/clang/lib/Makefile index acf8089629e41..e627d5a3a8101 100755 --- a/clang/lib/Makefile +++ b/clang/lib/Makefile @@ -9,9 +9,9 @@ CLANG_LEVEL := .. # ARCMigrate and Rewrite are always needed because of libclang. -PARALLEL_DIRS = Headers Basic Lex Parse AST Sema CodeGen Analysis Frontend \ - FrontendTool Tooling Driver Format Edit Rewrite Serialization \ - Index ASTMatchers +PARALLEL_DIRS = Headers Basic APINotes Lex Parse AST Sema CodeGen Analysis \ + Frontend FrontendTool Tooling Driver Format Edit Rewrite \ + Serialization Index ASTMatchers include $(CLANG_LEVEL)/../../Makefile.config diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp index ab1f97d31a690..5084aee0b7510 100644 --- a/clang/lib/Parse/ParseCXXInlineMethods.cpp +++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp @@ -47,6 +47,7 @@ NamedDecl *Parser::ParseCXXInlineMethodDef(AccessSpecifier AS, VS, ICIS_NoInit); if (FnD) { Actions.ProcessDeclAttributeList(getCurScope(), FnD, AccessAttrs); + Actions.ProcessAPINotes(FnD); if (PureSpecLoc.isValid()) Actions.ActOnPureSpecifier(FnD, PureSpecLoc); } diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 1ef53b36d7be7..c24d6a59f3085 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -2583,8 +2583,10 @@ Parser::ParseCXXClassMemberDeclaration(AccessSpecifier AS, // initialize it. ThisDecl = VT->getTemplatedDecl(); - if (ThisDecl && AccessAttrs) + if (ThisDecl) { Actions.ProcessDeclAttributeList(getCurScope(), ThisDecl, AccessAttrs); + Actions.ProcessAPINotes(ThisDecl); + } } // Error recovery might have converted a non-static member into a static diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp index 4ac8f42f1361a..cfbde0900a7d7 100644 --- a/clang/lib/Parse/ParseObjc.cpp +++ b/clang/lib/Parse/ParseObjc.cpp @@ -207,6 +207,8 @@ void Parser::CheckNestedObjCContexts(SourceLocation AtLoc) /// __attribute__((unavailable)) /// __attribute__((objc_exception)) - used by NSException on 64-bit /// __attribute__((objc_root_class)) +/// __attribute__((objc_subclassing_restricted)) +/// __attribute__((objc_complete_definition)) /// Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc, ParsedAttributes &attrs) { diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index 8aa005102fe4b..1f49e8411917e 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -16,6 +16,7 @@ add_clang_library(clangSema Sema.cpp SemaAccess.cpp SemaAttr.cpp + SemaAPINotes.cpp SemaCXXScopeSpec.cpp SemaCast.cpp SemaChecking.cpp @@ -57,4 +58,5 @@ add_clang_library(clangSema clangBasic clangEdit clangLex + clangAPINotes ) diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index f96430f1ecc98..54622923f7a36 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -77,7 +77,7 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, isMultiplexExternalSource(false), FPFeatures(pp.getLangOpts()), LangOpts(pp.getLangOpts()), PP(pp), Context(ctxt), Consumer(consumer), Diags(PP.getDiagnostics()), SourceMgr(PP.getSourceManager()), - CollectStats(false), CodeCompleter(CodeCompleter), + APINotes(SourceMgr), CollectStats(false), CodeCompleter(CodeCompleter), CurContext(nullptr), OriginalLexicalContext(nullptr), PackContext(nullptr), MSStructPragmaOn(false), MSPointerToMemberRepresentationMethod( diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp new file mode 100644 index 0000000000000..3114805da208b --- /dev/null +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -0,0 +1,359 @@ +//===--- SemaAPINotes.cpp - API Notes Handling ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the mapping from API notes to declaration attributes. +// +//===----------------------------------------------------------------------===// + +#include "clang/Sema/SemaInternal.h" +#include "clang/AST/DeclObjC.h" +#include "clang/APINotes/APINotesReader.h" +using namespace clang; + +/// Determine whether this is a multi-level pointer type. +static bool isMultiLevelPointerType(QualType type) { + QualType pointee = type->getPointeeType(); + if (pointee.isNull()) + return false; + + return pointee->isAnyPointerType() || pointee->isObjCObjectPointerType() || + pointee->isMemberPointerType(); +} + +// Apply nullability to the given declaration. +static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability) { + QualType type; + + // Nullability for a function/method appertains to the retain type. + if (auto function = dyn_cast(decl)) { + type = function->getReturnType(); + } else if (auto method = dyn_cast(decl)) { + type = method->getReturnType(); + } else if (auto value = dyn_cast(decl)) { + type = value->getType(); + } else if (auto property = dyn_cast(decl)) { + type = property->getType(); + } else { + return; + } + + // Check the nullability specifier on this type. + QualType origType = type; + S.checkNullabilityTypeSpecifier(type, nullability, decl->getLocation(), + /*isContextSensitive=*/false, + /*implicit=*/true); + if (type.getTypePtr() == origType.getTypePtr()) + return; + + if (auto function = dyn_cast(decl)) { + const FunctionType *fnType = function->getType()->castAs(); + if (const FunctionProtoType *proto = dyn_cast(fnType)) { + function->setType(S.Context.getFunctionType(type, proto->getParamTypes(), + proto->getExtProtoInfo())); + } else { + function->setType(S.Context.getFunctionNoProtoType(type, + fnType->getExtInfo())); + } + } else if (auto method = dyn_cast(decl)) { + method->setReturnType(type); + + // Make it a context-sensitive keyword if we can. + if (!isMultiLevelPointerType(type)) { + method->setObjCDeclQualifier( + Decl::ObjCDeclQualifier(method->getObjCDeclQualifier() | + Decl::OBJC_TQ_CSNullability)); + } + } else if (auto value = dyn_cast(decl)) { + value->setType(type); + + // Make it a context-sensitive keyword if we can. + if (auto parm = dyn_cast(decl)) { + if (parm->isObjCMethodParameter() && !isMultiLevelPointerType(type)) { + parm->setObjCDeclQualifier( + Decl::ObjCDeclQualifier(parm->getObjCDeclQualifier() | + Decl::OBJC_TQ_CSNullability)); + } + } + } else if (auto property = dyn_cast(decl)) { + property->setType(type, property->getTypeSourceInfo()); + + // Make it a property attribute if we can. + if (!isMultiLevelPointerType(type)) { + property->setPropertyAttributes( + ObjCPropertyDecl::OBJC_PR_null_resettable); + } + } else { + llvm_unreachable("cannot handle nullability here"); + } +} + +static void ProcessAPINotes(Sema &S, Decl *D, + const api_notes::CommonEntityInfo &Info) { + // Availability + if (Info.Unavailable && !D->hasAttr()) { + D->addAttr(UnavailableAttr::CreateImplicit(S.Context, Info.UnavailableMsg)); + } +} + +/// Process API notes for a variable or property. +static void ProcessAPINotes(Sema &S, Decl *D, + const api_notes::VariableInfo &Info) { + // Nullability. + if (auto Nullability = Info.getNullability()) { + applyNullability(S, D, *Nullability); + } + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info)); +} + +/// Process API notes for a global variable. +static void ProcessAPINotes(Sema &S, VarDecl *D, + const api_notes::GlobalVariableInfo &Info) { + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info)); +} + +/// Process API notes for an Objective-C property. +static void ProcessAPINotes(Sema &S, ObjCPropertyDecl *D, + const api_notes::ObjCPropertyInfo &Info) { + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info)); +} + +namespace { + typedef llvm::PointerUnion FunctionOrMethod; +} + +/// Process API notes for a function or method. +static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, + const api_notes::FunctionInfo &Info) { + // Find the declaration itself. + FunctionDecl *FD = AnyFunc.dyn_cast(); + Decl *D = FD; + ObjCMethodDecl *MD = 0; + if (!D) { + MD = AnyFunc.get(); + D = MD; + } + + // Nullability. + if (Info.NullabilityAudited) { + // Return type. + applyNullability(S, D, Info.getReturnTypeInfo()); + + // Parameters. + unsigned NumParams; + if (FD) + NumParams = FD->getNumParams(); + else + NumParams = MD->param_size(); + + for (unsigned I = 0; I != NumParams; ++I) { + ParmVarDecl *Param; + if (FD) + Param = FD->getParamDecl(I); + else + Param = MD->param_begin()[I]; + + applyNullability(S, Param, Info.getParamTypeInfo(I)); + } + } + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info)); +} + +/// Process API notes for a global function. +static void ProcessAPINotes(Sema &S, FunctionDecl *D, + const api_notes::GlobalFunctionInfo &Info) { + + // Handle common function information. + ProcessAPINotes(S, FunctionOrMethod(D), + static_cast(Info)); +} + +/// Process API notes for an Objective-C method. +static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, + const api_notes::ObjCMethodInfo &Info) { + // Designated initializers. + if (Info.DesignatedInit && !D->getAttr()) { + if (ObjCInterfaceDecl *IFace = D->getClassInterface()) { + D->addAttr(ObjCDesignatedInitializerAttr::CreateImplicit(S.Context)); + IFace->setHasDesignatedInitializers(); + } + } + + // Handle common function information. + ProcessAPINotes(S, FunctionOrMethod(D), + static_cast(Info)); +} + +/// Process API notes for an Objective-C class or protocol. +static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, + const api_notes::ObjCContextInfo &Info) { + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info)); +} + +/// Process API notes that are associated with this declaration, mapping them +/// to attributes as appropriate. +void Sema::ProcessAPINotes(Decl *D) { + if (!Context.getLangOpts().APINotes) + return; + + if (!D || D->getLocation().isInvalid()) + return; + + // Globals. + if (D->getDeclContext()->isFileContext()) { + // Global variables. + if (auto VD = dyn_cast(D)) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Info = Reader->lookupGlobalVariable(VD->getName())) { + ::ProcessAPINotes(*this, VD, *Info); + } + } + + return; + } + + // Global functions. + if (auto FD = dyn_cast(D)) { + if (FD->getDeclName().isIdentifier()) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Info = Reader->lookupGlobalFunction(FD->getName())) { + ::ProcessAPINotes(*this, FD, *Info); + } + } + } + + return; + } + + // Objective-C classes. + if (auto Class = dyn_cast(D)) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Info = Reader->lookupObjCClass(Class->getName())) { + ::ProcessAPINotes(*this, Class, Info->second); + } + } + + return; + } + + // Objective-C protocols. + if (auto Protocol = dyn_cast(D)) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Info = Reader->lookupObjCProtocol(Protocol->getName())) { + ::ProcessAPINotes(*this, Protocol, Info->second); + } + } + + return; + } + + return; + } + + if (auto ObjCContainer = dyn_cast(D->getDeclContext())) { + // Location function that looks up an Objective-C context. + auto GetContext = [&](api_notes::APINotesReader *Reader) + -> Optional { + if (auto Protocol = dyn_cast(ObjCContainer)) { + if (auto Found = Reader->lookupObjCProtocol(Protocol->getName())) + return Found->first; + + return None; + } + + if (auto Impl = dyn_cast(ObjCContainer)) { + if (auto Cat = Impl->getCategoryDecl()) + ObjCContainer = Cat; + else + return None; + } + + if (auto Category = dyn_cast(ObjCContainer)) { + if (Category->getClassInterface()) + ObjCContainer = Category->getClassInterface(); + else + return None; + } + + if (auto Impl = dyn_cast(ObjCContainer)) { + if (Impl->getClassInterface()) + ObjCContainer = Impl->getClassInterface(); + else + return None; + } + + if (auto Class = dyn_cast(ObjCContainer)) { + if (auto Found = Reader->lookupObjCClass(Class->getName())) + return Found->first; + + return None; + + } + + return None; + }; + + // Objective-C methods. + if (auto Method = dyn_cast(D)) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Context = GetContext(Reader)) { + // Map the selector. + Selector Sel = Method->getSelector(); + SmallVector SelPieces; + if (Sel.isUnarySelector()) + SelPieces.push_back(Sel.getNameForSlot(0)); + else { + for (unsigned i = 0, n = Sel.getNumArgs(); i != n; ++i) + SelPieces.push_back(Sel.getNameForSlot(i)); + } + + api_notes::ObjCSelectorRef SelectorRef; + SelectorRef.NumPieces = Sel.getNumArgs(); + SelectorRef.Identifiers = SelPieces; + + if (auto Info = Reader->lookupObjCMethod(*Context, SelectorRef, + Method->isInstanceMethod())){ + ::ProcessAPINotes(*this, Method, *Info); + } + } + } + } + + // Objective-C properties. + if (auto Property = dyn_cast(D)) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Context = GetContext(Reader)) { + if (auto Info = Reader->lookupObjCProperty(*Context, + Property->getName())) { + ::ProcessAPINotes(*this, Property, *Info); + } + } + } + + return; + } + + return; + } +} diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 6297efb73a20b..20beddffaacf2 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2216,6 +2216,10 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D, NewAttr = S.mergeMinSizeAttr(D, MA->getRange(), AttrSpellingListIndex); else if (const auto *OA = dyn_cast(Attr)) NewAttr = S.mergeOptimizeNoneAttr(D, OA->getRange(), AttrSpellingListIndex); + else if (const auto *SNA = dyn_cast(Attr)) + NewAttr = S.mergeSwiftNameAttr(D, SNA->getRange(), SNA->getName(), + AMK == Sema::AMK_Override, + AttrSpellingListIndex); else if (isa(Attr)) // AlignedAttrs are handled separately, because we need to handle all // such attributes on a declaration at the same time. @@ -2224,6 +2228,8 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D, (AMK == Sema::AMK_Override || AMK == Sema::AMK_ProtocolImplementation)) NewAttr = nullptr; + else if (isa(Attr) && AMK == Sema::AMK_Override) + NewAttr = nullptr; else if (Attr->duplicatesAllowed() || !DeclHasAttr(D, Attr)) NewAttr = cast(Attr->clone(S.Context)); @@ -11185,6 +11191,7 @@ void Sema::ActOnFinishDelayedAttribute(Scope *S, Decl *D, if (TemplateDecl *TD = dyn_cast(D)) D = TD->getTemplatedDecl(); ProcessDeclAttributeList(S, D, Attrs.getList()); + ProcessAPINotes(D); if (CXXMethodDecl *Method = dyn_cast_or_null(D)) if (Method->isStatic()) @@ -12469,6 +12476,7 @@ Decl *Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, if (Attr) ProcessDeclAttributeList(S, New, Attr); + ProcessAPINotes(New); // Set the lexical context. If the tag has a C++ scope specifier, the // lexical context will be different from the semantic context. @@ -13674,6 +13682,7 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl, if (Attr) ProcessDeclAttributeList(S, Record, Attr); + ProcessAPINotes(Record); } /// \brief Determine whether the given integral value is representable within @@ -13967,6 +13976,7 @@ Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst, if (New) { // Process attributes. if (Attr) ProcessDeclAttributeList(S, New, Attr); + ProcessAPINotes(New); // Register this decl in the current scope stack. New->setAccess(TheEnumDecl->getAccess()); @@ -14192,6 +14202,7 @@ void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceLocation LBraceLoc, if (Attr) ProcessDeclAttributeList(S, Enum, Attr); + ProcessAPINotes(Enum); if (Enum->isDependentType()) { for (unsigned i = 0, e = Elements.size(); i != e; ++i) { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 6d9d7d4aaab72..5dee760fe356f 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1249,6 +1249,26 @@ static void handleReturnsNonNullAttr(Sema &S, Decl *D, Attr.getAttributeSpellingListIndex())); } +static void handleNoEscapeAttr(Sema &S, Decl *D, const AttributeList &Attr) { + ParmVarDecl *PD = dyn_cast(D); + if (!PD) + return; + + // noescape only applies to pointer types. + QualType T = PD->getType(); + if (!T->isAnyPointerType() && !T->isBlockPointerType() && + !T->isReferenceType() && !T->isArrayType() && + !T->isMemberPointerType()) { + S.Diag(Attr.getLoc(), diag::warn_attribute_noescape_non_pointer) + << T; + return; + } + + D->addAttr(::new (S.Context) NoEscapeAttr( + Attr.getRange(), S.Context, + Attr.getAttributeSpellingListIndex())); +} + static void handleAssumeAlignedAttr(Sema &S, Decl *D, const AttributeList &Attr) { Expr *E = Attr.getArgAsExpr(0), @@ -2007,6 +2027,14 @@ static void handleAvailabilityAttr(Sema &S, Decl *D, dyn_cast_or_null(Attr.getMessageExpr())) Str = SE->getString(); + if (II->getName() == "swift") { + if (Introduced.isValid() || Deprecated.isValid() || Obsoleted.isValid() || + !IsUnavailable) { + S.Diag(Attr.getLoc(), diag::warn_availability_swift_unavailable_only); + return; + } + } + AvailabilityAttr *NewAttr = S.mergeAvailabilityAttr(ND, Attr.getRange(), II, Introduced.Version, Deprecated.Version, @@ -3417,6 +3445,23 @@ OptimizeNoneAttr *Sema::mergeOptimizeNoneAttr(Decl *D, SourceRange Range, AttrSpellingListIndex); } +SwiftNameAttr *Sema::mergeSwiftNameAttr(Decl *D, SourceRange Range, + StringRef Name, bool Override, + unsigned AttrSpellingListIndex) { + if (SwiftNameAttr *Inline = D->getAttr()) { + if (Override) { + // FIXME: Warn about an incompatible override. + return nullptr; + } + Diag(Inline->getLocation(), diag::warn_attribute_ignored) << Inline; + Diag(Range.getBegin(), diag::note_conflicting_attribute); + D->dropAttr(); + } + + return ::new (Context) SwiftNameAttr(Range, Context, Name, + AttrSpellingListIndex); +} + static void handleAlwaysInlineAttr(Sema &S, Decl *D, const AttributeList &Attr) { if (AlwaysInlineAttr *Inline = S.mergeAlwaysInlineAttr( @@ -4188,6 +4233,219 @@ static void handleObjCPreciseLifetimeAttr(Sema &S, Decl *D, Attr.getAttributeSpellingListIndex())); } +/// Do a very rough check to make sure \p Name looks like a Swift function name, +/// e.g. init(foo:bar:baz:) or controllerForName(_:), +/// and return the number of parameter names. +static bool validateSwiftFunctionName(StringRef Name, + unsigned &ParamCount, + bool &IsSingleParamInit) { + ParamCount = 0; + if (Name.back() != ')') + return false; + + StringRef BaseName, Parameters; + std::tie(BaseName, Parameters) = Name.split('('); + if (!isValidIdentifier(BaseName) || BaseName == "_") + return false; + + if (Parameters.empty()) + return false; + Parameters = Parameters.drop_back(); // ')' + if (Parameters.empty()) + return true; + + if (Parameters.back() != ':') + return false; + + StringRef NextParam; + do { + std::tie(NextParam, Parameters) = Parameters.split(':'); + + if (!isValidIdentifier(NextParam)) + return false; + ++ParamCount; + } while (!Parameters.empty()); + + IsSingleParamInit = + (ParamCount == 1 && BaseName == "init" && NextParam != "_"); + + return true; +} + +static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { + StringRef Name; + SourceLocation ArgLoc; + if (!S.checkStringLiteralArgumentAttr(Attr, 0, Name, &ArgLoc)) + return; + + if (isa(D) || isa(D)) { + ArrayRef Params; + unsigned ParamCount; + + if (const auto *Method = dyn_cast(D)) { + ParamCount = Method->getSelector().getNumArgs(); + Params = Method->parameters().slice(0, ParamCount); + } else { + const auto *Function = cast(D); + ParamCount = Function->getNumParams(); + Params = Function->parameters(); + } + + bool IsSingleParamInit; + unsigned SwiftParamCount; + if (!validateSwiftFunctionName(Name, SwiftParamCount, IsSingleParamInit)) { + S.Diag(ArgLoc, diag::err_attr_swift_name_function) << Attr.getName(); + return; + } + + bool ParamsOK; + if (SwiftParamCount == ParamCount) { + ParamsOK = true; + } else if (SwiftParamCount > ParamCount) { + ParamsOK = IsSingleParamInit && ParamCount == 0; + } else { + // We have fewer Swift parameters than Objective-C parameters, but that + // might be because we've transformed some of them. Check for potential + // "out" parameters and err on the side of not warning. + unsigned MaybeOutParamCount = + std::count_if(Params.begin(), Params.end(), + [](const ParmVarDecl *Param) -> bool { + QualType ParamTy = Param->getType(); + if (ParamTy->isReferenceType() || ParamTy->isPointerType()) + return !ParamTy->getPointeeType().isConstQualified(); + return false; + }); + ParamsOK = (SwiftParamCount + MaybeOutParamCount >= ParamCount); + } + + if (!ParamsOK) { + S.Diag(ArgLoc, diag::warn_attr_swift_name_num_params) + << (SwiftParamCount > ParamCount) << Attr.getName() + << ParamCount << SwiftParamCount; + return; + } + + } else if (isa(D) || isa(D) || + isa(D) || isa(D) || + isa(D) || isa(D) || isa(D) || + isa(D) || isa(D)) { + if (!isValidIdentifier(Name)) { + S.Diag(ArgLoc, diag::err_attr_swift_name_identifier) << Attr.getName(); + return; + } + + } else { + S.Diag(Attr.getLoc(), diag::err_attr_swift_name_decl_kind) + << Attr.getName(); + return; + } + + D->addAttr(::new (S.Context) + SwiftNameAttr(Attr.getRange(), S.Context, Name, + Attr.getAttributeSpellingListIndex())); +} + +static bool isErrorParameter(Sema &S, QualType paramType) { + if (auto ptr = paramType->getAs()) { + auto outerPointee = ptr->getPointeeType(); + + // NSError**. + if (auto objcPtr = outerPointee->getAs()) { + if (auto iface = objcPtr->getInterfaceDecl()) + if (iface->getIdentifier() == S.getNSErrorIdent()) + return true; + } + + // CFErrorRef*. + if (auto cPtr = outerPointee->getAs()) { + auto innerPointee = cPtr->getPointeeType(); + if (auto recordType = innerPointee->getAs()) { + if (S.isCFError(recordType->getDecl())) + return true; + } + } + } + + return false; +} + +static void handleSwiftError(Sema &S, Decl *D, const AttributeList &attr) { + SwiftErrorAttr::ConventionKind convention; + IdentifierLoc *conventionLoc = attr.getArgAsIdent(0); + StringRef conventionStr = conventionLoc->Ident->getName(); + if (!SwiftErrorAttr::ConvertStrToConventionKind(conventionStr, convention)) { + S.Diag(attr.getLoc(), diag::warn_attribute_type_not_supported) + << attr.getName() << conventionLoc->Ident; + return; + } + + auto requireErrorParameter = [&]() -> bool { + if (D->isInvalidDecl()) return true; + + for (unsigned i = 0, e = getFunctionOrMethodNumParams(D); i != e; ++i) { + if (isErrorParameter(S, getFunctionOrMethodParamType(D, i))) + return true; + } + + S.Diag(attr.getLoc(), diag::err_attr_swift_error_no_error_parameter) + << attr.getName() << isa(D); + return false; + }; + + auto requirePointerResult = [&] { + if (D->isInvalidDecl()) return true; + + // C, ObjC, and block pointers are definitely okay. + // References are definitely not okay. + // nullptr_t is weird but acceptable. + QualType returnType = getFunctionOrMethodResultType(D); + if (returnType->hasPointerRepresentation() && + !returnType->isReferenceType()) return true; + + S.Diag(attr.getLoc(), diag::err_attr_swift_error_return_type) + << attr.getName() << conventionStr + << isa(D) << /*pointer*/ 1; + return false; + }; + + auto requireIntegerResult = [&] { + if (D->isInvalidDecl()) return true; + + QualType returnType = getFunctionOrMethodResultType(D); + if (returnType->isIntegralType(S.Context)) return true; + + S.Diag(attr.getLoc(), diag::err_attr_swift_error_return_type) + << attr.getName() << conventionStr + << isa(D) << /*integral*/ 0; + return false; + }; + + switch (convention) { + case SwiftErrorAttr::None: + // No additional validation required. + break; + + case SwiftErrorAttr::NonNullError: + if (!requireErrorParameter()) return; + break; + + case SwiftErrorAttr::NullResult: + if (!requireErrorParameter()) return; + if (!requirePointerResult()) return; + break; + + case SwiftErrorAttr::NonZeroResult: + case SwiftErrorAttr::ZeroResult: + if (!requireErrorParameter()) return; + if (!requireIntegerResult()) return; + break; + } + + D->addAttr(::new (S.Context) + SwiftErrorAttr(attr.getRange(), S.Context, convention, + attr.getAttributeSpellingListIndex())); +} + //===----------------------------------------------------------------------===// // Microsoft specific attribute handlers. //===----------------------------------------------------------------------===// @@ -4855,6 +5113,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_ReturnsNonNull: handleReturnsNonNullAttr(S, D, Attr); break; + case AttributeList::AT_NoEscape: + handleNoEscapeAttr(S, D, Attr); + break; case AttributeList::AT_AssumeAligned: handleAssumeAlignedAttr(S, D, Attr); break; @@ -4979,6 +5240,12 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_ObjCRootClass: handleSimpleAttribute(S, D, Attr); break; + case AttributeList::AT_ObjCSubclassingRestricted: + handleSimpleAttribute(S, D, Attr); + break; + case AttributeList::AT_ObjCCompleteDefinition: + handleSimpleAttribute(S, D, Attr); + break; case AttributeList::AT_ObjCExplicitProtocolImpl: handleObjCSuppresProtocolAttr(S, D, Attr); break; @@ -5201,6 +5468,17 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_TypeTagForDatatype: handleTypeTagForDatatypeAttr(S, D, Attr); break; + + // Swift attributes. + case AttributeList::AT_SwiftPrivate: + handleSimpleAttribute(S, D, Attr); + break; + case AttributeList::AT_SwiftName: + handleSwiftName(S, D, Attr); + break; + case AttributeList::AT_SwiftError: + handleSwiftError(S, D, Attr); + break; } } @@ -5413,6 +5691,9 @@ void Sema::ProcessDeclAttributes(Scope *S, Decl *D, const Declarator &PD) { // Finally, apply any attributes on the decl itself. if (const AttributeList *Attrs = PD.getAttributes()) ProcessDeclAttributeList(S, D, Attrs); + + // Look for API notes that map to attributes. + ProcessAPINotes(D); } /// Is the given declaration allowed to use a forbidden type? diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 6c308ef9e6a0c..42f4fcee48cea 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7269,6 +7269,7 @@ Decl *Sema::ActOnStartNamespaceDef(Scope *NamespcScope, Namespc->setInvalidDecl(); ProcessDeclAttributeList(DeclRegionScope, Namespc, AttrList); + ProcessAPINotes(Namespc); // FIXME: Should we be merging attributes? if (const VisibilityAttr *Attr = Namespc->getAttr()) @@ -7642,6 +7643,7 @@ Decl *Sema::ActOnUsingDirective(Scope *S, if (UDir) ProcessDeclAttributeList(S, UDir, AttrList); + ProcessAPINotes(UDir); return UDir; } @@ -8576,6 +8578,7 @@ Decl *Sema::ActOnAliasDeclaration(Scope *S, NewTD->setInvalidDecl(); ProcessDeclAttributeList(S, NewTD, AttrList); + ProcessAPINotes(NewTD); CheckTypedefForVariablyModifiedType(S, NewTD); Invalid |= NewTD->isInvalidDecl(); diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 527ffa0fece85..c52558d69e8f6 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -982,6 +982,10 @@ ActOnStartClassInterface(Scope *S, SourceLocation AtInterfaceLoc, ObjCInterfaceDecl *IDecl = ObjCInterfaceDecl::Create(Context, CurContext, AtInterfaceLoc, ClassName, typeParamList, PrevIDecl, ClassLoc); + if (AttrList) + ProcessDeclAttributeList(TUScope, IDecl, AttrList); + ProcessAPINotes(IDecl); + if (PrevIDecl) { // Class already seen. Was it a definition? if (ObjCInterfaceDecl *Def = PrevIDecl->getDefinition()) { @@ -992,8 +996,6 @@ ActOnStartClassInterface(Scope *S, SourceLocation AtInterfaceLoc, } } - if (AttrList) - ProcessDeclAttributeList(TUScope, IDecl, AttrList); PushOnScopeChains(IDecl, TUScope); // Start the definition of this class. If we're in a redefinition case, there @@ -1170,6 +1172,7 @@ Sema::ActOnStartProtocolInterface(SourceLocation AtProtoInterfaceLoc, if (AttrList) ProcessDeclAttributeList(TUScope, PDecl, AttrList); + ProcessAPINotes(PDecl); // Merge attributes from previous declarations. if (PrevDecl) @@ -1691,6 +1694,7 @@ Sema::ActOnForwardProtocolDeclaration(SourceLocation AtProtocolLoc, if (attrList) ProcessDeclAttributeList(TUScope, PDecl, attrList); + ProcessAPINotes(PDecl); if (PrevDecl) mergeDeclAttributes(PDecl, PrevDecl); @@ -3680,7 +3684,7 @@ Decl *Sema::ActOnAtEnd(Scope *S, SourceRange AtEnd, ArrayRef allMethods, if (IDecl->getSuperClass() == nullptr) { // This class has no superclass, so check that it has been marked with // __attribute((objc_root_class)). - if (!HasRootClassAttr) { + if (!HasRootClassAttr && !IDecl->hasAttr()) { SourceLocation DeclLoc(IDecl->getLocation()); SourceLocation SuperClassLoc(getLocForEndOfToken(DeclLoc)); Diag(DeclLoc, diag::warn_objc_root_class_missing) @@ -3723,6 +3727,15 @@ Decl *Sema::ActOnAtEnd(Scope *S, SourceRange AtEnd, ArrayRef allMethods, ImplMethodsVsClassMethods(S, CatImplClass, Cat); } } + } else if (const ObjCInterfaceDecl *IntfDecl = + dyn_cast(ClassDecl)) { + if (const ObjCInterfaceDecl *Super = IntfDecl->getSuperClass()) { + if (!IntfDecl->hasAttr() && + Super->hasAttr()) { + Diag(IntfDecl->getLocation(), diag::err_restricted_superclass_mismatch); + Diag(Super->getLocation(), diag::note_class_declared); + } + } } if (isInterfaceDeclKind) { // Reject invalid vardecls. @@ -3755,12 +3768,10 @@ CvtQTToAstBitMask(ObjCDeclSpec::ObjCDeclQualifier PQTVal) { return (Decl::ObjCDeclQualifier) (unsigned) PQTVal; } -/// \brief Check whether the declared result type of the given Objective-C -/// method declaration is compatible with the method's class. -/// -static Sema::ResultTypeCompatibilityKind -CheckRelatedResultTypeCompatibility(Sema &S, ObjCMethodDecl *Method, - ObjCInterfaceDecl *CurrentClass) { +Sema::ResultTypeCompatibilityKind +Sema::checkRelatedResultTypeCompatibility( + const ObjCMethodDecl *Method, + const ObjCInterfaceDecl *CurrentClass) { QualType ResultType = Method->getReturnType(); // If an Objective-C method inherits its related result type, then its @@ -4216,6 +4227,7 @@ Decl *Sema::ActOnMethodDeclaration( // Apply the attributes to the parameter. ProcessDeclAttributeList(TUScope, Param, ArgInfo[i].ArgAttrs); + ProcessAPINotes(Param); if (Param->hasAttr()) { Diag(Param->getLocation(), diag::err_block_on_nonlocal); @@ -4246,6 +4258,7 @@ Decl *Sema::ActOnMethodDeclaration( if (AttrList) ProcessDeclAttributeList(TUScope, ObjCMethod, AttrList); + ProcessAPINotes(ObjCMethod); // Add the method now. const ObjCMethodDecl *PrevMethod = nullptr; @@ -4301,7 +4314,7 @@ Decl *Sema::ActOnMethodDeclaration( } ResultTypeCompatibilityKind RTC - = CheckRelatedResultTypeCompatibility(*this, ObjCMethod, CurrentClass); + = checkRelatedResultTypeCompatibility(ObjCMethod, CurrentClass); CheckObjCMethodOverrides(ObjCMethod, CurrentClass, RTC); diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 8821088baac17..ce1aa4c4448f0 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -1136,6 +1136,7 @@ Sema::CheckClassTemplate(Scope *S, unsigned TagSpec, TagUseKind TUK, if (Attr) ProcessDeclAttributeList(S, NewClass, Attr); + ProcessAPINotes(NewClass); if (PrevClassTemplate) mergeDeclAttributes(NewClass, PrevClassTemplate->getTemplatedDecl()); @@ -6465,6 +6466,7 @@ Sema::ActOnClassTemplateSpecialization(Scope *S, unsigned TagSpec, if (Attr) ProcessDeclAttributeList(S, Specialization, Attr); + ProcessAPINotes(Specialization); // Add alignment attributes if necessary; these attributes are checked when // the ASTContext lays out the structure. @@ -7390,6 +7392,7 @@ Sema::ActOnExplicitInstantiation(Scope *S, if (Attr) ProcessDeclAttributeList(S, Specialization, Attr); + ProcessAPINotes(Specialization); // Add the explicit instantiation into its lexical context. However, // since explicit instantiations are never found by name lookup, we @@ -7773,6 +7776,7 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S, // Merge attributes. if (AttributeList *Attr = D.getDeclSpec().getAttributes().getList()) ProcessDeclAttributeList(S, Prev, Attr); + ProcessAPINotes(Prev); } if (TSK == TSK_ExplicitInstantiationDefinition) InstantiateVariableDefinition(D.getIdentifierLoc(), Prev); @@ -7916,6 +7920,7 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S, AttributeList *Attr = D.getDeclSpec().getAttributes().getList(); if (Attr) ProcessDeclAttributeList(S, Specialization, Attr); + ProcessAPINotes(Specialization); if (Specialization->isDefined()) { // Let the ASTConsumer know that this function has been explicitly diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index e53c7792e50d1..81c8b7f19c6c0 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -3125,25 +3125,9 @@ static PointerDeclaratorKind classifyPointerDeclarator(Sema &S, if (auto recordType = type->getAs()) { RecordDecl *recordDecl = recordType->getDecl(); - bool isCFError = false; - if (S.CFError) { - // If we already know about CFError, test it directly. - isCFError = (S.CFError == recordDecl); - } else { - // Check whether this is CFError, which we identify based on its bridge - // to NSError. - if (recordDecl->getTagKind() == TTK_Struct && numNormalPointers > 0) { - if (auto bridgeAttr = recordDecl->getAttr()) { - if (bridgeAttr->getBridgedType() == S.getNSErrorIdent()) { - S.CFError = recordDecl; - isCFError = true; - } - } - } - } - // If this is CFErrorRef*, report it as such. - if (isCFError && numNormalPointers == 2 && numTypeSpecifierPointers < 2) { + if (numNormalPointers == 2 && numTypeSpecifierPointers < 2 && + S.isCFError(recordDecl)) { return PointerDeclaratorKind::CFErrorRefPointer; } break; @@ -3168,6 +3152,26 @@ static PointerDeclaratorKind classifyPointerDeclarator(Sema &S, } } +bool Sema::isCFError(RecordDecl *recordDecl) { + // If we already know about CFError, test it directly. + if (CFError) { + return (CFError == recordDecl); + } + + // Check whether this is CFError, which we identify based on being + // bridged to NSError. + if (recordDecl->getTagKind() == TTK_Struct) { + if (auto bridgeAttr = recordDecl->getAttr()) { + if (bridgeAttr->getBridgedType() == getNSErrorIdent()) { + CFError = recordDecl; + return true; + } + } + } + + return false; +} + static FileID getNullabilityCompletenessCheckFileID(Sema &S, SourceLocation loc) { // If we're anywhere in a function, method, or closure context, don't perform @@ -5470,21 +5474,24 @@ static bool handleMSPointerTypeQualifierAttr(TypeProcessingState &State, bool Sema::checkNullabilityTypeSpecifier(QualType &type, NullabilityKind nullability, SourceLocation nullabilityLoc, - bool isContextSensitive) { - // We saw a nullability type specifier. If this is the first one for - // this file, note that. - FileID file = getNullabilityCompletenessCheckFileID(*this, nullabilityLoc); - if (!file.isInvalid()) { - FileNullability &fileNullability = NullabilityMap[file]; - if (!fileNullability.SawTypeNullability) { - // If we have already seen a pointer declarator without a nullability - // annotation, complain about it. - if (fileNullability.PointerLoc.isValid()) { - Diag(fileNullability.PointerLoc, diag::warn_nullability_missing) - << static_cast(fileNullability.PointerKind); - } + bool isContextSensitive, + bool implicit) { + if (!implicit) { + // We saw a nullability type specifier. If this is the first one for + // this file, note that. + FileID file = getNullabilityCompletenessCheckFileID(*this, nullabilityLoc); + if (!file.isInvalid()) { + FileNullability &fileNullability = NullabilityMap[file]; + if (!fileNullability.SawTypeNullability) { + // If we have already seen a pointer declarator without a nullability + // annotation, complain about it. + if (fileNullability.PointerLoc.isValid()) { + Diag(fileNullability.PointerLoc, diag::warn_nullability_missing) + << static_cast(fileNullability.PointerKind); + } - fileNullability.SawTypeNullability = true; + fileNullability.SawTypeNullability = true; + } } } @@ -5495,6 +5502,9 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &type, if (auto existingNullability = attributed->getImmediateNullability()) { // Duplicated nullability. if (nullability == *existingNullability) { + if (implicit) + break; + Diag(nullabilityLoc, diag::warn_nullability_duplicate) << DiagNullabilityKind(nullability, isContextSensitive) << FixItHint::CreateRemoval(nullabilityLoc); @@ -5517,7 +5527,7 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &type, // have nullability specifiers on them, which means we cannot // provide a useful Fix-It. if (auto existingNullability = desugared->getNullability(Context)) { - if (nullability != *existingNullability) { + if (nullability != *existingNullability && !implicit) { Diag(nullabilityLoc, diag::err_nullability_conflicting) << DiagNullabilityKind(nullability, isContextSensitive) << DiagNullabilityKind(*existingNullability, false); @@ -5541,8 +5551,10 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &type, // If this definitely isn't a pointer type, reject the specifier. if (!desugared->canHaveNullability()) { - Diag(nullabilityLoc, diag::err_nullability_nonpointer) - << DiagNullabilityKind(nullability, isContextSensitive) << type; + if (!implicit) { + Diag(nullabilityLoc, diag::err_nullability_nonpointer) + << DiagNullabilityKind(nullability, isContextSensitive) << type; + } return true; } @@ -6247,7 +6259,8 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type, type, mapNullabilityAttrKind(attr.getKind()), attr.getLoc(), - attr.isContextSensitiveKeywordAttribute())) { + attr.isContextSensitiveKeywordAttribute(), + /*implicit=*/false)) { attr.setInvalid(); } diff --git a/clang/test/APINotes/Inputs/BrokenHeaders/APINotes.apinotes b/clang/test/APINotes/Inputs/BrokenHeaders/APINotes.apinotes new file mode 100644 index 0000000000000..d5473175ecf8e --- /dev/null +++ b/clang/test/APINotes/Inputs/BrokenHeaders/APINotes.apinotes @@ -0,0 +1,4 @@ +Name: SomeBrokenLib +Functions: + - Name: do_something_with_pointers + Nu llabilityOfRet: O diff --git a/clang/test/APINotes/Inputs/BrokenHeaders/SomeBrokenLib.h b/clang/test/APINotes/Inputs/BrokenHeaders/SomeBrokenLib.h new file mode 100644 index 0000000000000..b09c6f63eae02 --- /dev/null +++ b/clang/test/APINotes/Inputs/BrokenHeaders/SomeBrokenLib.h @@ -0,0 +1,6 @@ +#ifndef SOME_BROKEN_LIB_H +#define SOME_BROKEN_LIB_H + +void do_something_with_pointers(int *ptr1, int *ptr2); + +#endif // SOME_BROKEN_LIB_H diff --git a/clang/test/APINotes/Inputs/BrokenHeaders2/APINotes.apinotes b/clang/test/APINotes/Inputs/BrokenHeaders2/APINotes.apinotes new file mode 100644 index 0000000000000..33eeaaada999d --- /dev/null +++ b/clang/test/APINotes/Inputs/BrokenHeaders2/APINotes.apinotes @@ -0,0 +1,7 @@ +Name: SomeBrokenLib +Functions: + - Name: do_something_with_pointers + NullabilityOfRet: O + - Name: do_something_with_pointers + NullabilityOfRet: O + diff --git a/clang/test/APINotes/Inputs/BrokenHeaders2/SomeBrokenLib.h b/clang/test/APINotes/Inputs/BrokenHeaders2/SomeBrokenLib.h new file mode 100644 index 0000000000000..b09c6f63eae02 --- /dev/null +++ b/clang/test/APINotes/Inputs/BrokenHeaders2/SomeBrokenLib.h @@ -0,0 +1,6 @@ +#ifndef SOME_BROKEN_LIB_H +#define SOME_BROKEN_LIB_H + +void do_something_with_pointers(int *ptr1, int *ptr2); + +#endif // SOME_BROKEN_LIB_H diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes new file mode 100644 index 0000000000000..a585ca5f4df3b --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes @@ -0,0 +1,24 @@ +Name: SomeKit +Classes: + - Name: A + Methods: + - Selector: "transform:" + MethodKind: Instance + Availability: none + AvailabilityMsg: "anything but this" + - Selector: "transform:integer:" + MethodKind: Instance + NullabilityOfRet: N + Nullability: [ N, S ] + Properties: + - Name: intValue + Availability: none + AvailabilityMsg: "wouldn't work anyway" + - Name: B + Availability: none + AvailabilityMsg: "just don't" + - Name: C + Methods: + - Selector: "initWithA:" + MethodKind: Instance + DesignatedInit: true diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit_private.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit_private.apinotes new file mode 100644 index 0000000000000..28ede9dfa25c0 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit_private.apinotes @@ -0,0 +1,15 @@ +Name: SomeKit +Classes: + - Name: A + Methods: + - Selector: "privateTransform:input:" + MethodKind: Instance + NullabilityOfRet: N + Nullability: [ N, S ] + Properties: + - Name: internalProperty + Nullability: N +Protocols: + - Name: InternalProtocol + Availability: none + AvailabilityMsg: "not for you" diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h new file mode 100644 index 0000000000000..01b003d1eeef0 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h @@ -0,0 +1,23 @@ +#ifndef SOMEKIT_H +#define SOMEKIT_H + +__attribute__((objc_root_class)) +@interface A +-(A*)transform:(A*)input; +-(A*)transform:(A*)input integer:(int)integer; + +@property (nonatomic, readonly, retain) A* someA; +@property (nonatomic, retain) A* someOtherA; + +@property (nonatomic) int intValue; +@end + +@interface B : A +@end + +@interface C : A +- (instancetype)init; +- (instancetype)initWithA:(A*)a; +@end + +#endif diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKitForNullAnnotation.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKitForNullAnnotation.h new file mode 100644 index 0000000000000..d1eeb61991b48 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKitForNullAnnotation.h @@ -0,0 +1,55 @@ +#ifndef SOMEKIT_H +#define SOMEKIT_H + +#define ROOT_CLASS __attribute__((objc_root_class)) + +ROOT_CLASS +@interface A +-(A*)transform:(A*)input; +-(A*)transform:(A*)input integer:(int)integer; + +@property (nonatomic, readonly, retain) A* someA; +@property (nonatomic, retain) A* someOtherA; + +@property (nonatomic) int intValue; +@end + +@interface B : A +@end + +@interface C : A +- (instancetype)init; +- (instancetype)initWithA:(A*)a; +@end + + +@interface MyClass : A +- Inst; ++ Clas; +@end + +struct CGRect { + float origin; + float size; +}; +typedef struct CGRect NSRect; + +@interface I +- (void) Meth : (NSRect[4])exposedRects; +- (void) Meth1 : (const I*)exposedRects; +- (void) Meth2 : (const I*)exposedRects; +- (void) Meth3 : (I*)exposedRects; +- (const I*) Meth4; +- (const I*) Meth5 : (int) Arg1 : (const I*)Arg2 : (double)Arg3 : (const I*) Arg4 :(const volatile id) Arg5; +- (volatile const I*) Meth6 : (const char *)Arg1 : (const char *)Arg2 : (double)Arg3 : (const I*) Arg4 :(const volatile id) Arg5; +@end + +@class NSURL, NSArray, NSError; +@interface INTF_BLOCKS + + (void)getNonLocalVersionsOfItemAtURL:(NSURL *)url completionHandler:(void (^)(NSArray *nonLocalFileVersions, NSError *error))completionHandler; + + (void *)getNonLocalVersionsOfItemAtURL2:(NSURL *)url completionHandler:(void (^)(NSArray *nonLocalFileVersions, NSError *error))completionHandler; + + (NSError **)getNonLocalVersionsOfItemAtURL3:(int)url completionHandler:(void (^)(NSArray *nonLocalFileVersions, NSError *error))completionHandler; + + (id)getNonLocalVersionsOfItemAtURL4:(NSURL *)url completionHandler:(void (^)(int nonLocalFileVersions, NSError *error, NSURL*))completionHandler; +@end + +#endif diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_Private.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_Private.h new file mode 100644 index 0000000000000..c7611123e4ad2 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_Private.h @@ -0,0 +1,16 @@ +#ifndef SOMEKIT_PRIVATE_H +#define SOMEKIT_PRIVATE_H + +#import + +@interface A(Private) +-(A*)privateTransform:(A*)input; + +@property (nonatomic) A* internalProperty; +@end + +@protocol InternalProtocol +@end + +#endif + diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_PrivateForNullAnnotation.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_PrivateForNullAnnotation.h new file mode 100644 index 0000000000000..bae4456b40809 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_PrivateForNullAnnotation.h @@ -0,0 +1,17 @@ +#ifndef SOMEKIT_PRIVATE_H +#define SOMEKIT_PRIVATE_H + +#import + +@interface A(Private) +-(A*)privateTransform:(A*)input; + +@property (nonatomic) A* internalProperty; +@end + +@protocol InternalProtocol +- (id) MomeMethod; +@end + +#endif + diff --git a/clang/test/APINotes/Inputs/Headers/APINotes.apinotes b/clang/test/APINotes/Inputs/Headers/APINotes.apinotes new file mode 100644 index 0000000000000..a4ddafe2892ec --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/APINotes.apinotes @@ -0,0 +1,17 @@ +Name: HeaderLib +Functions: + - Name: custom_realloc + NullabilityOfRet: N + Nullability: [ N, S ] + - Name: unavailable_function + Availability: none + AvailabilityMsg: "I beg you not to use this" + - Name: do_something_with_pointers + NullabilityOfRet: O + Nullability: [ N, O ] + +Globals: + - Name: global_int + Nullability: N + - Name: unavailable_global_int + Availability: none diff --git a/clang/test/APINotes/Inputs/Headers/HeaderLib.h b/clang/test/APINotes/Inputs/Headers/HeaderLib.h new file mode 100644 index 0000000000000..1cf199cd49a02 --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/HeaderLib.h @@ -0,0 +1,13 @@ +#ifndef HEADER_LIB_H +#define HEADER_LIB_H + +void *custom_realloc(void *member, unsigned size); + +int *global_int; + +int unavailable_function(void); +int unavailable_global_int; + +void do_something_with_pointers(int *ptr1, int *ptr2); + +#endif diff --git a/clang/test/APINotes/Inputs/os-availability.apinotes b/clang/test/APINotes/Inputs/os-availability.apinotes new file mode 100644 index 0000000000000..d59e79ce9fc0d --- /dev/null +++ b/clang/test/APINotes/Inputs/os-availability.apinotes @@ -0,0 +1,53 @@ +Name: Foundation +Classes: + - Name: NSCountedSet + Availability: iOS + Methods: + - Selector: 'initWithCapacity:' + MethodKind: Instance + DesignatedInit: true + - Name: NSArray + Methods: + - Selector: 'init' + MethodKind: Instance + DesignatedInit: true + - Selector: 'initWithObjects:' + MethodKind: Instance + DesignatedInit: true + Availability: iOS + - Selector: 'initWithObjects:count:' + MethodKind: Instance + DesignatedInit: true + Availability: iOS + Properties: + - Name: 'familyNameios' + Nullability: N + Availability: iOS + - Name: 'fontName' + Nullability: N +Protocols: + - Name: UIApplicationDelegate + AuditedForNullability: true + Methods: + - Selector: 'application:willFinishLaunchingWithOptions:' + MethodKind: Instance + Nullability: [ N, U ] + - Name: UIApplicationDelegateIOS + Availability: iOS + AuditedForNullability: true + Methods: + - Selector: 'application:willFinishLaunchingWithOptions:' + MethodKind: Instance + Nullability: [ N, U ] +Functions: + - Name: NSAvailableWindowDepthsiOS + NullabilityOfRet: N + Availability: iOS + - Name: NSAvailableWindowDepths + NullabilityOfRet: N +Globals: + - Name: NSCalibratedWhiteColorSpace + Nullability: N + Availability: OSX + AvailabilityMsg: '' + diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes new file mode 100644 index 0000000000000..b1722406663eb --- /dev/null +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -0,0 +1,79 @@ +--- +Name: AppKit +Availability: available +AvailabilityMsg: '' +Classes: + - Name: NSCell + Availability: available + AvailabilityMsg: '' + Methods: + - Selector: init + MethodKind: Instance + NullabilityOfRet: U + Availability: available + AvailabilityMsg: '' + DesignatedInit: true + - Selector: 'initImageCell:' + MethodKind: Instance + Nullability: [ N ] + NullabilityOfRet: U + Availability: available + AvailabilityMsg: '' + DesignatedInit: true + - Selector: 'initTextCell:' + MethodKind: Instance + Nullability: [ N ] + NullabilityOfRet: U + Availability: available + AvailabilityMsg: '' + DesignatedInit: true + - Selector: 'initWithCoder:' + MethodKind: Instance + Nullability: [ N ] + NullabilityOfRet: U + Availability: available + AvailabilityMsg: '' + DesignatedInit: true + Required: true + - Name: NSView + AuditedForNullability: true + Availability: available + AvailabilityMsg: '' + Methods: + - Selector: 'addSubview:' + MethodKind: Instance + Nullability: [ N ] + NullabilityOfRet: N + Availability: available + AvailabilityMsg: '' + - Selector: 'addSubview:positioned:relativeTo:' + MethodKind: Instance + Nullability: [ N, N, O ] + NullabilityOfRet: N + Availability: available + AvailabilityMsg: '' + - Selector: 'beginDraggingSessionWithItems:event:source:' + MethodKind: Instance + Nullability: [ U, U, N ] + NullabilityOfRet: N + Availability: available + AvailabilityMsg: '' + Properties: + - Name: enclosingScrollView + Nullability: O + Availability: available + AvailabilityMsg: '' + - Name: makeBackingLayer + Nullability: N + Availability: available + AvailabilityMsg: '' +Functions: + - Name: NSAvailableWindowDepths + NullabilityOfRet: N + Availability: available + AvailabilityMsg: '' +Globals: + - Name: NSCalibratedWhiteColorSpace + Nullability: N + Availability: available + AvailabilityMsg: '' diff --git a/clang/test/APINotes/availability.m b/clang/test/APINotes/availability.m new file mode 100644 index 0000000000000..177700d63111d --- /dev/null +++ b/clang/test/APINotes/availability.m @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify + +#include "HeaderLib.h" +#import +#import + +int main() { + int i; + i = unavailable_function(); // expected-error{{'unavailable_function' is unavailable: I beg you not to use this}} + // expected-note@HeaderLib.h:8{{'unavailable_function' has been explicitly marked unavailable here}} + i = unavailable_global_int; // expected-error{{'unavailable_global_int' is unavailable}} + // expected-note@HeaderLib.h:9{{'unavailable_global_int' has been explicitly marked unavailable here}} + + B *b = 0; // expected-error{{'B' is unavailable: just don't}} + // expected-note@SomeKit/SomeKit.h:15{{'B' has been explicitly marked unavailable here}} + + id proto = 0; // expected-error{{'InternalProtocol' is unavailable: not for you}} + // expected-note@SomeKit/SomeKit_Private.h:12{{'InternalProtocol' has been explicitly marked unavailable here}} + + A *a = 0; + i = a.intValue; // expected-error{{intValue' is unavailable: wouldn't work anyway}} + // expected-note@SomeKit/SomeKit.h:12{{'intValue' has been explicitly marked unavailable here}} + + [a transform:a]; // expected-error{{'transform:' is unavailable: anything but this}} + // expected-note@SomeKit/SomeKit.h:6{{'transform:' has been explicitly marked unavailable here}} + + return 0; +} + diff --git a/clang/test/APINotes/cache.m b/clang/test/APINotes/cache.m new file mode 100644 index 0000000000000..6a2c2f5d17a4d --- /dev/null +++ b/clang/test/APINotes/cache.m @@ -0,0 +1,33 @@ +// RUN: rm -rf %t/APINotesCache +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify + +// Check for the presence of the cached compiled form. +// RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" + +// Run test again to ensure that caching doesn't cause problems. +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify + +// Check that the driver provides a default -fapinotes-cache-path= +// RUN: %clang -fsyntax-only -fapinotes -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -### 2>&1 | FileCheck --check-prefix=CHECK-DEFAULT-PATH %s +// CHECK-DEFAULT-PATH: -fapinotes-cache-path={{.*}}org.llvm.clang/APINotesCache + +// Check that the driver passes through a provided -fapinotes-cache-path= +// RUN: %clang -fsyntax-only -fapinotes -fapinotes-cache-path=/wobble -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -### 2>&1 | FileCheck --check-prefix=CHECK-PATH %s +// CHECK-PATH: -fapinotes-cache-path=/wobble + +#include "HeaderLib.h" +#import + +int main() { + int i; + i = unavailable_function(); // expected-error{{'unavailable_function' is unavailable: I beg you not to use this}} + // expected-note@HeaderLib.h:8{{'unavailable_function' has been explicitly marked unavailable here}} + + A *a = 0; + [a transform:a]; // expected-error{{'transform:' is unavailable: anything but this}} + // expected-note@SomeKit/SomeKit.h:6{{'transform:' has been explicitly marked unavailable here}} + + return 0; +} + diff --git a/clang/test/APINotes/cache_pruning.m b/clang/test/APINotes/cache_pruning.m new file mode 100644 index 0000000000000..54b0c647aef3e --- /dev/null +++ b/clang/test/APINotes/cache_pruning.m @@ -0,0 +1,49 @@ +// We need 'touch' and 'find' for this test to work. +// REQUIRES: shell + +// RUN: rm -rf %t/APINotesCache + +// Run Clang. This should generated the cached versions of both and a timestamp. +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DINCLUDE_HEADERLIB +// RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "APINotes.timestamp" + +// Set the timestamp back a very long time. We should try to prune, +// but nothing gets pruned because the API Notes files are new enough. +// RUN: touch -m -a -t 201101010000 %t/APINotes.timestamp +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "APINotes.timestamp" + +// Set the HeaderLib access time back a very long time. +// This shouldn't prune anything, because the timestamp has been updated, so +// the pruning mechanism won't fire. +// RUN: find %t/APINotesCache -name APINotes-*.apinotesc | xargs touch -a -t 201101010000 +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "APINotes.timestamp" + +// Set the timestack back a very long time. This should prune the +// HeaderLib file, because the pruning mechanism should fire and +// HeaderLib is both old and not used. +// RUN: touch -m -a -t 201101010000 %t/APINotesCache/APINotes.timestamp +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: ls %t/APINotesCache | not grep "APINotes-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "APINotes.timestamp" + +// Run Clang. This should generated the cached versions of both and a timestamp. +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DINCLUDE_HEADERLIB +// RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" +// RUN: ls %t/APINotesCache | grep "APINotes.timestamp" + +#ifdef INCLUDE_HEADERLIB +#include "HeaderLib.h" +#endif +#include + +int main() { return 0; } diff --git a/clang/test/APINotes/nullability.c b/clang/test/APINotes/nullability.c new file mode 100644 index 0000000000000..940587e8e0a12 --- /dev/null +++ b/clang/test/APINotes/nullability.c @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify + +#include "HeaderLib.h" + +int main() { + custom_realloc(0, 0); // expected-warning{{null passed to a callee that requires a non-null argument}} + int i = 0; + do_something_with_pointers(&i, 0); + do_something_with_pointers(0, &i); // expected-warning{{null passed to a callee that requires a non-null argument}} + + float *fp = global_int; // expected-warning{{incompatible pointer types initializing 'float *' with an expression of type 'int * _Nonnull'}} + return 0; +} + diff --git a/clang/test/APINotes/nullability.m b/clang/test/APINotes/nullability.m new file mode 100644 index 0000000000000..40901f2c4fd76 --- /dev/null +++ b/clang/test/APINotes/nullability.m @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify + +#import + + +int main() { + A *a; + + [a transform: 0 integer: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + + return 0; +} + diff --git a/clang/test/APINotes/objc_designated_inits.m b/clang/test/APINotes/objc_designated_inits.m new file mode 100644 index 0000000000000..194d135b6c0da --- /dev/null +++ b/clang/test/APINotes/objc_designated_inits.m @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify + +#include "HeaderLib.h" +#import + +@interface CSub : C +-(instancetype)initWithA:(A*)a; +@end + +@implementation CSub +-(instancetype)initWithA:(A*)a { // expected-warning{{designated initializer missing a 'super' call to a designated initializer of the super class}} + // expected-note@SomeKit/SomeKit.h:20 2{{method marked as designated initializer of the class here}} + self = [super init]; // expected-warning{{designated initializer invoked a non-designated initializer}} + return self; +} +@end diff --git a/clang/test/APINotes/yaml-convert-diags.c b/clang/test/APINotes/yaml-convert-diags.c new file mode 100644 index 0000000000000..e8767f228e5e9 --- /dev/null +++ b/clang/test/APINotes/yaml-convert-diags.c @@ -0,0 +1,6 @@ +// RUN: rm -rf %t +// RUN: not %clang_cc1 -fsyntax-only -fapinotes -fapinotes-cache-path=%t %s -I %S/Inputs/BrokenHeaders2 2>&1 | FileCheck %s + +#include "SomeBrokenLib.h" + +// CHECK: error: multiple definitions of global function 'do_something_with_pointers' diff --git a/clang/test/APINotes/yaml-os-availability.c b/clang/test/APINotes/yaml-os-availability.c new file mode 100644 index 0000000000000..62301af3a1ea4 --- /dev/null +++ b/clang/test/APINotes/yaml-os-availability.c @@ -0,0 +1,31 @@ +# RUN: %clang -cc1apinotes -yaml-to-binary -target i386-apple-ios7 -o %t-ios.apinotesc %S/Inputs/os-availability.apinotes +# RUN: %clang -cc1apinotes -binary-to-yaml %t-ios.apinotesc -o %t.os-availability-ios.apinotes +# RUN: FileCheck %s -check-prefix=IOS < %t.os-availability-ios.apinotes + +# RUN: %clang -cc1apinotes -yaml-to-binary -target x86_64-apple-macosx10.9 -o %t-osx.apinotesc %S/Inputs/os-availability.apinotes +# RUN: %clang -cc1apinotes -binary-to-yaml %t-osx.apinotesc -o %t.os-availability-osx.apinotes +# RUN: FileCheck %s -check-prefix=OSX < %t.os-availability-osx.apinotes + +# IOS: Foundation +# IOS: NSArray +# IOS: initWithObjects +# IOS: familyNameios +# IOS: fontName +# IOS: NSCountedSet +# IOS: UIApplicationDelegate +# IOS: UIApplicationDelegateIOS +# IOS: NSAvailableWindowDepths +# IOS: NSAvailableWindowDepthsiOS +# IOS-NOT: NSCalibratedWhiteColorSpace + +# OSX: Foundation +# qqOSX: NSArray +# OSX-NOT: initWithObjects +# OSX-NOT: familyNameios +# OSX: fontName +# OSX-NOT: NSCountedSet +# OSX: UIApplicationDelegate +# OSX-NOT: UIApplicationDelegateIOS +# OSX: NSAvailableWindowDepths +# OSX-NOT: NSAvailableWindowDepthsiOS +# OSX: NSCalibratedWhiteColorSpace diff --git a/clang/test/APINotes/yaml-parse-diags.c b/clang/test/APINotes/yaml-parse-diags.c new file mode 100644 index 0000000000000..4505e293ef898 --- /dev/null +++ b/clang/test/APINotes/yaml-parse-diags.c @@ -0,0 +1,6 @@ +// RUN: rm -rf %t +// RUN: %clang_cc1 -fsyntax-only -fapinotes -fapinotes-cache-path=%t %s -I %S/Inputs/BrokenHeaders -verify + +#include "SomeBrokenLib.h" + +// expected-error@APINotes.apinotes:4{{unknown key 'Nu llabilityOfRet'}} diff --git a/clang/test/APINotes/yaml-reader-errors.c b/clang/test/APINotes/yaml-reader-errors.c new file mode 100644 index 0000000000000..51dfe3aed841f --- /dev/null +++ b/clang/test/APINotes/yaml-reader-errors.c @@ -0,0 +1,65 @@ +# RUN: not %clang -cc1apinotes -yaml-to-binary -target i386-apple-ios7 -o %t.apinotesc %s > %t.err 2>&1 +# RUN: FileCheck %s < %t.err + +--- +Name: UIKit +Availability: iOS +AvailabilityMsg: iOSOnly +Classes: + - Name: UIFont + Availability: iOS + AvailabilityMsg: iOSOnly + Methods: + - Selector: 'fontWithName:size:' + MethodKind: Instance + Nullability: [ N ] + NullabilityOfRet: O + Availability: iOS + AvailabilityMsg: iOSOnly + DesignatedInit: true +# CHECK: duplicate definition of method '-[UIFont fontWithName:size:]' + - Selector: 'fontWithName:size:' + MethodKind: Instance + Nullability: [ N ] + NullabilityOfRet: O + Availability: iOS + AvailabilityMsg: iOSOnly + DesignatedInit: true + Properties: + - Name: familyName + Nullability: N + Availability: iOS + AvailabilityMsg: iOSOnly + - Name: fontName + Nullability: N + Availability: iOS + AvailabilityMsg: iOSOnly +# CHECK: duplicate definition of property 'UIFont.familyName' + - Name: familyName + Nullability: N + Availability: iOS + AvailabilityMsg: iOSOnly +# CHECK: multiple definitions of class 'UIFont' + - Name: UIFont +Protocols: + - Name: MyProto + AuditedForNullability: true +# CHECK: multiple definitions of protocol 'MyProto' + - Name: MyProto + AuditedForNullability: true +Functions: + - Name: 'globalFoo' + Nullability: [ N, N, O, S ] + NullabilityOfRet: O + Availability: iOS + AvailabilityMsg: iOSOnly + - Name: 'globalFoo2' + Nullability: [ N, N, O, S ] + NullabilityOfRet: O +Globals: + - Name: globalVar + Nullability: O + Availability: iOS + AvailabilityMsg: iOSOnly + - Name: globalVar2 + Nullability: O diff --git a/clang/test/APINotes/yaml-reader-test.c b/clang/test/APINotes/yaml-reader-test.c new file mode 100644 index 0000000000000..01ad90a5a2292 --- /dev/null +++ b/clang/test/APINotes/yaml-reader-test.c @@ -0,0 +1,102 @@ +# RUN: %clang -cc1apinotes -dump %s | FileCheck %s +--- +Name: UIKit +Availability: iOS +AvailabilityMsg: iOSOnly +Classes: + - Name: UIFont + Availability: iOS + AvailabilityMsg: iOSOnly + Methods: + - Selector: 'fontWithName:size:' + MethodKind: Instance + Nullability: [ N ] + NullabilityOfRet: O + Availability: iOS + AvailabilityMsg: iOSOnly + DesignatedInit: true + Properties: + - Name: familyName + Nullability: N + Availability: iOS + AvailabilityMsg: iOSOnly + - Name: fontName + Nullability: N + Availability: iOS + AvailabilityMsg: iOSOnly +Protocols: + - Name: MyProto + AuditedForNullability: true + - Name: MyProto2 + AuditedForNullability: true +Functions: + - Name: 'globalFoo' + Nullability: [ N, N, O, S ] + NullabilityOfRet: O + Availability: iOS + AvailabilityMsg: iOSOnly + - Name: 'globalFoo2' + Nullability: [ N, N, O, S ] + NullabilityOfRet: O +Globals: + - Name: globalVar + Nullability: O + Availability: iOS + AvailabilityMsg: iOSOnly + - Name: globalVar2 + Nullability: O + + +# CHECK: Name: UIKit +# CHECK: Availability: iOS +# CHECK: AvailabilityMsg: iOSOnly +# CHECK: Classes: +# CHECK: - Name: UIFont +# CHECK: Availability: iOS +# CHECK: AvailabilityMsg: iOSOnly +# CHECK: Methods: +# CHECK: - Selector: 'fontWithName:size:' +# CHECK: MethodKind: Instance +# CHECK: Nullability: [ N ] +# CHECK: NullabilityOfRet: O +# CHECK: Availability: iOS +# CHECK: AvailabilityMsg: iOSOnly +# CHECK: DesignatedInit: true +# CHECK: Properties: +# CHECK: - Name: familyName +# CHECK: Nullability: N +# CHECK: Availability: iOS +# CHECK: AvailabilityMsg: iOSOnly +# CHECK: - Name: fontName +# CHECK: Nullability: N +# CHECK: Availability: iOS +# CHECK: AvailabilityMsg: iOSOnly +# CHECK:Protocols: +# CHECK: - Name: MyProto +# CHECK: AuditedForNullability: true +# CHECK: Availability: available +# CHECK: AvailabilityMsg: '' +# CHECK: - Name: MyProto2 +# CHECK: AuditedForNullability: true +# CHECK: Availability: available +# CHECK: AvailabilityMsg: '' +# CHECK:Functions: +# CHECK: - Name: globalFoo +# CHECK: Nullability: [ N, N, O, U ] +# CHECK: NullabilityOfRet: O +# CHECK: Availability: iOS +# CHECK: AvailabilityMsg: iOSOnly +# CHECK: - Name: globalFoo2 +# CHECK: Nullability: [ N, N, O, U ] +# CHECK: NullabilityOfRet: O +# CHECK: Availability: available +# CHECK: AvailabilityMsg: '' +# CHECK:Globals: +# CHECK: - Name: globalVar +# CHECK: Nullability: O +# CHECK: Availability: iOS +# CHECK: AvailabilityMsg: iOSOnly +# CHECK: - Name: globalVar2 +# CHECK: Nullability: O +# CHECK: Availability: available +# CHECK: AvailabilityMsg: diff --git a/clang/test/APINotes/yaml-roundtrip.c b/clang/test/APINotes/yaml-roundtrip.c new file mode 100644 index 0000000000000..b721b6a6e0167 --- /dev/null +++ b/clang/test/APINotes/yaml-roundtrip.c @@ -0,0 +1,10 @@ +# RUN: %clang -cc1apinotes -yaml-to-binary -o %t.apinotesc %S/Inputs/roundtrip.apinotes +# RUN: %clang -cc1apinotes -binary-to-yaml -o %t.apinotes %t.apinotesc + +# Handle the infurating '...' the YAML writer adds but the parser +# can't read. + +# RUN: cp %S/Inputs/roundtrip.apinotes %t-reference.apinotes +# RUN: echo "..." >> %t-reference.apinotes +# RUN: diff %t-reference.apinotes %t.apinotes + diff --git a/clang/test/Misc/warning-flags.c b/clang/test/Misc/warning-flags.c index 5cc769faad975..9abfb51955d91 100644 --- a/clang/test/Misc/warning-flags.c +++ b/clang/test/Misc/warning-flags.c @@ -18,7 +18,7 @@ This test serves two purposes: The list of warnings below should NEVER grow. It should gradually shrink to 0. -CHECK: Warnings without flags (85): +CHECK: Warnings without flags (86): CHECK-NEXT: ext_excess_initializers CHECK-NEXT: ext_excess_initializers_in_char_array_initializer CHECK-NEXT: ext_expected_semi_decl_list @@ -40,6 +40,7 @@ CHECK-NEXT: pp_out_of_date_dependency CHECK-NEXT: pp_poisoning_existing_macro CHECK-NEXT: w_asm_qualifier_ignored CHECK-NEXT: warn_accessor_property_type_mismatch +CHECK-NEXT: warn_apinotes_message CHECK-NEXT: warn_arcmt_nsalloc_realloc CHECK-NEXT: warn_asm_label_on_auto_decl CHECK-NEXT: warn_c_kext diff --git a/clang/test/Sema/attr-availability.c b/clang/test/Sema/attr-availability.c index d003e1e2e363b..e9ebf7fd54dbe 100644 --- a/clang/test/Sema/attr-availability.c +++ b/clang/test/Sema/attr-availability.c @@ -81,6 +81,21 @@ extern int x2 __attribute__((availability(macosx,introduced=10.2))); // expected extern int x2 __attribute__((availability(macosx,introduced=10.5))); // expected-warning {{availability does not match previous declaration}} + +#if __has_feature(attribute_availability_swift) +# warning "okay" +// expected-warning@-1{{okay}} +#else +# error "Missing __has_feature" +#endif + + +extern int noSwiftGlobal1 __attribute__((availability(swift, unavailable))); +extern int noSwiftGlobal1 __attribute__((availability(macosx, introduced=10.1))); // okay +extern int noSwiftGlobal1 __attribute__((availability(swift, unavailable, message="and this one has a message"))); // okay + +extern int noSwiftGlobal2 __attribute__((availability(swift, introduced=5))); // expected-warning{{only 'unavailable' is supported for Swift availability}} + enum Original { OriginalDeprecated __attribute__((availability(macosx, deprecated=10.2))), // expected-note + {{'OriginalDeprecated' has been explicitly marked deprecated here}} OriginalUnavailable __attribute__((availability(macosx, unavailable))) // expected-note + {{'OriginalUnavailable' has been explicitly marked unavailable here}} diff --git a/clang/test/Sema/attr-noescape.c b/clang/test/Sema/attr-noescape.c new file mode 100644 index 0000000000000..ec367b652543c --- /dev/null +++ b/clang/test/Sema/attr-noescape.c @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 %s -fblocks -verify -fsyntax-only + +#if !__has_attribute(noescape) +# error "missing noescape attribute" +#endif + +int *global_var __attribute((noescape)); // expected-warning{{'noescape' attribute only applies to parameters}} + +void foo(__attribute__((noescape)) int *int_ptr, + __attribute__((noescape)) int (^block)(int), + __attribute((noescape)) int integer) { // expected-warning{{'noescape' attribute ignored on parameter of non-pointer type 'int'}} +} diff --git a/clang/test/SemaObjC/attr-swift.m b/clang/test/SemaObjC/attr-swift.m new file mode 100644 index 0000000000000..98c705423f120 --- /dev/null +++ b/clang/test/SemaObjC/attr-swift.m @@ -0,0 +1,158 @@ +// RUN: %clang_cc1 -verify -fsyntax-only -fobjc-arc -fblocks %s + +// --- swift_private --- + +__attribute__((swift_private)) +@protocol FooProto +@end + +__attribute__((swift_private)) +@interface Foo +@end + +@interface Bar +@property id prop __attribute__((swift_private)); +- (void)instMethod __attribute__((swift_private)); ++ (instancetype)bar __attribute__((swift_private)); +@end + +void function(id) __attribute__((swift_private)); + +struct __attribute__((swift_private)) Point { + int x; + int y; +}; + +enum __attribute__((swift_private)) Colors { + Red, Green, Blue +}; + +typedef struct { + float x, y, z; +} Point3D __attribute__((swift_private)); + + +// --- swift_name --- + +__attribute__((swift_name("SNFooType"))) +@protocol SNFoo +@end + +__attribute__((swift_name("SNFooClass"))) +@interface SNFoo +- (instancetype)init __attribute__((swift_name("init()"))); +- (instancetype)initWithValue:(int)value __attribute__((swift_name("fooWithValue(_:)"))); + ++ (void)refresh __attribute__((swift_name("refresh()"))); + ++ (instancetype)foo __attribute__((swift_name("foo()"))); ++ (SNFoo *)fooWithValue:(int)value __attribute__((swift_name("foo(value:)"))); ++ (SNFoo *)fooWithValue:(int)value value:(int)value2 __attribute__((swift_name("foo(value:extra:)"))); ++ (SNFoo *)fooWithConvertingValue:(int)value value:(int)value2 __attribute__((swift_name("init(_:extra:)"))); + ++ (SNFoo *)fooWithOtherValue:(int)value __attribute__((swift_name("init"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} ++ (SNFoo *)fooWithAnotherValue:(int)value __attribute__((swift_name("foo()"))); // expected-warning {{too few parameters in 'swift_name' attribute (expected 1; got 0)}} ++ (SNFoo *)fooWithYetAnotherValue:(int)value __attribute__((swift_name("foo(value:extra:)"))); // expected-warning {{too many parameters in 'swift_name' attribute (expected 1; got 2)}} + ++ (SNFoo *)fooAndReturnErrorCode:(int *)errorCode __attribute__((swift_name("foo()"))); // no-warning ++ (SNFoo *)fooWithValue:(int)value andReturnErrorCode:(int *)errorCode __attribute__((swift_name("foo(value:)"))); // no-warning ++ (SNFoo *)fooFromErrorCode:(const int *)errorCode __attribute__((swift_name("foo()"))); // expected-warning {{too few parameters in 'swift_name' attribute (expected 1; got 0)}} ++ (SNFoo *)fooWithValue:(int)value fromErrorCode:(const int *)errorCode __attribute__((swift_name("foo(value:)"))); // expected-warning {{too few parameters in 'swift_name' attribute (expected 2; got 1)}} ++ (SNFoo *)fooWithPointerA:(int *)value andReturnErrorCode:(int *)errorCode __attribute__((swift_name("foo()"))); // no-warning ++ (SNFoo *)fooWithPointerB:(int *)value andReturnErrorCode:(int *)errorCode __attribute__((swift_name("foo(pointer:)"))); // no-warning ++ (SNFoo *)fooWithPointerC:(int *)value andReturnErrorCode:(int *)errorCode __attribute__((swift_name("foo(pointer:errorCode:)"))); // no-warning ++ (SNFoo *)fooWithOtherFoo:(SNFoo *)other __attribute__((swift_name("foo()"))); // expected-warning {{too few parameters in 'swift_name' attribute (expected 1; got 0)}} + ++ (instancetype)specialFoo __attribute__((swift_name("init(options:)"))); ++ (instancetype)specialBar __attribute__((swift_name("init(options:extra:)"))); // expected-warning {{too many parameters in 'swift_name' attribute (expected 0; got 2)}} ++ (instancetype)specialBaz __attribute__((swift_name("init(_:)"))); // expected-warning {{too many parameters in 'swift_name' attribute (expected 0; got 1)}} ++ (instancetype)specialGarply __attribute__((swift_name("foo(options:)"))); // expected-warning {{too many parameters in 'swift_name' attribute (expected 0; got 1)}} + ++ (instancetype)trailingParen __attribute__((swift_name("foo("))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} ++ (instancetype)trailingColon:(int)value __attribute__((swift_name("foo(value)"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} ++ (instancetype)initialIgnore:(int)value __attribute__((swift_name("_(value:)"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} ++ (instancetype)middleOmitted:(int)value __attribute__((swift_name("foo(:)"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} + +@property(strong) id someProp __attribute__((swift_name("prop"))); +@end + +enum __attribute__((swift_name("MoreColors"))) MoreColors { + Cyan, + Magenta, + Yellow __attribute__((swift_name("RoseGold"))), + Black __attribute__((swift_name("SpaceGrey()"))) // expected-error {{parameter of 'swift_name' attribute must be an ASCII identifier string}} +}; + +struct __attribute__((swift_name("FooStruct"))) BarStruct { + int x, y, z __attribute__((swift_name("zed"))); +}; + +int global_int __attribute__((swift_name("GlobalInt"))); + +void foo1(int i) __attribute__((swift_name("foo"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} +void foo2(int i) __attribute__((swift_name("foo()"))); // expected-warning{{too few parameters in 'swift_name' attribute (expected 1; got 0)}} +void foo2(int i) __attribute__((swift_name("foo(a:b:)"))); // expected-warning{{too many parameters in 'swift_name' attribute (expected 1; got 2)}} +void foo3(int i, int j) __attribute__((swift_name("fooWithX(_:y:)"))); // okay +void foo4(int i, int *error) __attribute__((swift_name("fooWithA(_:)"))); // okay + +typedef int some_int_type __attribute__((swift_name("SomeInt"))); + +// --- swift_error --- + +@class NSError; + +typedef struct __attribute__((objc_bridge(NSError))) __CFError *CFErrorRef; + +@interface Erroneous +- (_Bool) tom0: (NSError**) err __attribute__((swift_error(none))); +- (_Bool) tom1: (NSError**) err __attribute__((swift_error(nonnull_error))); +- (_Bool) tom2: (NSError**) err __attribute__((swift_error(null_result))); // expected-error {{'swift_error' attribute with 'null_result' convention can only be applied to a method returning a pointer}} +- (_Bool) tom3: (NSError**) err __attribute__((swift_error(nonzero_result))); +- (_Bool) tom4: (NSError**) err __attribute__((swift_error(zero_result))); + +- (Undeclared) richard0: (NSError**) err __attribute__((swift_error(none))); // expected-error {{expected a type}} +- (Undeclared) richard1: (NSError**) err __attribute__((swift_error(nonnull_error))); // expected-error {{expected a type}} +- (Undeclared) richard2: (NSError**) err __attribute__((swift_error(null_result))); // expected-error {{expected a type}} +// FIXME: the follow-on warnings should really be suppressed, but apparently having an ill-formed return type doesn't mark anything as invalid +- (Undeclared) richard3: (NSError**) err __attribute__((swift_error(nonzero_result))); // expected-error {{expected a type}} expected-error {{can only be applied}} +- (Undeclared) richard4: (NSError**) err __attribute__((swift_error(zero_result))); // expected-error {{expected a type}} expected-error {{can only be applied}} + +- (instancetype) harry0: (NSError**) err __attribute__((swift_error(none))); +- (instancetype) harry1: (NSError**) err __attribute__((swift_error(nonnull_error))); +- (instancetype) harry2: (NSError**) err __attribute__((swift_error(null_result))); +- (instancetype) harry3: (NSError**) err __attribute__((swift_error(nonzero_result))); // expected-error {{'swift_error' attribute with 'nonzero_result' convention can only be applied to a method returning an integral type}} +- (instancetype) harry4: (NSError**) err __attribute__((swift_error(zero_result))); // expected-error {{'swift_error' attribute with 'zero_result' convention can only be applied to a method returning an integral type}} + +- (instancetype) harry0 __attribute__((swift_error(none))); +- (instancetype) harry1 __attribute__((swift_error(nonnull_error))); // expected-error {{'swift_error' attribute can only be applied to a method with an error parameter}} +- (instancetype) harry2 __attribute__((swift_error(null_result))); // expected-error {{'swift_error' attribute can only be applied to a method with an error parameter}} +- (instancetype) harry3 __attribute__((swift_error(nonzero_result))); // expected-error {{'swift_error' attribute can only be applied to a method with an error parameter}} +- (instancetype) harry4 __attribute__((swift_error(zero_result))); // expected-error {{'swift_error' attribute can only be applied to a method with an error parameter}} +@end + +extern _Bool tom0(CFErrorRef *) __attribute__((swift_error(none))); +extern _Bool tom1(CFErrorRef *) __attribute__((swift_error(nonnull_error))); +extern _Bool tom2(CFErrorRef *) __attribute__((swift_error(null_result))); // expected-error {{'swift_error' attribute with 'null_result' convention can only be applied to a function returning a pointer}} +extern _Bool tom3(CFErrorRef *) __attribute__((swift_error(nonzero_result))); +extern _Bool tom4(CFErrorRef *) __attribute__((swift_error(zero_result))); + +extern Undeclared richard0(CFErrorRef *) __attribute__((swift_error(none))); // expected-error {{unknown type name 'Undeclared'}} +extern Undeclared richard1(CFErrorRef *) __attribute__((swift_error(nonnull_error))); // expected-error {{unknown type name 'Undeclared'}} +extern Undeclared richard2(CFErrorRef *) __attribute__((swift_error(null_result))); // expected-error {{unknown type name 'Undeclared'}} +extern Undeclared richard3(CFErrorRef *) __attribute__((swift_error(nonzero_result))); // expected-error {{unknown type name 'Undeclared'}} +extern Undeclared richard4(CFErrorRef *) __attribute__((swift_error(zero_result))); // expected-error {{unknown type name 'Undeclared'}} + +extern void *harry0(CFErrorRef *) __attribute__((swift_error(none))); +extern void *harry1(CFErrorRef *) __attribute__((swift_error(nonnull_error))); +extern void *harry2(CFErrorRef *) __attribute__((swift_error(null_result))); +extern void *harry3(CFErrorRef *) __attribute__((swift_error(nonzero_result))); // expected-error {{'swift_error' attribute with 'nonzero_result' convention can only be applied to a function returning an integral type}} +extern void *harry4(CFErrorRef *) __attribute__((swift_error(zero_result))); // expected-error {{'swift_error' attribute with 'zero_result' convention can only be applied to a function returning an integral type}} + +extern void *wilma0(void) __attribute__((swift_error(none))); +extern void *wilma1(void) __attribute__((swift_error(nonnull_error))); // expected-error {{'swift_error' attribute can only be applied to a function with an error parameter}} +extern void *wilma2(void) __attribute__((swift_error(null_result))); // expected-error {{'swift_error' attribute can only be applied to a function with an error parameter}} +extern void *wilma3(void) __attribute__((swift_error(nonzero_result))); // expected-error {{'swift_error' attribute can only be applied to a function with an error parameter}} +extern void *wilma4(void) __attribute__((swift_error(zero_result))); // expected-error {{'swift_error' attribute can only be applied to a function with an error parameter}} + + +extern _Bool suzanne __attribute__((swift_error(none))); // expected-error {{'swift_error' attribute only applies to functions and methods}} diff --git a/clang/test/SemaObjC/subclassing-restricted-attr.m b/clang/test/SemaObjC/subclassing-restricted-attr.m new file mode 100644 index 0000000000000..2e77df8763aab --- /dev/null +++ b/clang/test/SemaObjC/subclassing-restricted-attr.m @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wno-objc-root-class %s +// RUN: %clang_cc1 -x objective-c++ -fsyntax-only -verify -Wno-objc-root-class %s +// rdar://16560476 + +__attribute__((objc_subclassing_restricted)) +@interface Leaf // okay +@end + +__attribute__((objc_subclassing_restricted)) +@interface SubClassOfLeaf : Leaf // expected-note {{class is declared here}} +@end + + +@interface SubClass : SubClassOfLeaf // expected-error {{cannot subclass a class with objc_subclassing_restricted attribute}} +@end + +__attribute__((objc_root_class)) +@interface PlainRoot +@end + +__attribute__((objc_subclassing_restricted)) +@interface Sub2Class : PlainRoot // okay +@end diff --git a/clang/tools/arcmt-test/Makefile b/clang/tools/arcmt-test/Makefile index d9d44bb05bd26..ec7683b368221 100644 --- a/clang/tools/arcmt-test/Makefile +++ b/clang/tools/arcmt-test/Makefile @@ -21,6 +21,6 @@ LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option USEDLIBS = clangARCMigrate.a clangRewrite.a \ clangFrontend.a clangDriver.a clangSerialization.a clangParse.a \ clangSema.a clangEdit.a clangAnalysis.a clangAST.a clangLex.a \ - clangBasic.a + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/clang/tools/c-arcmt-test/Makefile b/clang/tools/c-arcmt-test/Makefile index 03e0c9e58c4d5..ec5e122d0d1a5 100644 --- a/clang/tools/c-arcmt-test/Makefile +++ b/clang/tools/c-arcmt-test/Makefile @@ -45,6 +45,7 @@ USEDLIBS = clang.a \ clangFrontend.a clangDriver.a \ clangStaticAnalyzerCheckers.a clangStaticAnalyzerCore.a \ clangSerialization.a clangParse.a clangSema.a \ - clangAnalysis.a clangEdit.a clangAST.a clangLex.a clangBasic.a + clangAnalysis.a clangEdit.a clangAST.a clangLex.a clangAPINotes.a \ + clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/clang/tools/c-index-test/Makefile b/clang/tools/c-index-test/Makefile index b757b477958e3..7e133770b1c77 100644 --- a/clang/tools/c-index-test/Makefile +++ b/clang/tools/c-index-test/Makefile @@ -42,7 +42,7 @@ USEDLIBS = clang.a \ clangToolingCore.a \ clangSerialization.a clangParse.a clangSema.a \ clangAnalysis.a clangEdit.a clangAST.a clangLex.a \ - clangBasic.a + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/clang/tools/clang-check/CMakeLists.txt b/clang/tools/clang-check/CMakeLists.txt index 04151a8e0331d..233f9818fc963 100644 --- a/clang/tools/clang-check/CMakeLists.txt +++ b/clang/tools/clang-check/CMakeLists.txt @@ -9,6 +9,7 @@ add_clang_executable(clang-check ) target_link_libraries(clang-check + clangAPINotes clangAST clangBasic clangDriver diff --git a/clang/tools/clang-check/Makefile b/clang/tools/clang-check/Makefile index da010ab1f32a5..f2e280d920b45 100644 --- a/clang/tools/clang-check/Makefile +++ b/clang/tools/clang-check/Makefile @@ -23,6 +23,6 @@ USEDLIBS = clangFrontend.a clangCodeGen.a clangIndex.a \ clangStaticAnalyzerFrontend.a clangStaticAnalyzerCheckers.a \ clangStaticAnalyzerCore.a clangAnalysis.a clangRewriteFrontend.a \ clangRewrite.a clangEdit.a clangAST.a clangLex.a \ - clangBasic.a + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/clang/tools/clang-format/Makefile b/clang/tools/clang-format/Makefile index 76e31cc1a0725..58642f140b920 100644 --- a/clang/tools/clang-format/Makefile +++ b/clang/tools/clang-format/Makefile @@ -17,6 +17,6 @@ TOOL_NO_EXPORTS = 1 include $(CLANG_LEVEL)/../../Makefile.config LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option USEDLIBS = clangFormat.a clangToolingCore.a clangDriver.a clangRewrite.a \ - clangLex.a clangBasic.a + clangLex.a clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/clang/tools/diagtool/Makefile b/clang/tools/diagtool/Makefile index d49e976e6428b..b5026210433d0 100644 --- a/clang/tools/diagtool/Makefile +++ b/clang/tools/diagtool/Makefile @@ -20,7 +20,7 @@ include $(CLANG_LEVEL)/../../Makefile.config LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option USEDLIBS = clangFrontend.a clangDriver.a clangSerialization.a clangParse.a \ clangSema.a clangAnalysis.a clangEdit.a clangAST.a clangLex.a \ - clangBasic.a + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/clang/tools/driver/CMakeLists.txt b/clang/tools/driver/CMakeLists.txt index a57b22e0a26d4..73bebb6c73689 100644 --- a/clang/tools/driver/CMakeLists.txt +++ b/clang/tools/driver/CMakeLists.txt @@ -28,10 +28,12 @@ add_clang_executable(clang driver.cpp cc1_main.cpp cc1as_main.cpp + apinotes_main.cpp ) target_link_libraries(clang clangBasic + clangAPINotes clangCodeGen clangDriver clangFrontend diff --git a/clang/tools/driver/Makefile b/clang/tools/driver/Makefile index 347702eb9611e..d2626a1154fa8 100644 --- a/clang/tools/driver/Makefile +++ b/clang/tools/driver/Makefile @@ -47,7 +47,8 @@ ifeq ($(ENABLE_CLANG_ARCMT),1) USEDLIBS += clangARCMigrate.a endif -USEDLIBS += clangAnalysis.a clangEdit.a clangAST.a clangLex.a clangBasic.a +USEDLIBS += clangAnalysis.a clangEdit.a clangAST.a clangLex.a \ + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/clang/tools/driver/apinotes_main.cpp b/clang/tools/driver/apinotes_main.cpp new file mode 100644 index 0000000000000..76892b69ba4d7 --- /dev/null +++ b/clang/tools/driver/apinotes_main.cpp @@ -0,0 +1,149 @@ +//===-- api_notes.cpp - API Notes Driver ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file provides conversion between the YAML (source) and binary forms +/// of API notes. +/// +//===----------------------------------------------------------------------===// +#include "clang/APINotes/APINotesYAMLCompiler.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Signals.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/Triple.h" + +using namespace llvm; +namespace api_notes = clang::api_notes; + +int cc1apinotes_main(ArrayRef Argv, const char *Argv0, + void *MainAddr) { + + // Mark all our options with this category, everything else (except for + // -version and -help) will be hidden. + static cl::OptionCategory APINotesCategory("API Notes options"); + + static cl::opt + Action(cl::desc("Mode:"), cl::init(api_notes::ActionType::None), + cl::values( + clEnumValN(api_notes::ActionType::YAMLToBinary, + "yaml-to-binary", + "Convert YAML to binary format"), + clEnumValN(api_notes::ActionType::BinaryToYAML, + "binary-to-yaml", + "Convert binary format to YAML"), + clEnumValN(api_notes::ActionType::Dump, + "dump", + "Parse and dump the output"), + clEnumValEnd), + cl::cat(APINotesCategory)); + + static cl::opt + InputFilename(cl::Positional, cl::desc(""), + cl::Required, cl::cat(APINotesCategory)); + + static cl::opt + Target("target", cl::desc("Generate binary format for the given target"), + cl::cat(APINotesCategory)); + + static cl::opt + OutputFilename("o", cl::desc("Output file name"), cl::cat(APINotesCategory)); + + cl::HideUnrelatedOptions(APINotesCategory); + + SmallVector Args; + Args.push_back(Argv0); + Args.append(Argv.begin(), Argv.end()); + cl::ParseCommandLineOptions(Args.size(), + Args.data(), + "Clang API Notes Tool\n"); + + if (Action == clang::api_notes::ActionType::None) { + errs() << "action required\n"; + cl::PrintHelpMessage(); + return 1; + } + + auto fileBufOrErr = MemoryBuffer::getFile(InputFilename); + if (std::error_code EC = fileBufOrErr.getError()) { + llvm::errs() << "\n Could not open input file: " + EC.message() << '\n'; + return true; + } + StringRef input = fileBufOrErr.get()->getBuffer(); + + switch (Action) { + case api_notes::ActionType::None: + llvm_unreachable("handled above"); + + case api_notes::ActionType::YAMLToBinary: { + if (OutputFilename.empty()) { + errs() << "output file is required\n"; + cl::PrintHelpMessage(); + return 1; + } + + api_notes::OSType targetOS = api_notes::OSType::Absent; + // TODO: Check that we've specified the target. + if (!Target.empty()) { + llvm::Triple target(llvm::Triple::normalize(Target)); + switch (target.getOS()) { + case llvm::Triple::Darwin: + case llvm::Triple::MacOSX: + targetOS = api_notes::OSType::OSX; + break; + case llvm::Triple::IOS: + targetOS = api_notes::OSType::IOS; + break; + default: + errs() << "target is not supported\n"; + return 1; + } + } + std::error_code EC; + llvm::raw_fd_ostream os(OutputFilename, EC, + llvm::sys::fs::OpenFlags::F_None); + + if (api_notes::compileAPINotes(input, os, targetOS)) + return 1; + + os.flush(); + + return os.has_error(); + } + + case api_notes::ActionType::BinaryToYAML: { + if (OutputFilename.empty()) { + errs() << "output file required\n"; + cl::PrintHelpMessage(); + return 1; + } + + std::error_code EC; + llvm::raw_fd_ostream os(OutputFilename, EC, + llvm::sys::fs::OpenFlags::F_None); + + if (api_notes::decompileAPINotes(std::move(fileBufOrErr.get()), os)) + return 1; + + os.flush(); + + return os.has_error(); + } + + case api_notes::ActionType::Dump: + return api_notes::parseAndDumpAPINotes(input); + } + + return 1; +} + diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp index ea218d5403d83..00ba309edaf31 100644 --- a/clang/tools/driver/driver.cpp +++ b/clang/tools/driver/driver.cpp @@ -200,6 +200,8 @@ extern int cc1_main(ArrayRef Argv, const char *Argv0, void *MainAddr); extern int cc1as_main(ArrayRef Argv, const char *Argv0, void *MainAddr); +extern int cc1apinotes_main(ArrayRef Argv, const char *Argv0, + void *MainAddr); static void insertTargetAndModeArgs(StringRef Target, StringRef Mode, SmallVectorImpl &ArgVector, @@ -301,6 +303,8 @@ static int ExecuteCC1Tool(ArrayRef argv, StringRef Tool) { return cc1_main(argv.slice(2), argv[0], GetExecutablePathVP); if (Tool == "as") return cc1as_main(argv.slice(2), argv[0], GetExecutablePathVP); + if (Tool == "apinotes") + return cc1apinotes_main(argv.slice(2), argv[0], GetExecutablePathVP); // Reject unknown tools. llvm::errs() << "error: unknown integrated tool '" << Tool << "'\n"; diff --git a/clang/tools/libclang/CMakeLists.txt b/clang/tools/libclang/CMakeLists.txt index e8c247bf4264d..07fc17e0a4e1d 100644 --- a/clang/tools/libclang/CMakeLists.txt +++ b/clang/tools/libclang/CMakeLists.txt @@ -39,6 +39,7 @@ set(SOURCES set(LIBS clangAST + clangAPINotes clangBasic clangFrontend clangIndex diff --git a/clang/tools/libclang/Makefile b/clang/tools/libclang/Makefile index 84914e0f4609f..1b8f5f924f757 100644 --- a/clang/tools/libclang/Makefile +++ b/clang/tools/libclang/Makefile @@ -30,7 +30,7 @@ USEDLIBS = clangIndex.a clangARCMigrate.a \ clangRewrite.a \ clangAnalysis.a clangEdit.a \ clangASTMatchers.a \ - clangAST.a clangLex.a clangBasic.a + clangAST.a clangLex.a clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/Makefile diff --git a/clang/unittests/AST/DeclTest.cpp b/clang/unittests/AST/DeclTest.cpp index 87aeef47c61fb..67b80acf46bbf 100644 --- a/clang/unittests/AST/DeclTest.cpp +++ b/clang/unittests/AST/DeclTest.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "MatchVerifier.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Tooling/Tooling.h" #include "gtest/gtest.h" @@ -57,3 +58,53 @@ TEST(Decl, CleansUpAPValues) { "constexpr _Complex __uint128_t c = 0xffffffffffffffff;", Args)); } + +TEST(Decl, Availability) { + const char *CodeStr = "int x __attribute__((availability(macosx, " + "introduced=10.2, deprecated=10.8, obsoleted=10.10)));"; + auto Matcher = varDecl(hasName("x")); + std::vector Args = {"-target", "x86_64-apple-macosx10.9"}; + + class AvailabilityVerifier : public MatchVerifier { + public: + void verify(const MatchFinder::MatchResult &Result, + const clang::VarDecl &Node) override { + if (Node.getAvailability(nullptr, clang::VersionTuple(10, 1)) != + clang::AR_NotYetIntroduced) { + setFailure("failed introduced"); + } + if (Node.getAvailability(nullptr, clang::VersionTuple(10, 2)) != + clang::AR_Available) { + setFailure("failed available (exact)"); + } + if (Node.getAvailability(nullptr, clang::VersionTuple(10, 3)) != + clang::AR_Available) { + setFailure("failed available"); + } + if (Node.getAvailability(nullptr, clang::VersionTuple(10, 8)) != + clang::AR_Deprecated) { + setFailure("failed deprecated (exact)"); + } + if (Node.getAvailability(nullptr, clang::VersionTuple(10, 9)) != + clang::AR_Deprecated) { + setFailure("failed deprecated"); + } + if (Node.getAvailability(nullptr, clang::VersionTuple(10, 10)) != + clang::AR_Unavailable) { + setFailure("failed obsoleted (exact)"); + } + if (Node.getAvailability(nullptr, clang::VersionTuple(10, 11)) != + clang::AR_Unavailable) { + setFailure("failed obsoleted"); + } + + if (Node.getAvailability() != clang::AR_Deprecated) + setFailure("did not default to target OS version"); + + setSuccess(); + } + }; + + AvailabilityVerifier Verifier; + EXPECT_TRUE(Verifier.match(CodeStr, Matcher, Args, Lang_C)); +} diff --git a/clang/unittests/AST/Makefile b/clang/unittests/AST/Makefile index e3b3d7dc33e8c..a306ac94568d4 100644 --- a/clang/unittests/AST/Makefile +++ b/clang/unittests/AST/Makefile @@ -14,6 +14,7 @@ LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ clangRewrite.a clangRewriteFrontend.a \ clangParse.a clangSema.a clangAnalysis.a \ - clangEdit.a clangAST.a clangASTMatchers.a clangLex.a clangBasic.a + clangEdit.a clangAST.a clangASTMatchers.a clangLex.a \ + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/ASTMatchers/Dynamic/Makefile b/clang/unittests/ASTMatchers/Dynamic/Makefile index df253b8a1d32a..d0c48528f05ee 100644 --- a/clang/unittests/ASTMatchers/Dynamic/Makefile +++ b/clang/unittests/ASTMatchers/Dynamic/Makefile @@ -17,6 +17,7 @@ USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ clangDynamicASTMatchers.a \ clangAnalysis.a clangEdit.a clangAST.a clangASTMatchers.a \ clangLex.a \ - clangBasic.a + clangBasic.a \ + clangAPINotes.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/ASTMatchers/Makefile b/clang/unittests/ASTMatchers/Makefile index 92f2fa0e5d179..accbca0552e49 100644 --- a/clang/unittests/ASTMatchers/Makefile +++ b/clang/unittests/ASTMatchers/Makefile @@ -17,6 +17,7 @@ LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ clangRewrite.a clangRewriteFrontend.a \ clangParse.a clangSema.a clangAnalysis.a \ - clangEdit.a clangAST.a clangASTMatchers.a clangLex.a clangBasic.a + clangEdit.a clangAST.a clangASTMatchers.a clangLex.a \ + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/CodeGen/Makefile b/clang/unittests/CodeGen/Makefile index de347e1afddc9..01bdf76789676 100644 --- a/clang/unittests/CodeGen/Makefile +++ b/clang/unittests/CodeGen/Makefile @@ -15,6 +15,6 @@ LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader mc option \ USEDLIBS = clangCodeGen.a clangFrontend.a clangSerialization.a \ clangDriver.a \ clangParse.a clangSema.a clangAnalysis.a \ - clangEdit.a clangAST.a clangLex.a clangBasic.a + clangEdit.a clangAST.a clangLex.a clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/Format/Makefile b/clang/unittests/Format/Makefile index f95d6d34127bc..7029ea7002834 100644 --- a/clang/unittests/Format/Makefile +++ b/clang/unittests/Format/Makefile @@ -14,6 +14,6 @@ LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option USEDLIBS = clangFormat.a clangTooling.a clangToolingCore.a clangFrontend.a \ clangSerialization.a clangDriver.a clangParse.a clangRewrite.a \ clangRewriteFrontend.a clangSema.a clangAnalysis.a clangEdit.a \ - clangAST.a clangASTMatchers.a clangLex.a clangBasic.a + clangAST.a clangASTMatchers.a clangLex.a clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/Frontend/Makefile b/clang/unittests/Frontend/Makefile index a63ae18245349..a6b6091a6dda8 100644 --- a/clang/unittests/Frontend/Makefile +++ b/clang/unittests/Frontend/Makefile @@ -16,6 +16,6 @@ USEDLIBS = clangFrontendTool.a clangFrontend.a clangDriver.a \ clangStaticAnalyzerCheckers.a clangStaticAnalyzerCore.a \ clangARCMigrate.a clangRewrite.a \ clangRewriteFrontend.a clangEdit.a \ - clangAnalysis.a clangAST.a clangLex.a clangBasic.a + clangAnalysis.a clangAST.a clangLex.a clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/Lex/CMakeLists.txt b/clang/unittests/Lex/CMakeLists.txt index 461e0d95fc87e..68730b40d7ca0 100644 --- a/clang/unittests/Lex/CMakeLists.txt +++ b/clang/unittests/Lex/CMakeLists.txt @@ -12,6 +12,7 @@ target_link_libraries(LexTests clangAST clangBasic clangLex + clangAPINotes clangParse clangSema ) diff --git a/clang/unittests/Lex/Makefile b/clang/unittests/Lex/Makefile index 071d01c8b567b..c34ef76de7bb5 100644 --- a/clang/unittests/Lex/Makefile +++ b/clang/unittests/Lex/Makefile @@ -11,6 +11,6 @@ CLANG_LEVEL = ../.. TESTNAME = Lex LINK_COMPONENTS := mcparser support mc bitreader USEDLIBS = clangParse.a clangSema.a clangAnalysis.a clangEdit.a \ - clangSerialization.a clangAST.a clangLex.a clangBasic.a + clangSerialization.a clangAST.a clangLex.a clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/Sema/Makefile b/clang/unittests/Sema/Makefile index 7fd5c27ad607b..ef8852dd90860 100644 --- a/clang/unittests/Sema/Makefile +++ b/clang/unittests/Sema/Makefile @@ -14,6 +14,7 @@ LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ clangRewrite.a clangRewriteFrontend.a \ clangParse.a clangSema.a clangAnalysis.a \ - clangEdit.a clangAST.a clangASTMatchers.a clangLex.a clangBasic.a + clangEdit.a clangAST.a clangASTMatchers.a clangLex.a \ + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/Tooling/Makefile b/clang/unittests/Tooling/Makefile index 514e80bd03098..93483d93cadd6 100644 --- a/clang/unittests/Tooling/Makefile +++ b/clang/unittests/Tooling/Makefile @@ -15,6 +15,6 @@ USEDLIBS = clangTooling.a clangToolingCore.a clangFrontend.a \ clangSerialization.a clangDriver.a \ clangParse.a clangRewrite.a clangRewriteFrontend.a \ clangSema.a clangAnalysis.a clangEdit.a \ - clangAST.a clangASTMatchers.a clangLex.a clangBasic.a + clangAST.a clangASTMatchers.a clangLex.a clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/clang/unittests/libclang/Makefile b/clang/unittests/libclang/Makefile index 037dea34d299f..4dbafb7a2d075 100644 --- a/clang/unittests/libclang/Makefile +++ b/clang/unittests/libclang/Makefile @@ -32,6 +32,6 @@ USEDLIBS = clang.a \ clangToolingCore.a \ clangSerialization.a clangParse.a clangSema.a \ clangAnalysis.a clangEdit.a clangAST.a clangLex.a \ - clangBasic.a + clangAPINotes.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile From 03530f9358d206e57435e6906a74447c69ab9936 Mon Sep 17 00:00:00 2001 From: Frederic Riss Date: Thu, 5 Nov 2015 17:53:42 -0800 Subject: [PATCH 002/585] Apply Swift-related changes to the swift-llvm repo apple-llvm-split-commit: 3e2331faf2911f1ac0ca73c6ec15b4cf34393174 apple-llvm-split-dir: llvm/ --- llvm/docs/LangRef.rst | 11 + llvm/include/llvm-c/Core.h | 2 + llvm/include/llvm/ADT/DenseMapInfo.h | 81 ++- llvm/include/llvm/ADT/Fixnum.h | 190 +++++++ llvm/include/llvm/Analysis/LoopInfo.h | 2 + llvm/include/llvm/Bitcode/RecordLayout.h | 537 ++++++++++++++++++ llvm/include/llvm/CodeGen/FastISel.h | 5 +- .../llvm/CodeGen/FunctionLoweringInfo.h | 29 + llvm/include/llvm/IR/Argument.h | 6 + llvm/include/llvm/IR/Attributes.h | 2 + llvm/include/llvm/IR/Instructions.h | 12 + llvm/include/llvm/MC/MCAsmInfo.h | 5 + llvm/include/llvm/MC/MCDirectives.h | 1 + llvm/include/llvm/MC/MCSymbolMachO.h | 5 + llvm/include/llvm/Target/TargetCallingConv.h | 10 + llvm/include/llvm/Target/TargetCallingConv.td | 10 + .../include/llvm/Target/TargetFrameLowering.h | 5 +- llvm/include/llvm/Target/TargetLowering.h | 11 +- .../llvm/Transforms/Utils/SSAUpdaterImpl.h | 3 +- llvm/lib/AsmParser/LLLexer.cpp | 3 + llvm/lib/AsmParser/LLParser.cpp | 13 +- llvm/lib/AsmParser/LLToken.h | 3 + llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 29 +- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp | 7 +- llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp | 7 +- llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp | 12 +- llvm/lib/CodeGen/MachineSSAUpdater.cpp | 5 + llvm/lib/CodeGen/PrologEpilogInserter.cpp | 3 +- llvm/lib/CodeGen/SelectionDAG/FastISel.cpp | 20 +- .../SelectionDAG/FunctionLoweringInfo.cpp | 28 + .../SelectionDAG/SelectionDAGBuilder.cpp | 195 ++++++- .../SelectionDAG/SelectionDAGBuilder.h | 2 + .../CodeGen/SelectionDAG/SelectionDAGISel.cpp | 119 ++++ .../CodeGen/SelectionDAG/TargetLowering.cpp | 2 + llvm/lib/IR/AsmWriter.cpp | 3 + llvm/lib/IR/Attributes.cpp | 6 + llvm/lib/IR/Function.cpp | 10 + llvm/lib/IR/Instructions.cpp | 1 + llvm/lib/IR/Verifier.cpp | 35 +- llvm/lib/MC/MCAsmInfo.cpp | 1 + llvm/lib/MC/MCAsmInfoDarwin.cpp | 1 + llvm/lib/MC/MCAsmStreamer.cpp | 1 + llvm/lib/MC/MCELFStreamer.cpp | 3 + llvm/lib/MC/MCMachOStreamer.cpp | 4 + llvm/lib/MC/MCParser/AsmParser.cpp | 7 +- .../AArch64/AArch64CallingConvention.td | 13 + llvm/lib/Target/AArch64/AArch64FastISel.cpp | 31 +- .../Target/AArch64/AArch64FrameLowering.cpp | 170 ++++-- .../lib/Target/AArch64/AArch64FrameLowering.h | 8 + llvm/lib/Target/AArch64/AArch64ISelLowering.h | 4 + .../Target/AArch64/AArch64RegisterInfo.cpp | 19 + llvm/lib/Target/AArch64/AArch64RegisterInfo.h | 5 + llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp | 9 + llvm/lib/Target/ARM/ARMCallingConv.td | 30 + llvm/lib/Target/ARM/ARMFastISel.cpp | 32 ++ llvm/lib/Target/ARM/ARMISelLowering.cpp | 1 + llvm/lib/Target/ARM/ARMISelLowering.h | 4 + .../Target/Hexagon/HexagonFrameLowering.cpp | 3 +- .../lib/Target/Hexagon/HexagonFrameLowering.h | 3 +- llvm/lib/Target/X86/X86CallingConv.td | 28 + llvm/lib/Target/X86/X86FastISel.cpp | 33 ++ llvm/lib/Target/X86/X86FrameLowering.cpp | 3 +- llvm/lib/Target/X86/X86FrameLowering.h | 4 +- llvm/lib/Target/X86/X86ISelLowering.cpp | 2 +- llvm/lib/Target/X86/X86ISelLowering.h | 4 + llvm/lib/Target/X86/X86RegisterInfo.cpp | 6 + llvm/lib/Transforms/Utils/SSAUpdater.cpp | 5 + llvm/test/Bitcode/swifterror.ll | 8 + llvm/test/Bitcode/swiftself.ll | 8 + llvm/test/CodeGen/AArch64/swifterror.ll | 371 ++++++++++++ llvm/test/CodeGen/AArch64/swiftself.ll | 29 + llvm/test/CodeGen/ARM/swift-ios.ll | 73 +++ llvm/test/CodeGen/ARM/swift-return.ll | 130 +++++ llvm/test/CodeGen/ARM/swifterror.ll | 368 ++++++++++++ llvm/test/CodeGen/ARM/swiftself.ll | 32 ++ llvm/test/CodeGen/X86/alias-gep.ll | 22 + llvm/test/CodeGen/X86/swift-return.ll | 135 +++++ llvm/test/CodeGen/X86/swifterror.ll | 276 +++++++++ llvm/test/CodeGen/X86/swiftself.ll | 29 + llvm/test/Transforms/GlobalOpt/alias-used.ll | 19 + llvm/test/Verifier/swifterror.ll | 4 + llvm/test/Verifier/swiftself.ll | 4 + 82 files changed, 3300 insertions(+), 72 deletions(-) create mode 100644 llvm/include/llvm/ADT/Fixnum.h create mode 100644 llvm/include/llvm/Bitcode/RecordLayout.h create mode 100644 llvm/test/Bitcode/swifterror.ll create mode 100644 llvm/test/Bitcode/swiftself.ll create mode 100644 llvm/test/CodeGen/AArch64/swifterror.ll create mode 100644 llvm/test/CodeGen/AArch64/swiftself.ll create mode 100644 llvm/test/CodeGen/ARM/swift-ios.ll create mode 100644 llvm/test/CodeGen/ARM/swift-return.ll create mode 100644 llvm/test/CodeGen/ARM/swifterror.ll create mode 100644 llvm/test/CodeGen/ARM/swiftself.ll create mode 100644 llvm/test/CodeGen/X86/alias-gep.ll create mode 100644 llvm/test/CodeGen/X86/swift-return.ll create mode 100644 llvm/test/CodeGen/X86/swifterror.ll create mode 100644 llvm/test/CodeGen/X86/swiftself.ll create mode 100644 llvm/test/Verifier/swifterror.ll create mode 100644 llvm/test/Verifier/swiftself.ll diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 388bb6b164203..1062778ac5742 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1037,6 +1037,17 @@ Currently, only the following parameter attributes are defined: ``dereferenceable()``). This attribute may only be applied to pointer typed parameters. +``swiftself`` + This indicates that the parameter is the self/context parameter. This is not + a valid attribute for return values and can only be applied to one + parameter. + +``swifterror`` + This indicates that the parameter is a pointer type. That pointer holds a + pointer to the error object. We can only load and store from the parameter + to get the pointer to the error object. This is not a valid attribute for + return values and can only be applied to one parameter. + .. _gc: Garbage Collector Strategy Names diff --git a/llvm/include/llvm-c/Core.h b/llvm/include/llvm-c/Core.h index 519363271ad57..91ae239afc59b 100644 --- a/llvm/include/llvm-c/Core.h +++ b/llvm/include/llvm-c/Core.h @@ -170,6 +170,8 @@ typedef enum { LLVMJumpTableAttribute = 1ULL << 45, LLVMConvergentAttribute = 1ULL << 46, LLVMSafeStackAttribute = 1ULL << 47, + LLVMSwiftSelfAttribute = 1ULL << 48, + LLVMSwiftErrorAttribute = 1ULL << 49, */ } LLVMAttribute; diff --git a/llvm/include/llvm/ADT/DenseMapInfo.h b/llvm/include/llvm/ADT/DenseMapInfo.h index a844ebcccf5b8..d7c34876be71a 100644 --- a/llvm/include/llvm/ADT/DenseMapInfo.h +++ b/llvm/include/llvm/ADT/DenseMapInfo.h @@ -19,6 +19,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/PointerLikeTypeTraits.h" #include "llvm/Support/type_traits.h" +#include namespace llvm { @@ -132,6 +133,20 @@ template<> struct DenseMapInfo { } }; +/// Simplistic combination of 32-bit hash values into 32-bit hash values. +static inline unsigned combineHashValue(unsigned a, unsigned b) { + uint64_t key = (uint64_t)a << 32 | (uint64_t)b; + key += ~(key << 32); + key ^= (key >> 22); + key += ~(key << 13); + key ^= (key >> 8); + key += (key << 3); + key ^= (key >> 15); + key += ~(key << 27); + key ^= (key >> 31); + return (unsigned)key; +} + // Provide DenseMapInfo for all pairs whose members have info. template struct DenseMapInfo > { @@ -148,17 +163,8 @@ struct DenseMapInfo > { SecondInfo::getTombstoneKey()); } static unsigned getHashValue(const Pair& PairVal) { - uint64_t key = (uint64_t)FirstInfo::getHashValue(PairVal.first) << 32 - | (uint64_t)SecondInfo::getHashValue(PairVal.second); - key += ~(key << 32); - key ^= (key >> 22); - key += ~(key << 13); - key ^= (key >> 8); - key += (key << 3); - key ^= (key >> 15); - key += ~(key << 27); - key ^= (key >> 31); - return (unsigned)key; + return combineHashValue(FirstInfo::getHashValue(PairVal.first), + SecondInfo::getHashValue(PairVal.second)); } static bool isEqual(const Pair &LHS, const Pair &RHS) { return FirstInfo::isEqual(LHS.first, RHS.first) && @@ -166,6 +172,59 @@ struct DenseMapInfo > { } }; +template +struct DenseMapInfo > { + typedef std::tuple Tuple; + + /// Helper class + template struct UnsignedC { }; + + static inline Tuple getEmptyKey() { + return Tuple(DenseMapInfo::getEmptyKey()...); + } + + static inline Tuple getTombstoneKey() { + return Tuple(DenseMapInfo::getTombstoneKey()...); + } + + template + static unsigned getHashValueImpl(const Tuple& values, std::false_type) { + typedef typename std::tuple_element::type EltType; + std::integral_constant atEnd; + return combineHashValue( + DenseMapInfo::getHashValue(std::get(values)), + getHashValueImpl(values, atEnd)); + } + + template + static unsigned getHashValueImpl(const Tuple& values, std::true_type) { + return 0; + } + + static unsigned getHashValue(const std::tuple& values) { + std::integral_constant atEnd; + return getHashValueImpl<0>(values, atEnd); + } + + template + static bool isEqualImpl(const Tuple &lhs, const Tuple &rhs, std::false_type) { + typedef typename std::tuple_element::type EltType; + std::integral_constant atEnd; + return DenseMapInfo::isEqual(std::get(lhs), std::get(rhs)) + && isEqualImpl(lhs, rhs, atEnd); + } + + template + static bool isEqualImpl(const Tuple &lhs, const Tuple &rhs, std::true_type) { + return true; + } + + static bool isEqual(const Tuple &lhs, const Tuple &rhs) { + std::integral_constant atEnd; + return isEqualImpl<0>(lhs, rhs, atEnd); + } +}; + // Provide DenseMapInfo for StringRefs. template <> struct DenseMapInfo { static inline StringRef getEmptyKey() { diff --git a/llvm/include/llvm/ADT/Fixnum.h b/llvm/include/llvm/ADT/Fixnum.h new file mode 100644 index 0000000000000..033cebe6f7574 --- /dev/null +++ b/llvm/include/llvm/ADT/Fixnum.h @@ -0,0 +1,190 @@ +//===- Fixnum.h - An integer type with an explicit bit width ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +/// \file Declares Fixnum, an integer type with an explicit bit width, +/// and utilities for working with bit widths of integers. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_ADT_FIXNUM_H +#define LLVM_ADT_FIXNUM_H + +#include "llvm/Support/MathExtras.h" +#include "llvm/Support/PointerLikeTypeTraits.h" +#include +#include + +namespace llvm { + +/// Defines a member #type that is the smallest signed integer that can hold +/// a value with the given bit width. +template +struct int_least { + static_assert(Bits <= 64, "too many bits"); + using type = typename std::conditional<(Bits <= 8), int_least8_t, + typename std::conditional<(Bits <= 16), int_least16_t, + typename std::conditional<(Bits <= 32), int_least32_t, + int_least64_t>::type>::type>::type; +}; + +/// Defines a member #type that is the smallest unsigned integer that can hold +/// a value with the given bit width. +template +struct uint_least { + using type = + typename std::make_unsigned::type>::type; +}; + +/// A wrapper for an integer type that is guaranteed to only use a certain +/// number of bits. +/// +/// This can be used to treat an integer like a pointer with low bits free. +/// +/// Note that if the integer type is signed, \p Bits must include the sign +/// bit, just like a bitfield. +template ::type> +class Fixnum { + static_assert(Bits <= (std::numeric_limits::digits + + std::numeric_limits::is_signed), + "too many bits for integer type"); + + IntType Value; + + void assertValid() const { + assert((std::is_signed::value ? llvm::isInt(Value) + : llvm::isUInt(Value)) && + "value exceeds limited bit width"); + } + +public: + using value_type = IntType; + + Fixnum() : Value(0) {} + + /*implicit*/ Fixnum(IntType val) : Value(val) { + assertValid(); + } + + /// Initialize a Fixnum from another, smaller Fixnum. + /// + /// This is always safe and thus permitted as an implicit coercion. + template + /*implicit*/ Fixnum( + const typename std::enable_if<(OtherBits < Bits), + Fixnum>::type &other) + : Value(static_cast(other)) {} + + /// Initialize a Fixnum from another of a different width. + /// + /// This is permitted, but checked with assertions. It must be explicitly + /// requested -- it is not a valid implicit conversion. + template + explicit Fixnum(const Fixnum &other) { + operator=(other); + } + + /// Assign to a Fixnum from another of a different width. + /// + /// This is permitted, but checked with assertions. + template + Fixnum &operator=(const Fixnum &other) { + Value = static_cast(other); + assert(static_cast(Value) == other && + "cannot represent the same value"); + assert(((Value < 0) == (other < 0)) && "signedness mismatch"); + assertValid(); + return *this; + } + + /*implicit*/ operator IntType() const { + return Value; + } + + Fixnum &operator++() { + assert((Value != std::numeric_limits::max()) && + "increment would cause wraparound"); + ++Value; + assertValid(); + return *this; + } + + Fixnum operator++(int) { + assert((Value != std::numeric_limits::max()) && + "increment would cause wraparound"); + Fixnum result = *this; + ++Value; + assertValid(); + return result; + } + + Fixnum &operator--() { + assert((Value != std::numeric_limits::min()) && + "decrement would cause wraparound"); + --Value; + assertValid(); + return *this; + } + + Fixnum operator--(int) { + assert((Value != std::numeric_limits::min()) && + "decrement would cause wraparound"); + Fixnum result = *this; + --Value; + assertValid(); + return result; + } + + bool operator==(const Fixnum &RHS) const { + return Value == RHS.Value; + } + bool operator!=(const Fixnum &RHS) const { + return !operator==(RHS); + } + + bool operator==(int RHS) const { + return Value == IntType(RHS); + } + bool operator!=(int RHS) const { + return !operator==(RHS); + } +}; + +// Fixnum can be treated like a pointer with low bits free if it is no +// larger than a pointer. +template +class PointerLikeTypeTraits> { + using IntPointerType = + typename std::conditional::value, + intptr_t, uintptr_t>::type; + +public: + static_assert(sizeof(IntType) <= sizeof(IntPointerType), + "Fixnum is too big to fit in a pointer"); + + static inline void * + getAsVoidPointer(const Fixnum &I) { + auto opaqueValue = static_cast(I) << NumLowBitsAvailable; + return reinterpret_cast(opaqueValue); + } + + static inline Fixnum + getFromVoidPointer(const void *P) { + auto opaqueValue = reinterpret_cast(P); + return static_cast(opaqueValue >> NumLowBitsAvailable); + } + + enum { + NumLowBitsAvailable = std::numeric_limits::digits - IntBits + }; +}; + +} // end namespace llvm + +#endif diff --git a/llvm/include/llvm/Analysis/LoopInfo.h b/llvm/include/llvm/Analysis/LoopInfo.h index ac0a4b02f4458..19ebabf6d4003 100644 --- a/llvm/include/llvm/Analysis/LoopInfo.h +++ b/llvm/include/llvm/Analysis/LoopInfo.h @@ -542,6 +542,8 @@ class LoopInfoBase { /// LoopT *getLoopFor(const BlockT *BB) const { return BBMap.lookup(BB); } + const DenseMap &getBlockMap() const { return BBMap; } + /// operator[] - same as getLoopFor... /// const LoopT *operator[](const BlockT *BB) const { diff --git a/llvm/include/llvm/Bitcode/RecordLayout.h b/llvm/include/llvm/Bitcode/RecordLayout.h new file mode 100644 index 0000000000000..c3cbf13b2239f --- /dev/null +++ b/llvm/include/llvm/Bitcode/RecordLayout.h @@ -0,0 +1,537 @@ +//===--- BCRecordLayout.h - Convenience wrappers for bitcode ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file Convenience wrappers for the LLVM bitcode format and bitstream APIs. +/// +/// This allows you to use a sort of DSL to declare and use bitcode abbrevs +/// and records. Example: +/// +/// \code +/// using Metadata = BCRecordLayout< +/// METADATA_ID, // ID +/// BCFixed<16>, // Module format major version +/// BCFixed<16>, // Module format minor version +/// BCBlob // misc. version information +/// >; +/// unsigned MetadataAbbrevCode = Metadata::emitAbbrev(Out); +/// Metadata::emitRecord(Out, ScratchRecord, MetadataAbbrevCode, +/// VERSION_MAJOR, VERSION_MINOR, extraData); +/// \endcode +/// +/// For details on the bitcode format, see +/// http://llvm.org/docs/BitCodeFormat.html +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_BITCODE_RECORDLAYOUT_H +#define LLVM_BITCODE_RECORDLAYOUT_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Fixnum.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Bitcode/BitCodes.h" +#include "llvm/Bitcode/BitstreamWriter.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/MathExtras.h" + +namespace llvm { + +namespace impl { + /// Convenience base for all kinds of bitcode abbreviation fields. + /// + /// This just defines common properties queried by the metaprogramming. + template + class BCField { + public: + static const bool IS_COMPOUND = COMPOUND; + + /// Asserts that the given data is a valid value for this field. + template + static void assertValid(const T &data) {} + + /// Converts a raw numeric representation of this value to its preferred + /// type. + template + static T convert(T rawValue) { + return rawValue; + } + }; +} // end namespace impl + + +/// Represents a literal operand in a bitcode record. +/// +/// The value of a literal operand is the same for all instances of the record, +/// so it is only emitted in the abbreviation definition. +/// +/// Note that because this uses a compile-time template, you cannot have a +/// literal operand that is fixed at run-time without dropping down to the +/// raw LLVM APIs. +template +class BCLiteral : public impl::BCField<> { +public: + static void emitOp(llvm::BitCodeAbbrev &abbrev) { + abbrev.Add(llvm::BitCodeAbbrevOp(Value)); + } + + template + static void assertValid(const T &data) { + assert(data == Value && "data value does not match declared literal value"); + } +}; + +/// Represents a fixed-width value in a bitcode record. +/// +/// Note that the LLVM bitcode format only supports unsigned values. +template +class BCFixed : public impl::BCField<> { +public: + static_assert(Width <= 64, "fixed-width field is too large"); + + static void emitOp(llvm::BitCodeAbbrev &abbrev) { + abbrev.Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, Width)); + } + + template + static void assertValid(const T &data) { + assert(data >= 0 && "cannot encode signed integers"); + assert(llvm::isUInt(data) && + "data value does not fit in the given bit width"); + } + + using value_type = Fixnum; + + template + static value_type convert(T rawValue) { + return static_cast(rawValue); + } +}; + +/// Represents a variable-width value in a bitcode record. +/// +/// The \p Width parameter should include the continuation bit. +/// +/// Note that the LLVM bitcode format only supports unsigned values. +template +class BCVBR : public impl::BCField<> { + static_assert(Width >= 2, "width does not have room for continuation bit"); + +public: + static void emitOp(llvm::BitCodeAbbrev &abbrev) { + abbrev.Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::VBR, Width)); + } + + template + static void assertValid(const T &data) { + assert(data >= 0 && "cannot encode signed integers"); + } +}; + +/// Represents a character encoded in LLVM's Char6 encoding. +/// +/// This format is suitable for encoding decimal numbers (without signs or +/// exponents) and C identifiers (without dollar signs), but not much else. +/// +/// \sa http://llvm.org/docs/BitCodeFormat.html#char6-encoded-value +class BCChar6 : public impl::BCField<> { +public: + static void emitOp(llvm::BitCodeAbbrev &abbrev) { + abbrev.Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Char6)); + } + + template + static void assertValid(const T &data) { + assert(llvm::BitCodeAbbrevOp::isChar6(data) && "invalid Char6 data"); + } + + template + char convert(T rawValue) { + return static_cast(rawValue); + } +}; + +/// Represents an untyped blob of bytes. +/// +/// If present, this must be the last field in a record. +class BCBlob : public impl::BCField { +public: + static void emitOp(llvm::BitCodeAbbrev &abbrev) { + abbrev.Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Blob)); + } +}; + +/// Represents an array of some other type. +/// +/// If present, this must be the last field in a record. +template +class BCArray : public impl::BCField { + static_assert(!Element::IS_COMPOUND, "arrays can only contain scalar types"); +public: + static void emitOp(llvm::BitCodeAbbrev &abbrev) { + abbrev.Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Array)); + Element::emitOp(abbrev); + } +}; + + +namespace impl { + /// Attaches the last field to an abbreviation. + /// + /// This is the base case for \c emitOps. + /// + /// \sa BCRecordLayout::emitAbbrev + template + static void emitOps(llvm::BitCodeAbbrev &abbrev) { + Last::emitOp(abbrev); + } + + /// Attaches fields to an abbreviation. + /// + /// This is the recursive case for \c emitOps. + /// + /// \sa BCRecordLayout::emitAbbrev + template + static void emitOps(llvm::BitCodeAbbrev &abbrev) { + static_assert(!First::IS_COMPOUND, + "arrays and blobs may not appear in the middle of a record"); + First::emitOp(abbrev); + emitOps(abbrev); + } + + + /// Helper class for dealing with a scalar element in the middle of a record. + /// + /// \sa BCRecordLayout + template + class BCRecordCoding { + public: + template + static void emit(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, FirstData data, Data... rest) { + static_assert(!First::IS_COMPOUND, + "arrays and blobs may not appear in the middle of a record"); + First::assertValid(data); + buffer.push_back(data); + BCRecordCoding::emit(out, buffer, abbrCode, rest...); + } + + template + static void read(ArrayRef buffer, + FirstData &data, Data &&...rest) { + assert(!buffer.empty() && "too few elements in buffer"); + data = First::convert(buffer.front()); + BCRecordCoding::read(buffer.slice(1), + std::forward(rest)...); + } + + template + static void read(ArrayRef buffer, + NoneType, Data &&...rest) { + assert(!buffer.empty() && "too few elements in buffer"); + BCRecordCoding::read(buffer.slice(1), + std::forward(rest)...); + } + }; + + /// Helper class for dealing with a scalar element at the end of a record. + /// + /// This has a separate implementation because up until now we've only been + /// \em building the record (into a data buffer), and now we need to hand it + /// off to the BitstreamWriter to be emitted. + /// + /// \sa BCRecordLayout + template + class BCRecordCoding { + public: + template + static void emit(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, LastData data) { + static_assert(!Last::IS_COMPOUND, + "arrays and blobs need special handling"); + Last::assertValid(data); + buffer.push_back(data); + out.EmitRecordWithAbbrev(abbrCode, buffer); + } + + template + static void read(ArrayRef buffer, LastData &data) { + assert(buffer.size() == 1 && "record data does not match layout"); + data = Last::convert(buffer.front()); + } + + template + static void read(ArrayRef buffer, NoneType) { + assert(buffer.size() == 1 && "record data does not match layout"); + (void)buffer; + } + + template + static void read(ArrayRef buffer) = delete; + }; + + /// Helper class for dealing with an array at the end of a record. + /// + /// \sa BCRecordLayout::emitRecord + template + class BCRecordCoding> { + public: + template + static void emit(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, StringRef arrayData) { + // FIXME: validate array data. + out.EmitRecordWithArray(abbrCode, buffer, arrayData); + } + + template + static void emit(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, const ArrayTy &arrayData) { +#ifndef NDEBUG + for (auto &item : arrayData) + EleTy::assertValid(item); +#endif + buffer.reserve(buffer.size() + arrayData.size()); + std::copy(arrayData.begin(), arrayData.end(), + std::back_inserter(buffer)); + out.EmitRecordWithAbbrev(abbrCode, buffer); + } + + template + static void emit(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, FirstData firstData, + RestData... restData) { + std::array arrayData{ { + firstData, + restData... + } }; + emit(out, buffer, abbrCode, arrayData); + } + + template + static void emit(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, NoneType) { + out.EmitRecordWithAbbrev(abbrCode, buffer); + } + + template + static void read(ArrayRef buffer, ArrayRef &rawData) { + rawData = buffer; + } + + template + static void read(ArrayRef buffer, ArrayTy &array) { + array.append(llvm::map_iterator(buffer.begin(), ElementTy::convert), + llvm::map_iterator(buffer.end(), ElementTy::convert)); + } + + template + static void read(ArrayRef buffer, NoneType) { + (void)buffer; + } + + template + static void read(ArrayRef buffer) = delete; + }; + + /// Helper class for dealing with a blob at the end of a record. + /// + /// \sa BCRecordLayout + template<> + class BCRecordCoding { + public: + template + static void emit(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, StringRef blobData) { + out.EmitRecordWithBlob(abbrCode, buffer, blobData); + } + + template + static void read(ArrayRef buffer) { + (void)buffer; + } + + /// Blob data is not stored in the buffer if you are using the correct + /// accessor; this method should not be used. + template + static void read(ArrayRef buffer, DataTy &data) = delete; + }; + + /// A type trait whose \c type field is the last of its template parameters. + template + struct last_type { + using type = typename last_type::type; + }; + + template + struct last_type { + using type = Last; + }; + + /// A type trait whose \c value field is \c true if the last type is BCBlob. + template + using has_blob = std::is_same::type>; + + /// A type trait whose \c value field is \c true if the given type is a + /// BCArray (of any element kind). + template + struct is_array { + private: + template + static bool check(BCArray *); + static int check(...); + + public: + typedef bool value_type; + static constexpr bool value = + !std::is_same::value; + }; + + /// A type trait whose \c value field is \c true if the last type is a + /// BCArray (of any element kind). + template + using has_array = is_array::type>; +} // end namespace impl + +/// Represents a single bitcode record type. +/// +/// This class template is meant to be instantiated and then given a name, +/// so that from then on that name can be used +template +class BCGenericRecordLayout { + llvm::BitstreamWriter &Out; + +public: + /// The abbreviation code used for this record in the current block. + /// + /// Note that this is not the same as the semantic record code, which is the + /// first field of the record. + const unsigned AbbrevCode; + + /// Create a layout and register it with the given bitstream writer. + explicit BCGenericRecordLayout(llvm::BitstreamWriter &out) + : Out(out), AbbrevCode(emitAbbrev(out)) {} + + /// Emit a record to the bitstream writer, using the given buffer for scratch + /// space. + /// + /// Note that even fixed arguments must be specified here. + template + void emit(BufferTy &buffer, unsigned recordID, Data... data) const { + emitRecord(Out, buffer, AbbrevCode, recordID, data...); + } + + /// Registers this record's layout with the bitstream reader. + /// + /// \returns The abbreviation code for the newly-registered record type. + static unsigned emitAbbrev(llvm::BitstreamWriter &out) { + auto *abbrev = new llvm::BitCodeAbbrev(); + impl::emitOps(*abbrev); + return out.EmitAbbrev(abbrev); + } + + /// Emit a record identified by \p abbrCode to bitstream reader \p out, using + /// \p buffer for scratch space. + /// + /// Note that even fixed arguments must be specified here. Blobs are passed + /// as StringRefs, while arrays can be passed inline, as aggregates, or as + /// pre-encoded StringRef data. Skipped values and empty arrays should use + /// the special Nothing value. + template + static void emitRecord(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, unsigned recordID, Data... data) { + static_assert(sizeof...(data) <= sizeof...(Fields) || + impl::has_array::value, + "Too many record elements"); + static_assert(sizeof...(data) >= sizeof...(Fields), + "Too few record elements"); + buffer.clear(); + impl::BCRecordCoding::emit(out, buffer, abbrCode, + recordID, data...); + } + + /// Extract record data from \p buffer into the given data fields. + /// + /// Note that even fixed arguments must be specified here. Pass \c Nothing + /// if you don't care about a particular parameter. Blob data is not included + /// in the buffer and should be handled separately by the caller. + template + static void readRecord(ArrayRef buffer, Data &&... data) { + static_assert(sizeof...(data) <= sizeof...(Fields), + "Too many record elements"); + static_assert(sizeof...(Fields) <= + sizeof...(data) + impl::has_blob::value, + "Too few record elements"); + return impl::BCRecordCoding::read(buffer, + std::forward(data)...); + } + + /// Extract record data from \p buffer into the given data fields. + /// + /// Note that even fixed arguments must be specified here. Pass \c Nothing + /// if you don't care about a particular parameter. Blob data is not included + /// in the buffer and should be handled separately by the caller. + template + static void readRecord(BufferTy &buffer, Data &&... data) { + return readRecord(llvm::makeArrayRef(buffer), std::forward(data)...); + } +}; + +/// A record with a fixed record code. +template +class BCRecordLayout : public BCGenericRecordLayout, + Fields...> { + using Base = BCGenericRecordLayout, Fields...>; +public: + enum : unsigned { + /// The record code associated with this layout. + Code = RecordCode + }; + + /// Create a layout and register it with the given bitstream writer. + explicit BCRecordLayout(llvm::BitstreamWriter &out) : Base(out) {} + + /// Emit a record to the bitstream writer, using the given buffer for scratch + /// space. + /// + /// Note that even fixed arguments must be specified here. + template + void emit(BufferTy &buffer, Data... data) const { + Base::emit(buffer, RecordCode, data...); + } + + /// Emit a record identified by \p abbrCode to bitstream reader \p out, using + /// \p buffer for scratch space. + /// + /// Note that even fixed arguments must be specified here. Currently, arrays + /// and blobs can only be passed as StringRefs. + template + static void emitRecord(llvm::BitstreamWriter &out, BufferTy &buffer, + unsigned abbrCode, Data... data) { + Base::emitRecord(out, buffer, abbrCode, RecordCode, data...); + } +}; + +/// RAII object to pair entering and exiting a sub-block. +class BCBlockRAII { + llvm::BitstreamWriter &Writer; +public: + BCBlockRAII(llvm::BitstreamWriter &writer, unsigned blockID, + unsigned abbrevLen) + : Writer(writer) { + writer.EnterSubblock(blockID, abbrevLen); + } + + ~BCBlockRAII() { + Writer.ExitBlock(); + } +}; + +} // end namespace llvm + +#endif diff --git a/llvm/include/llvm/CodeGen/FastISel.h b/llvm/include/llvm/CodeGen/FastISel.h index b2bb8566aadf1..4f594a9c00df2 100644 --- a/llvm/include/llvm/CodeGen/FastISel.h +++ b/llvm/include/llvm/CodeGen/FastISel.h @@ -40,12 +40,15 @@ class FastISel { bool IsByVal : 1; bool IsInAlloca : 1; bool IsReturned : 1; + bool IsSwiftSelf : 1; + bool IsSwiftError : 1; uint16_t Alignment; ArgListEntry() : Val(nullptr), Ty(nullptr), IsSExt(false), IsZExt(false), IsInReg(false), IsSRet(false), IsNest(false), IsByVal(false), - IsInAlloca(false), IsReturned(false), Alignment(0) {} + IsInAlloca(false), IsReturned(false), IsSwiftSelf(false), + IsSwiftError(false), Alignment(0) {} /// \brief Set CallLoweringInfo attribute flags based on a call instruction /// and called function attributes. diff --git a/llvm/include/llvm/CodeGen/FunctionLoweringInfo.h b/llvm/include/llvm/CodeGen/FunctionLoweringInfo.h index bd8da736c16b9..7125b69179290 100644 --- a/llvm/include/llvm/CodeGen/FunctionLoweringInfo.h +++ b/llvm/include/llvm/CodeGen/FunctionLoweringInfo.h @@ -69,6 +69,35 @@ class FunctionLoweringInfo { /// MBBMap - A mapping from LLVM basic blocks to their machine code entry. DenseMap MBBMap; + typedef SmallVector SwiftErrorVRegs; + typedef SmallVector SwiftErrorValues; + /// A function can only have a single swifterror argument. And if it does + /// have a swifterror argument, it must be the first entry in + /// SwiftErrorVals. + SwiftErrorValues SwiftErrorVals; + + /// Track the virtual register for each swifterror value in a given basic + /// block. Entries in SwiftErrorVRegs have the same ordering as entries + /// in SwiftErrorValues. + /// Note that another choice that is more straight-forward is to use + /// Map>. It + /// maintains a map from swifterror values to virtual registers for each + /// machine basic block. This choice does not require a one-to-one + /// corresponse between SwiftErrorValues and SwiftErrorVRegs. But because of + /// efficiency concern, we do not choose it. + llvm::DenseMap SwiftErrorMap; + + /// Track the virtual register for a swifterror value at the end of a basic + /// block when the basic block is not yet visited. + llvm::DenseMap + SwiftErrorWorklist; + + /// Find the swifterror virtual register in SwiftErrorMap. We will assert + /// failure when the value does not exist in swifterror map. + unsigned findSwiftErrorVReg(const MachineBasicBlock*, const Value*) const; + /// Set the swifterror virtual register in SwiftErrorMap. + void setSwiftErrorVReg(const MachineBasicBlock *MBB, const Value*, unsigned); + /// ValueMap - Since we emit code for the function a basic block at a time, /// we must remember which virtual registers hold the values for /// cross-basic-block values. diff --git a/llvm/include/llvm/IR/Argument.h b/llvm/include/llvm/IR/Argument.h index 0092f49e49ad6..22973753e123f 100644 --- a/llvm/include/llvm/IR/Argument.h +++ b/llvm/include/llvm/IR/Argument.h @@ -73,6 +73,12 @@ class Argument : public Value, public ilist_node { /// containing function. bool hasByValAttr() const; + /// \brief Return true if this argument has the swiftself attribute. + bool hasSwiftSelfAttr() const; + + /// \brief Return true if this argument has the swifterror attribute. + bool hasSwiftErrorAttr() const; + /// \brief Return true if this argument has the byval attribute or inalloca /// attribute on it in its containing function. These attributes both /// represent arguments being passed by value. diff --git a/llvm/include/llvm/IR/Attributes.h b/llvm/include/llvm/IR/Attributes.h index 15f48fa38d93c..2eac273bd43d9 100644 --- a/llvm/include/llvm/IR/Attributes.h +++ b/llvm/include/llvm/IR/Attributes.h @@ -115,6 +115,8 @@ class Attribute { SanitizeAddress, ///< AddressSanitizer is on. SanitizeThread, ///< ThreadSanitizer is on. SanitizeMemory, ///< MemorySanitizer is on. + SwiftError, ///< Argument is swift error. + SwiftSelf, ///< Argument is swift self/context. UWTable, ///< Function must be in a unwind table ZExt, ///< Zero extended before/after call diff --git a/llvm/include/llvm/IR/Instructions.h b/llvm/include/llvm/IR/Instructions.h index c2ade1994f5dc..efb0038e24e75 100644 --- a/llvm/include/llvm/IR/Instructions.h +++ b/llvm/include/llvm/IR/Instructions.h @@ -151,6 +151,18 @@ class AllocaInst : public UnaryInstruction { (V ? 32 : 0)); } + /// \brief Return true if this alloca is used as a swifterror argument to a + /// call. + bool isSwiftError() const { + return getSubclassDataFromInstruction() & 64; + } + + /// \brief Specify whether this alloca is used to represent a swifterror. + void setSwiftError(bool V) { + setInstructionSubclassData((getSubclassDataFromInstruction() & ~64) | + (V ? 64 : 0)); + } + // Methods for support type inquiry through isa, cast, and dyn_cast: static inline bool classof(const Instruction *I) { return (I->getOpcode() == Instruction::Alloca); diff --git a/llvm/include/llvm/MC/MCAsmInfo.h b/llvm/include/llvm/MC/MCAsmInfo.h index 384584ef4ef0e..3106c8c501f31 100644 --- a/llvm/include/llvm/MC/MCAsmInfo.h +++ b/llvm/include/llvm/MC/MCAsmInfo.h @@ -280,6 +280,10 @@ class MCAsmInfo { /// to false. bool HasNoDeadStrip; + /// True if this target supports the MachO .alt_entry directive. Defaults to + /// false. + bool HasAltEntry; + /// Used to declare a global as being a weak symbol. Defaults to ".weak". const char *WeakDirective; @@ -498,6 +502,7 @@ class MCAsmInfo { bool hasSingleParameterDotFile() const { return HasSingleParameterDotFile; } bool hasIdentDirective() const { return HasIdentDirective; } bool hasNoDeadStrip() const { return HasNoDeadStrip; } + bool hasAltEntry() const { return HasAltEntry; } const char *getWeakDirective() const { return WeakDirective; } const char *getWeakRefDirective() const { return WeakRefDirective; } bool hasWeakDefDirective() const { return HasWeakDefDirective; } diff --git a/llvm/include/llvm/MC/MCDirectives.h b/llvm/include/llvm/MC/MCDirectives.h index 326b2a1ac061b..8c74b169135b1 100644 --- a/llvm/include/llvm/MC/MCDirectives.h +++ b/llvm/include/llvm/MC/MCDirectives.h @@ -35,6 +35,7 @@ enum MCSymbolAttr { MCSA_Local, ///< .local (ELF) MCSA_NoDeadStrip, ///< .no_dead_strip (MachO) MCSA_SymbolResolver, ///< .symbol_resolver (MachO) + MCSA_AltEntry, ///< .alt_entry (MachO) MCSA_PrivateExtern, ///< .private_extern (MachO) MCSA_Protected, ///< .protected (ELF) MCSA_Reference, ///< .reference (MachO) diff --git a/llvm/include/llvm/MC/MCSymbolMachO.h b/llvm/include/llvm/MC/MCSymbolMachO.h index 5b0321fe9f732..8b63c43466a35 100644 --- a/llvm/include/llvm/MC/MCSymbolMachO.h +++ b/llvm/include/llvm/MC/MCSymbolMachO.h @@ -33,6 +33,7 @@ class MCSymbolMachO : public MCSymbol { SF_WeakReference = 0x0040, SF_WeakDefinition = 0x0080, SF_SymbolResolver = 0x0100, + SF_AltEntry = 0x0200, // Common alignment SF_CommonAlignmentMask = 0xF0FF, @@ -88,6 +89,10 @@ class MCSymbolMachO : public MCSymbol { modifyFlags(SF_SymbolResolver, SF_SymbolResolver); } + void setAltEntry() const { + modifyFlags(SF_AltEntry, SF_AltEntry); + } + void setDesc(unsigned Value) const { assert(Value == (Value & SF_DescFlagsMask) && "Invalid .desc value!"); diff --git a/llvm/include/llvm/Target/TargetCallingConv.h b/llvm/include/llvm/Target/TargetCallingConv.h index 9d4e7a04d905a..35088627c4c9b 100644 --- a/llvm/include/llvm/Target/TargetCallingConv.h +++ b/llvm/include/llvm/Target/TargetCallingConv.h @@ -46,6 +46,10 @@ namespace ISD { static const uint64_t SplitOffs = 11; static const uint64_t InAlloca = 1ULL<<12; ///< Passed with inalloca static const uint64_t InAllocaOffs = 12; + static const uint64_t SwiftSelf = 1ULL<<13; ///< Swift self parameter + static const uint64_t SwiftSelfOffs = 13; + static const uint64_t SwiftError = 1ULL<<14; ///< Swift error parameter + static const uint64_t SwiftErrorOffs = 14; static const uint64_t OrigAlign = 0x1FULL<<27; static const uint64_t OrigAlignOffs = 27; static const uint64_t ByValSize = 0x3fffffffULL<<32; ///< Struct size @@ -79,6 +83,12 @@ namespace ISD { bool isInAlloca() const { return Flags & InAlloca; } void setInAlloca() { Flags |= One << InAllocaOffs; } + bool isSwiftSelf() const { return Flags & SwiftSelf; } + void setSwiftSelf() { Flags = One << SwiftSelfOffs; } + + bool isSwiftError() const { return Flags & SwiftError; } + void setSwiftError() { Flags = One << SwiftErrorOffs; } + bool isNest() const { return Flags & Nest; } void setNest() { Flags |= One << NestOffs; } diff --git a/llvm/include/llvm/Target/TargetCallingConv.td b/llvm/include/llvm/Target/TargetCallingConv.td index 2e766c448b34a..3d8639dfe1daa 100644 --- a/llvm/include/llvm/Target/TargetCallingConv.td +++ b/llvm/include/llvm/Target/TargetCallingConv.td @@ -42,6 +42,16 @@ class CCIf : CCPredicateAction { class CCIfByVal : CCIf<"ArgFlags.isByVal()", A> { } +/// CCIfSwiftSelf - If the current argument has swiftself parameter attribute, +/// apply Action A. +class CCIfSwiftSelf : CCIf<"ArgFlags.isSwiftSelf()", A> { +} + +/// CCIfSwiftError - If the current argument has swifterror parameter attribute, +/// apply Action A. +class CCIfSwiftError : CCIf<"ArgFlags.isSwiftError()", A> { +} + /// CCIfConsecutiveRegs - If the current argument has InConsecutiveRegs /// parameter attribute, apply Action A. class CCIfConsecutiveRegs : CCIf<"ArgFlags.isInConsecutiveRegs()", A> { diff --git a/llvm/include/llvm/Target/TargetFrameLowering.h b/llvm/include/llvm/Target/TargetFrameLowering.h index 398c91ef56249..61c10c7436a38 100644 --- a/llvm/include/llvm/Target/TargetFrameLowering.h +++ b/llvm/include/llvm/Target/TargetFrameLowering.h @@ -120,7 +120,10 @@ class TargetFrameLowering { virtual bool assignCalleeSavedSpillSlots(MachineFunction &MF, const TargetRegisterInfo *TRI, - std::vector &CSI) const { + std::vector &CSI, + unsigned &MinCSFrameIndex, + unsigned &MaxCSFrameIndex) const { + return false; } diff --git a/llvm/include/llvm/Target/TargetLowering.h b/llvm/include/llvm/Target/TargetLowering.h index e17682f704e4d..bd6fa4b0678ba 100644 --- a/llvm/include/llvm/Target/TargetLowering.h +++ b/llvm/include/llvm/Target/TargetLowering.h @@ -2266,6 +2266,11 @@ class TargetLowering : public TargetLoweringBase { return false; } + /// Return true if the target supports swifterror attribute. + virtual bool supportSwiftError() const { + return false; + } + //===--------------------------------------------------------------------===// // Lowering methods - These methods must be implemented by targets so that // the SelectionDAGBuilder code knows how to lower these. @@ -2296,11 +2301,14 @@ class TargetLowering : public TargetLoweringBase { bool isByVal : 1; bool isInAlloca : 1; bool isReturned : 1; + bool isSwiftSelf : 1; + bool isSwiftError : 1; uint16_t Alignment; ArgListEntry() : isSExt(false), isZExt(false), isInReg(false), isSRet(false), isNest(false), isByVal(false), isInAlloca(false), - isReturned(false), Alignment(0) { } + isReturned(false), isSwiftSelf(false), isSwiftError(false), + Alignment(0) { } void setAttributes(ImmutableCallSite *CS, unsigned AttrIdx); }; @@ -2335,6 +2343,7 @@ class TargetLowering : public TargetLoweringBase { SmallVector Outs; SmallVector OutVals; SmallVector Ins; + SmallVector InVals; CallLoweringInfo(SelectionDAG &DAG) : RetTy(nullptr), RetSExt(false), RetZExt(false), IsVarArg(false), diff --git a/llvm/include/llvm/Transforms/Utils/SSAUpdaterImpl.h b/llvm/include/llvm/Transforms/Utils/SSAUpdaterImpl.h index 425ecd3cfb5e0..acd371e0d9dc3 100644 --- a/llvm/include/llvm/Transforms/Utils/SSAUpdaterImpl.h +++ b/llvm/include/llvm/Transforms/Utils/SSAUpdaterImpl.h @@ -38,6 +38,7 @@ class SSAUpdaterImpl { typedef typename Traits::BlkT BlkT; typedef typename Traits::ValT ValT; typedef typename Traits::PhiT PhiT; + typedef typename Traits::PhiItT PhiItT; /// BBInfo - Per-basic block information used internally by SSAUpdaterImpl. /// The predecessors of each block are cached here since pred_iterator is @@ -376,7 +377,7 @@ class SSAUpdaterImpl { /// FindExistingPHI - Look through the PHI nodes in a block to see if any of /// them match what is needed. void FindExistingPHI(BlkT *BB, BlockListTy *BlockList) { - for (typename BlkT::iterator BBI = BB->begin(), BBE = BB->end(); + for (PhiItT BBI = Traits::PhiItT_begin(BB), BBE = Traits::PhiItT_end(BB); BBI != BBE; ++BBI) { PhiT *SomePHI = Traits::InstrIsPHI(&*BBI); if (!SomePHI) diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp index 2eb5f0bf45d5b..8e302b2538a93 100644 --- a/llvm/lib/AsmParser/LLLexer.cpp +++ b/llvm/lib/AsmParser/LLLexer.cpp @@ -583,6 +583,7 @@ lltok::Kind LLLexer::LexIdentifier() { KEYWORD(x86_64_sysvcc); KEYWORD(x86_64_win64cc); KEYWORD(webkit_jscc); + KEYWORD(swiftcc); KEYWORD(anyregcc); KEYWORD(preserve_mostcc); KEYWORD(preserve_allcc); @@ -636,6 +637,8 @@ lltok::Kind LLLexer::LexIdentifier() { KEYWORD(sanitize_address); KEYWORD(sanitize_thread); KEYWORD(sanitize_memory); + KEYWORD(swifterror); + KEYWORD(swiftself); KEYWORD(uwtable); KEYWORD(zeroext); diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp index 7774a70f5f44c..190612a64d5e4 100644 --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -1036,6 +1036,8 @@ bool LLParser::ParseFnAttributeValuePairs(AttrBuilder &B, case lltok::kw_nonnull: case lltok::kw_returned: case lltok::kw_sret: + case lltok::kw_swifterror: + case lltok::kw_swiftself: HaveError |= Error(Lex.getLoc(), "invalid use of parameter-only attribute on a function"); @@ -1309,6 +1311,8 @@ bool LLParser::ParseOptionalParamAttrs(AttrBuilder &B) { case lltok::kw_returned: B.addAttribute(Attribute::Returned); break; case lltok::kw_signext: B.addAttribute(Attribute::SExt); break; case lltok::kw_sret: B.addAttribute(Attribute::StructRet); break; + case lltok::kw_swifterror: B.addAttribute(Attribute::SwiftError); break; + case lltok::kw_swiftself: B.addAttribute(Attribute::SwiftSelf); break; case lltok::kw_zeroext: B.addAttribute(Attribute::ZExt); break; case lltok::kw_alignstack: @@ -1396,6 +1400,8 @@ bool LLParser::ParseOptionalReturnAttrs(AttrBuilder &B) { case lltok::kw_nocapture: case lltok::kw_returned: case lltok::kw_sret: + case lltok::kw_swifterror: + case lltok::kw_swiftself: HaveError |= Error(Lex.getLoc(), "invalid use of parameter-only attribute"); break; @@ -1532,6 +1538,7 @@ bool LLParser::ParseOptionalDLLStorageClass(unsigned &Res) { /// ::= 'preserve_mostcc' /// ::= 'preserve_allcc' /// ::= 'ghccc' +/// ::= 'swiftcc' /// ::= 'hhvmcc' /// ::= 'hhvm_ccc' /// ::= 'cc' UINT @@ -1562,6 +1569,7 @@ bool LLParser::ParseOptionalCallingConv(unsigned &CC) { case lltok::kw_preserve_mostcc:CC = CallingConv::PreserveMost; break; case lltok::kw_preserve_allcc: CC = CallingConv::PreserveAll; break; case lltok::kw_ghccc: CC = CallingConv::GHC; break; + case lltok::kw_swiftcc: CC = CallingConv::Swift; break; case lltok::kw_hhvmcc: CC = CallingConv::HHVM; break; case lltok::kw_hhvm_ccc: CC = CallingConv::HHVM_C; break; case lltok::kw_cc: { @@ -5744,7 +5752,8 @@ bool LLParser::ParseCall(Instruction *&Inst, PerFunctionState &PFS, //===----------------------------------------------------------------------===// /// ParseAlloc -/// ::= 'alloca' 'inalloca'? Type (',' TypeAndValue)? (',' 'align' i32)? +/// ::= 'alloca' 'inalloca'? 'swifterror'? Type (',' TypeAndValue)? +/// (',' 'align' i32)? int LLParser::ParseAlloc(Instruction *&Inst, PerFunctionState &PFS) { Value *Size = nullptr; LocTy SizeLoc, TyLoc; @@ -5752,6 +5761,7 @@ int LLParser::ParseAlloc(Instruction *&Inst, PerFunctionState &PFS) { Type *Ty = nullptr; bool IsInAlloca = EatIfPresent(lltok::kw_inalloca); + bool IsSwiftError = EatIfPresent(lltok::kw_swifterror); if (ParseType(Ty, TyLoc)) return true; @@ -5776,6 +5786,7 @@ int LLParser::ParseAlloc(Instruction *&Inst, PerFunctionState &PFS) { AllocaInst *AI = new AllocaInst(Ty, Size, Alignment); AI->setUsedWithInAlloca(IsInAlloca); + AI->setSwiftError(IsSwiftError); Inst = AI; return AteExtraComma ? InstExtraComma : InstNormal; } diff --git a/llvm/lib/AsmParser/LLToken.h b/llvm/lib/AsmParser/LLToken.h index 06b5f9b0800f9..b6f7f02fa7bb1 100644 --- a/llvm/lib/AsmParser/LLToken.h +++ b/llvm/lib/AsmParser/LLToken.h @@ -95,6 +95,7 @@ namespace lltok { kw_spir_kernel, kw_spir_func, kw_x86_64_sysvcc, kw_x86_64_win64cc, kw_webkit_jscc, kw_anyregcc, + kw_swiftcc, kw_preserve_mostcc, kw_preserve_allcc, kw_ghccc, kw_hhvmcc, kw_hhvm_ccc, @@ -142,6 +143,8 @@ namespace lltok { kw_sret, kw_sanitize_thread, kw_sanitize_memory, + kw_swifterror, + kw_swiftself, kw_uwtable, kw_zeroext, diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index 7c1b208c62610..142f73a86e4c8 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1351,6 +1351,10 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) { return Attribute::SanitizeThread; case bitc::ATTR_KIND_SANITIZE_MEMORY: return Attribute::SanitizeMemory; + case bitc::ATTR_KIND_SWIFT_ERROR: + return Attribute::SwiftError; + case bitc::ATTR_KIND_SWIFT_SELF: + return Attribute::SwiftSelf; case bitc::ATTR_KIND_UW_TABLE: return Attribute::UWTable; case bitc::ATTR_KIND_Z_EXT: @@ -2063,6 +2067,23 @@ std::error_code BitcodeReader::parseMetadata() { if (Tag >= 1u << 16 || Version != 0) return error("Invalid record"); + // Deprecated internal hack to support serializing MDModule. + // This node has since been deleted. + // Upgrading this node is not officially supported. This code + // may be removed in the future. + if (Tag == dwarf::DW_TAG_module) { + if (Record.size() != 6) + return error("Invalid record"); + + MDValueList.assignValue( + GET_OR_DISTINCT(DIModule, Record[0], + (Context, getMDOrNull(Record[4]), + getMDString(Record[5]), nullptr, + nullptr, nullptr)), + NextMDValueNo++); + break; + } + auto *Header = getMDString(Record[3]); SmallVector DwarfOps; for (unsigned I = 4, E = Record.size(); I != E; ++I) @@ -4715,10 +4736,11 @@ std::error_code BitcodeReader::parseFunctionBody(Function *F) { uint64_t AlignRecord = Record[3]; const uint64_t InAllocaMask = uint64_t(1) << 5; const uint64_t ExplicitTypeMask = uint64_t(1) << 6; - // Reserve bit 7 for SwiftError flag. - // const uint64_t SwiftErrorMask = uint64_t(1) << 7; - const uint64_t FlagMask = InAllocaMask | ExplicitTypeMask; + const uint64_t SwiftErrorMask = uint64_t(1) << 7; + const uint64_t FlagMask = InAllocaMask | ExplicitTypeMask | + SwiftErrorMask; bool InAlloca = AlignRecord & InAllocaMask; + bool SwiftError = AlignRecord & SwiftErrorMask; Type *Ty = getTypeByID(Record[0]); if ((AlignRecord & ExplicitTypeMask) == 0) { auto *PTy = dyn_cast_or_null(Ty); @@ -4737,6 +4759,7 @@ std::error_code BitcodeReader::parseFunctionBody(Function *F) { return error("Invalid record"); AllocaInst *AI = new AllocaInst(Ty, Size, Align); AI->setUsedWithInAlloca(InAlloca); + AI->setSwiftError(SwiftError); I = AI; InstructionList.push_back(I); break; diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp index c73b099e27f92..90fb55700cd32 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -248,6 +248,10 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) { return bitc::ATTR_KIND_SANITIZE_THREAD; case Attribute::SanitizeMemory: return bitc::ATTR_KIND_SANITIZE_MEMORY; + case Attribute::SwiftError: + return bitc::ATTR_KIND_SWIFT_ERROR; + case Attribute::SwiftSelf: + return bitc::ATTR_KIND_SWIFT_SELF; case Attribute::UWTable: return bitc::ATTR_KIND_UW_TABLE; case Attribute::ZExt: @@ -2051,8 +2055,7 @@ static void WriteInstruction(const Instruction &I, unsigned InstID, assert(AlignRecord < 1 << 5 && "alignment greater than 1 << 64"); AlignRecord |= AI.isUsedWithInAlloca() << 5; AlignRecord |= 1 << 6; - // Reserve bit 7 for SwiftError flag. - // AlignRecord |= AI.isSwiftError() << 7; + AlignRecord |= AI.isSwiftError() << 7; Vals.push_back(AlignRecord); break; } diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp index 8fe5094ba689e..1002fc3f6a1c4 100644 --- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -1187,8 +1187,13 @@ bool AsmPrinter::doFinalization(Module &M) { EmitVisibility(Name, Alias.getVisibility()); + const MCExpr *Expr = lowerConstant(Alias.getAliasee()); + + if (MAI->hasAltEntry() && isa(Expr)) + OutStreamer->EmitSymbolAttribute(Name, MCSA_AltEntry); + // Emit the directives as assignments aka .set: - OutStreamer->EmitAssignment(Name, lowerConstant(Alias.getAliasee())); + OutStreamer->EmitAssignment(Name, Expr); // If the aliasee does not correspond to a symbol in the output, i.e. the // alias is not of an object or the aliased object is private, then set the diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp index c441a67a5d70b..ef0ea4301d057 100644 --- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp @@ -691,6 +691,8 @@ DIE *DwarfUnit::getOrCreateContextDIE(const DIScope *Context) { return getOrCreateTypeDIE(T); if (auto *NS = dyn_cast(Context)) return getOrCreateNameSpace(NS); + if (auto *M = dyn_cast(Context)) + return getOrCreateModule(M); if (auto *SP = dyn_cast(Context)) return getOrCreateSubprogramDIE(SP); if (auto *M = dyn_cast(Context)) @@ -996,6 +998,13 @@ void DwarfUnit::constructTypeDIE(DIE &Buffer, const DICompositeType *CTy) { if (!Name.empty()) addString(Buffer, dwarf::DW_AT_name, Name); + // For Swift, mangled names are put into DW_AT_linkage_name; human-readable + // names are emitted put into DW_AT_name and the accelerator table. + if ((CTy->getRuntimeLang() == dwarf::DW_LANG_Swift || + CTy->getRuntimeLang() == dwarf::DW_LANG_PLI) && + CTy->getRawIdentifier()) + addString(Buffer, dwarf::DW_AT_linkage_name, CTy->getIdentifier()); + if (Tag == dwarf::DW_TAG_enumeration_type || Tag == dwarf::DW_TAG_class_type || Tag == dwarf::DW_TAG_structure_type || Tag == dwarf::DW_TAG_union_type) { @@ -1018,8 +1027,7 @@ void DwarfUnit::constructTypeDIE(DIE &Buffer, const DICompositeType *CTy) { // No harm in adding the runtime language to the declaration. unsigned RLang = CTy->getRuntimeLang(); if (RLang) - addUInt(Buffer, dwarf::DW_AT_APPLE_runtime_class, dwarf::DW_FORM_data1, - RLang); + addUInt(Buffer, dwarf::DW_AT_APPLE_runtime_class, None, RLang); } } diff --git a/llvm/lib/CodeGen/MachineSSAUpdater.cpp b/llvm/lib/CodeGen/MachineSSAUpdater.cpp index 71a6ebaba2431..57597a68a46f7 100644 --- a/llvm/lib/CodeGen/MachineSSAUpdater.cpp +++ b/llvm/lib/CodeGen/MachineSSAUpdater.cpp @@ -246,6 +246,11 @@ class SSAUpdaterTraits { static BlkSucc_iterator BlkSucc_begin(BlkT *BB) { return BB->succ_begin(); } static BlkSucc_iterator BlkSucc_end(BlkT *BB) { return BB->succ_end(); } + /// Iterator over phis in a block. + typedef BlkT::iterator PhiItT; + static PhiItT PhiItT_begin(BlkT *BB) { return BB->begin(); } + static PhiItT PhiItT_end(BlkT *BB) { return BB->end(); } + /// Iterator for PHI operands. class PHI_iterator { private: diff --git a/llvm/lib/CodeGen/PrologEpilogInserter.cpp b/llvm/lib/CodeGen/PrologEpilogInserter.cpp index 13e753692de41..76e4e4330af80 100644 --- a/llvm/lib/CodeGen/PrologEpilogInserter.cpp +++ b/llvm/lib/CodeGen/PrologEpilogInserter.cpp @@ -311,7 +311,8 @@ void PEI::assignCalleeSavedSpillSlots(MachineFunction &F, const TargetFrameLowering *TFI = F.getSubtarget().getFrameLowering(); MachineFrameInfo *MFI = F.getFrameInfo(); - if (!TFI->assignCalleeSavedSpillSlots(F, RegInfo, CSI)) { + if (!TFI->assignCalleeSavedSpillSlots(F, RegInfo, CSI, MinCSFrameIndex, + MaxCSFrameIndex)) { // If target doesn't implement this, use generic code. if (CSI.empty()) diff --git a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp index adc51c555649b..b7af6868a67c9 100644 --- a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp @@ -88,6 +88,8 @@ void FastISel::ArgListEntry::setAttributes(ImmutableCallSite *CS, IsByVal = CS->paramHasAttr(AttrIdx, Attribute::ByVal); IsInAlloca = CS->paramHasAttr(AttrIdx, Attribute::InAlloca); IsReturned = CS->paramHasAttr(AttrIdx, Attribute::Returned); + IsSwiftSelf = CS->paramHasAttr(AttrIdx, Attribute::SwiftSelf); + IsSwiftError = CS->paramHasAttr(AttrIdx, Attribute::SwiftError); Alignment = CS->getParamAlignment(AttrIdx); } @@ -960,6 +962,10 @@ bool FastISel::lowerCallTo(CallLoweringInfo &CLI) { Flags.setInReg(); if (Arg.IsSRet) Flags.setSRet(); + if (Arg.IsSwiftSelf) + Flags.setSwiftSelf(); + if (Arg.IsSwiftError) + Flags.setSwiftError(); if (Arg.IsByVal) Flags.setByVal(); if (Arg.IsInAlloca) { @@ -1322,12 +1328,24 @@ bool FastISel::selectBitCast(const User *I) { return true; } +// Return true if we should copy from swift error to the final vreg as specified +// by SwiftErrorWorklist. +static bool shouldCopySwiftErrorsToFinalVRegs(const TargetLowering &TLI, + FunctionLoweringInfo &FuncInfo) { + if (!TLI.supportSwiftError()) + return false; + return FuncInfo.SwiftErrorWorklist.count(FuncInfo.MBB); +} + bool FastISel::selectInstruction(const Instruction *I) { // Just before the terminator instruction, insert instructions to // feed PHI nodes in successor blocks. - if (isa(I)) + if (isa(I)) { + if (shouldCopySwiftErrorsToFinalVRegs(TLI, FuncInfo)) + return false; if (!handlePHINodesInSuccessorBlocks(I->getParent())) return false; + } DbgLoc = I->getDebugLoc(); diff --git a/llvm/lib/CodeGen/SelectionDAG/FunctionLoweringInfo.cpp b/llvm/lib/CodeGen/SelectionDAG/FunctionLoweringInfo.cpp index 52216c2c1e822..7b4c0e8f9537b 100644 --- a/llvm/lib/CodeGen/SelectionDAG/FunctionLoweringInfo.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/FunctionLoweringInfo.cpp @@ -593,3 +593,31 @@ void llvm::AddLandingPadInfo(const LandingPadInst &I, MachineModuleInfo &MMI, } } } + +unsigned FunctionLoweringInfo::findSwiftErrorVReg(const MachineBasicBlock *MBB, + const Value* Val) const { + // Find the index in SwiftErrorVals. + unsigned End = SwiftErrorVals.size(); + unsigned Index = End; + for (unsigned I = 0; I < End; I++) + if (SwiftErrorVals[I] == Val) { + Index = I; + break; + } + assert(Index < End && "Can't find value in SwiftErrorVals"); + return SwiftErrorMap.lookup(MBB)[Index]; +} + +void FunctionLoweringInfo::setSwiftErrorVReg(const MachineBasicBlock *MBB, + const Value* Val, unsigned VReg) { + // Find the index in SwiftErrorVals. + unsigned End = SwiftErrorVals.size(); + unsigned Index = End; + for (unsigned I = 0, E = SwiftErrorVals.size(); I < E; I++) + if (SwiftErrorVals[I] == Val) { + Index = I; + break; + } + assert(Index < End && "Can't find value in SwiftErrorVals"); + SwiftErrorMap[MBB][Index] = VReg; +} diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index 75b2960bb37e7..a8c4c1b3c1edc 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -888,10 +888,46 @@ SDValue SelectionDAGBuilder::getControlRoot() { return Root; } +/// Copy swift error to the final virtual register at end of a basic block, as +/// specified by SwiftErrorWorklist, if necessary. +static void copySwiftErrorsToFinalVRegs(SelectionDAGBuilder &SDB) { + const TargetLowering &TLI = SDB.DAG.getTargetLoweringInfo(); + if (!TLI.supportSwiftError()) + return; + + if (!SDB.FuncInfo.SwiftErrorWorklist.count(SDB.FuncInfo.MBB)) + return; + + // Go through entries in SwiftErrorWorklist, and create copy as necessary. + auto &WorklistEntry = SDB.FuncInfo.SwiftErrorWorklist[SDB.FuncInfo.MBB]; + auto &MapEntry = SDB.FuncInfo.SwiftErrorMap[SDB.FuncInfo.MBB]; + for (unsigned I = 0, E = WorklistEntry.size(); I < E; I++) { + unsigned WorkReg = WorklistEntry[I]; + + // Find the swifterror virtual register for the value in SwiftErrorMap. + unsigned MapReg = MapEntry[I]; + assert(TargetRegisterInfo::isVirtualRegister(MapReg) && + "Entries in SwiftErrorMap should be virtual registers"); + + if (WorkReg == MapReg) + continue; + + // Create copy from SwiftErrorMap to SwiftWorklist. + auto &DL = SDB.DAG.getDataLayout(); + SDValue CopyNode = SDB.DAG.getCopyToReg( + SDB.getRoot(), SDB.getCurSDLoc(), WorkReg, + SDB.DAG.getRegister(MapReg, EVT(TLI.getPointerTy(DL)))); + MapEntry[I] = WorkReg; + SDB.DAG.setRoot(CopyNode); + } +} + void SelectionDAGBuilder::visit(const Instruction &I) { // Set up outgoing PHI node register values before emitting the terminator. - if (isa(&I)) + if (isa(&I)) { + copySwiftErrorsToFinalVRegs(*this); HandlePHINodesInSuccessorBlocks(I.getParent()); + } ++SDNodeOrder; @@ -1416,6 +1452,23 @@ void SelectionDAGBuilder::visitRet(const ReturnInst &I) { } } + // Push in swifterror virtual register as the last element of Outs. This makes + // sure swifterror virtual register will be returned in the swifterror + // physical register. + const Function *F = I.getParent()->getParent(); + if (F->getAttributes().hasAttrSomewhere(Attribute::SwiftError) && + TLI.supportSwiftError()) { + ISD::ArgFlagsTy Flags = ISD::ArgFlagsTy(); + Flags.setSwiftError(); + Outs.push_back(ISD::OutputArg(Flags, EVT(TLI.getPointerTy(DL)) /*vt*/, + EVT(TLI.getPointerTy(DL)) /*argvt*/, + true /*isfixed*/, 1 /*origidx*/, + 0 /*partOffs*/)); + // Create SDNode for the swifterror virtual register. + OutVals.push_back(DAG.getRegister(FuncInfo.SwiftErrorMap[FuncInfo.MBB][0], + EVT(TLI.getPointerTy(DL)))); + } + bool isVarArg = DAG.getMachineFunction().getFunction()->isVarArg(); CallingConv::ID CallConv = DAG.getMachineFunction().getFunction()->getCallingConv(); @@ -3098,6 +3151,18 @@ void SelectionDAGBuilder::visitLoad(const LoadInst &I) { return visitAtomicLoad(I); const Value *SV = I.getOperand(0); + if (const Argument *Arg = dyn_cast(SV)) { + const TargetLowering &TLI = DAG.getTargetLoweringInfo(); + if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) + return visitLoadFromSwiftError(I); + } + + if (const AllocaInst *Alloca = dyn_cast(SV)) { + const TargetLowering &TLI = DAG.getTargetLoweringInfo(); + if (Alloca->isSwiftError() && TLI.supportSwiftError()) + return visitLoadFromSwiftError(I); + } + SDValue Ptr = getValue(SV); Type *Ty = I.getType(); @@ -3192,6 +3257,62 @@ void SelectionDAGBuilder::visitLoad(const LoadInst &I) { DAG.getVTList(ValueVTs), Values)); } +void SelectionDAGBuilder::visitStoreToSwiftError(const StoreInst &I) { + const TargetLowering &TLI = DAG.getTargetLoweringInfo(); + assert(TLI.supportSwiftError() && + "call visitStoreToSwiftError when backend supports swifterror"); + + SmallVector ValueVTs; + SmallVector Offsets; + const Value *SrcV = I.getOperand(0); + ComputeValueVTs(DAG.getTargetLoweringInfo(), DAG.getDataLayout(), + SrcV->getType(), ValueVTs, &Offsets); + assert(ValueVTs.size() == 1 && "expect a single EVT for swifterror"); + + SDValue Src = getValue(SrcV); + // Create a virtual register, then update the virtual register. + auto &DL = DAG.getDataLayout(); + const TargetRegisterClass *RC = TLI.getRegClassFor(TLI.getPointerTy(DL)); + unsigned VReg = FuncInfo.MF->getRegInfo().createVirtualRegister(RC); + // Chain, DL, Reg, N or Chain, DL, Reg, N, Glue + // Chain can be getRoot or getControlRoot. + SDValue CopyNode = DAG.getCopyToReg(getRoot(), getCurSDLoc(), VReg, + SDValue(Src.getNode(), Src.getResNo())); + DAG.setRoot(CopyNode); + FuncInfo.setSwiftErrorVReg(FuncInfo.MBB, I.getOperand(1), VReg); +} + +void SelectionDAGBuilder::visitLoadFromSwiftError(const LoadInst &I) { + assert(DAG.getTargetLoweringInfo().supportSwiftError() && + "call visitLoadFromSwiftError when backend supports swifterror"); + + assert(!I.isVolatile() && + I.getMetadata(LLVMContext::MD_nontemporal) == nullptr && + I.getMetadata(LLVMContext::MD_invariant_load) == nullptr && + "Support volatile, non temporal, invariant for load_from_swift_error"); + + const Value *SV = I.getOperand(0); + Type *Ty = I.getType(); + AAMDNodes AAInfo; + I.getAAMetadata(AAInfo); + assert(!AA->pointsToConstantMemory(MemoryLocation( + SV, DAG.getDataLayout().getTypeStoreSize(Ty), AAInfo)) && + "load_from_swift_error should not be constant memory"); + + SmallVector ValueVTs; + SmallVector Offsets; + ComputeValueVTs(DAG.getTargetLoweringInfo(), DAG.getDataLayout(), Ty, + ValueVTs, &Offsets); + assert(ValueVTs.size() == 1 && "expect a single EVT for swifterror"); + + // Chain, DL, Reg, VT, Glue or Chain, DL, Reg, VT + SDValue L = DAG.getCopyFromReg(getRoot(), getCurSDLoc(), + FuncInfo.findSwiftErrorVReg(FuncInfo.MBB, SV), + ValueVTs[0]); + + setValue(&I, L); +} + void SelectionDAGBuilder::visitStore(const StoreInst &I) { if (I.isAtomic()) return visitAtomicStore(I); @@ -3199,6 +3320,18 @@ void SelectionDAGBuilder::visitStore(const StoreInst &I) { const Value *SrcV = I.getOperand(0); const Value *PtrV = I.getOperand(1); + if (const Argument *Arg = dyn_cast(PtrV)) { + const TargetLowering &TLI = DAG.getTargetLoweringInfo(); + if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) + return visitStoreToSwiftError(I); + } + + if (const AllocaInst *Alloca = dyn_cast(PtrV)) { + const TargetLowering &TLI = DAG.getTargetLoweringInfo(); + if (Alloca->isSwiftError() && TLI.supportSwiftError()) + return visitStoreToSwiftError(I); + } + SmallVector ValueVTs; SmallVector Offsets; ComputeValueVTs(DAG.getTargetLoweringInfo(), DAG.getDataLayout(), @@ -5345,11 +5478,14 @@ void SelectionDAGBuilder::LowerCallTo(ImmutableCallSite CS, SDValue Callee, PointerType *PT = cast(CS.getCalledValue()->getType()); FunctionType *FTy = cast(PT->getElementType()); Type *RetTy = FTy->getReturnType(); + auto &DL = DAG.getDataLayout(); TargetLowering::ArgListTy Args; TargetLowering::ArgListEntry Entry; Args.reserve(CS.arg_size()); + bool HasSwiftError = false; + const TargetLowering &TLI = DAG.getTargetLoweringInfo(); for (ImmutableCallSite::arg_iterator i = CS.arg_begin(), e = CS.arg_end(); i != e; ++i) { const Value *V = *i; @@ -5363,6 +5499,14 @@ void SelectionDAGBuilder::LowerCallTo(ImmutableCallSite CS, SDValue Callee, // Skip the first return-type Attribute to get to params. Entry.setAttributes(&CS, i - CS.arg_begin() + 1); + + // Use swifterror virtual register as input to the call. + if (Entry.isSwiftError && TLI.supportSwiftError()) { + HasSwiftError = true; + Entry.Node = DAG.getRegister(FuncInfo.SwiftErrorMap[FuncInfo.MBB][0], + EVT(TLI.getPointerTy(DL))); + } + Args.push_back(Entry); // If we have an explicit sret argument that is an Instruction, (i.e., it @@ -5384,6 +5528,19 @@ void SelectionDAGBuilder::LowerCallTo(ImmutableCallSite CS, SDValue Callee, if (Result.first.getNode()) setValue(CS.getInstruction(), Result.first); + + // The last element of CLI.InVals has the SDValue for swifterror return. + // Here we copy it to a virtual register and update SwiftErrorMap for + // book-keeping. + if (HasSwiftError && TLI.supportSwiftError()) { + // Get the last element of InVals. + SDValue Src = CLI.InVals.back(); + const TargetRegisterClass *RC = TLI.getRegClassFor(TLI.getPointerTy(DL)); + unsigned VReg = FuncInfo.MF->getRegInfo().createVirtualRegister(RC); + SDValue CopyNode = CLI.DAG.getCopyToReg(Result.second, CLI.DL, VReg, Src); + FuncInfo.SwiftErrorMap[FuncInfo.MBB][0] = VReg; + DAG.setRoot(CopyNode); + } } /// IsOnlyUsedInZeroEqualityComparison - Return true if it only matters that the @@ -7021,6 +7178,8 @@ TargetLowering::LowerCallTo(TargetLowering::CallLoweringInfo &CLI) const { Entry.isNest = false; Entry.isByVal = false; Entry.isReturned = false; + Entry.isSwiftSelf = false; + Entry.isSwiftError = false; Entry.Alignment = Align; CLI.getArgs().insert(CLI.getArgs().begin(), Entry); CLI.RetTy = Type::getVoidTy(CLI.RetTy->getContext()); @@ -7049,10 +7208,23 @@ TargetLowering::LowerCallTo(TargetLowering::CallLoweringInfo &CLI) const { } } + // We push in swifterror return as the last element of CLI.Ins. + ArgListTy &Args = CLI.getArgs(); + if (supportSwiftError()) { + for (unsigned i = 0, e = Args.size(); i != e; ++i) { + if (Args[i].isSwiftError) { + ISD::InputArg MyFlags; + MyFlags.VT = getPointerTy(DL); + MyFlags.ArgVT = EVT(getPointerTy(DL)); + MyFlags.Flags.setSwiftError(); + CLI.Ins.push_back(MyFlags); + } + } + } + // Handle all of the outgoing arguments. CLI.Outs.clear(); CLI.OutVals.clear(); - ArgListTy &Args = CLI.getArgs(); for (unsigned i = 0, e = Args.size(); i != e; ++i) { SmallVector ValueVTs; ComputeValueVTs(*this, DL, Args[i].Ty, ValueVTs); @@ -7078,6 +7250,10 @@ TargetLowering::LowerCallTo(TargetLowering::CallLoweringInfo &CLI) const { Flags.setInReg(); if (Args[i].isSRet) Flags.setSRet(); + if (Args[i].isSwiftSelf) + Flags.setSwiftSelf(); + if (Args[i].isSwiftError) + Flags.setSwiftError(); if (Args[i].isByVal) Flags.setByVal(); if (Args[i].isInAlloca) { @@ -7163,6 +7339,9 @@ TargetLowering::LowerCallTo(TargetLowering::CallLoweringInfo &CLI) const { SmallVector InVals; CLI.Chain = LowerCall(CLI, InVals); + // Update CLI.InVals to use outside of this function. + CLI.InVals = InVals; + // Verify that the target's LowerCall behaved as expected. assert(CLI.Chain.getNode() && CLI.Chain.getValueType() == MVT::Other && "LowerCall didn't return a valid chain!"); @@ -7350,6 +7529,10 @@ void SelectionDAGISel::LowerArguments(const Function &F) { Flags.setInReg(); if (F.getAttributes().hasAttribute(Idx, Attribute::StructRet)) Flags.setSRet(); + if (F.getAttributes().hasAttribute(Idx, Attribute::SwiftSelf)) + Flags.setSwiftSelf(); + if (F.getAttributes().hasAttribute(Idx, Attribute::SwiftError)) + Flags.setSwiftError(); if (F.getAttributes().hasAttribute(Idx, Attribute::ByVal)) Flags.setByVal(); if (F.getAttributes().hasAttribute(Idx, Attribute::InAlloca)) { @@ -7507,6 +7690,14 @@ void SelectionDAGISel::LowerArguments(const Function &F) { FuncInfo->setArgumentFrameIndex(&*I, FI->getIndex()); } + // Update SwiftErrorMap. + if (Res.getOpcode() == ISD::CopyFromReg && TLI->supportSwiftError() && + F.getAttributes().hasAttribute(Idx, Attribute::SwiftError)) { + unsigned Reg = cast(Res.getOperand(1))->getReg(); + if (TargetRegisterInfo::isVirtualRegister(Reg)) + FuncInfo->SwiftErrorMap[FuncInfo->MBB][0] = Reg; + } + // If this argument is live outside of the entry block, insert a copy from // wherever we got it to the vreg that other BB's will reference it as. if (!TM.Options.EnableFastISel && Res.getOpcode() == ISD::CopyFromReg) { diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h index 321e18d682a4c..2696d89eb856b 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h @@ -840,6 +840,8 @@ class SelectionDAGBuilder { bool visitBinaryFloatCall(const CallInst &I, unsigned Opcode); void visitAtomicLoad(const LoadInst &I); void visitAtomicStore(const StoreInst &I); + void visitLoadFromSwiftError(const LoadInst &I); + void visitStoreToSwiftError(const StoreInst &I); void visitInlineAsm(ImmutableCallSite CS); const char *visitIntrinsicCall(const CallInst &I, unsigned Intrinsic); diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp index a9113252abb7d..29313e04a1b6b 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp @@ -1106,12 +1106,130 @@ static void collectFailStats(const Instruction *I) { } #endif +/// Set up SwiftErrorVals by going through the function. If the function has +/// swifterror argument, it will be the first entry. +static void setupSwiftErrorVals(const Function &Fn, const TargetLowering *TLI, + FunctionLoweringInfo *FuncInfo) { + if (!TLI->supportSwiftError()) + return; + + FuncInfo->SwiftErrorVals.clear(); + FuncInfo->SwiftErrorMap.clear(); + FuncInfo->SwiftErrorWorklist.clear(); + + // Check if function has a swifterror argument. + for (Function::const_arg_iterator AI = Fn.arg_begin(), AE = Fn.arg_end(); + AI != AE; ++AI) + if (AI->hasSwiftErrorAttr()) + FuncInfo->SwiftErrorVals.push_back(AI); + + for (const auto &LLVMBB : Fn) + for (const auto &Inst : LLVMBB) { + if (const AllocaInst *Alloca = dyn_cast(&Inst)) + if (Alloca->isSwiftError()) + FuncInfo->SwiftErrorVals.push_back(Alloca); + } +} + +/// For each basic block, merge incoming swifterror values or simply propagate +/// them. The merged results will be saved in SwiftErrorMap. For predecessors +/// that are not yet visited, we create virtual registers to hold the swifterror +/// values and save them in SwiftErrorWorklist. +static void mergeIncomingSwiftErrors(FunctionLoweringInfo *FuncInfo, + const TargetLowering *TLI, + const TargetInstrInfo *TII, + const BasicBlock *LLVMBB, + SelectionDAGBuilder *SDB) { + if (!TLI->supportSwiftError()) + return; + + // We should only do this when we have swifterror parameter or swifterror + // alloc. + if (FuncInfo->SwiftErrorVals.empty()) + return; + + // At beginning of a basic block, insert PHI nodes or get the virtual + // register from the only predecessor, and update SwiftErrorMap; if one + // of the predecessors is not visited, update SwiftErrorWorklist. + // At end of a basic block, if a block is in SwiftErrorWorklist, insert copy + // to sync up the virtual register assignment. + + // Always create a virtual register for each swifterror value in entry block. + auto &DL = SDB->DAG.getDataLayout(); + const TargetRegisterClass *RC = TLI->getRegClassFor(TLI->getPointerTy(DL)); + if (pred_begin(LLVMBB) == pred_end(LLVMBB)) { + for (unsigned I = 0, E = FuncInfo->SwiftErrorVals.size(); I < E; I++) { + unsigned VReg = FuncInfo->MF->getRegInfo().createVirtualRegister(RC); + // Assign Undef to Vreg. We construct MI directly to make sure it works + // with FastISel. + BuildMI(*FuncInfo->MBB, FuncInfo->InsertPt, SDB->getCurDebugLoc(), + TII->get(TargetOpcode::IMPLICIT_DEF), VReg); + FuncInfo->SwiftErrorMap[FuncInfo->MBB].push_back(VReg); + } + return; + } + + if (auto *UniquePred = LLVMBB->getUniquePredecessor()) { + auto *UniquePredMBB = FuncInfo->MBBMap[UniquePred]; + if (!FuncInfo->SwiftErrorMap.count(UniquePredMBB)) { + // Update SwiftErrorWorklist with a new virtual register. + for (unsigned I = 0, E = FuncInfo->SwiftErrorVals.size(); I < E; I++) { + unsigned VReg = FuncInfo->MF->getRegInfo().createVirtualRegister(RC); + FuncInfo->SwiftErrorWorklist[UniquePredMBB].push_back(VReg); + // Propagate the information from the single predecessor. + FuncInfo->SwiftErrorMap[FuncInfo->MBB].push_back(VReg); + } + return; + } + // Propagate the information from the single predecessor. + FuncInfo->SwiftErrorMap[FuncInfo->MBB] = + FuncInfo->SwiftErrorMap[UniquePredMBB]; + return; + } + + // For the case of multiple predecessors, update SwiftErrorWorklist. + // Handle the case where we have two or more predecessors being the same. + for (const_pred_iterator PI = pred_begin(LLVMBB), PE = pred_end(LLVMBB); + PI != PE; ++PI) { + auto *PredMBB = FuncInfo->MBBMap[*PI]; + if (!FuncInfo->SwiftErrorMap.count(PredMBB) && + !FuncInfo->SwiftErrorWorklist.count(PredMBB)) { + for (unsigned I = 0, E = FuncInfo->SwiftErrorVals.size(); I < E; I++) { + unsigned VReg = FuncInfo->MF->getRegInfo().createVirtualRegister(RC); + FuncInfo->SwiftErrorWorklist[PredMBB].push_back(VReg); + } + } + } + + // For the case of multiple predecessors, create a virtual register for + // each swifterror value and generate Phi node. + for (unsigned I = 0, E = FuncInfo->SwiftErrorVals.size(); I < E; I++) { + unsigned VReg = FuncInfo->MF->getRegInfo().createVirtualRegister(RC); + FuncInfo->SwiftErrorMap[FuncInfo->MBB].push_back(VReg); + + MachineInstrBuilder SwiftErrorPHI = BuildMI(*FuncInfo->MBB, + FuncInfo->MBB->begin(), SDB->getCurDebugLoc(), + TII->get(TargetOpcode::PHI), VReg); + for (const_pred_iterator PI = pred_begin(LLVMBB), PE = pred_end(LLVMBB); + PI != PE; ++PI) { + auto *PredMBB = FuncInfo->MBBMap[*PI]; + unsigned SwiftErrorReg = FuncInfo->SwiftErrorMap.count(PredMBB) ? + FuncInfo->SwiftErrorMap[PredMBB][I] : + FuncInfo->SwiftErrorWorklist[PredMBB][I]; + SwiftErrorPHI.addReg(SwiftErrorReg) + .addMBB(PredMBB); + } + } +} + void SelectionDAGISel::SelectAllBasicBlocks(const Function &Fn) { // Initialize the Fast-ISel state, if needed. FastISel *FastIS = nullptr; if (TM.Options.EnableFastISel) FastIS = TLI->createFastISel(*FuncInfo, LibInfo); + setupSwiftErrorVals(Fn, TLI, FuncInfo); + // Iterate over all basic blocks in the function. ReversePostOrderTraversal RPOT(&Fn); for (ReversePostOrderTraversal::rpo_iterator @@ -1150,6 +1268,7 @@ void SelectionDAGISel::SelectAllBasicBlocks(const Function &Fn) { if (!FuncInfo->MBB) continue; // Some blocks like catchpads have no code or MBB. FuncInfo->InsertPt = FuncInfo->MBB->getFirstNonPHI(); + mergeIncomingSwiftErrors(FuncInfo, TLI, TII, LLVMBB, SDB); // Setup an EH landing-pad block. FuncInfo->ExceptionPointerVirtReg = 0; diff --git a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp index 21935cdd46993..81e990e6c1ca1 100644 --- a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp @@ -77,6 +77,8 @@ void TargetLowering::ArgListEntry::setAttributes(ImmutableCallSite *CS, isByVal = CS->paramHasAttr(AttrIdx, Attribute::ByVal); isInAlloca = CS->paramHasAttr(AttrIdx, Attribute::InAlloca); isReturned = CS->paramHasAttr(AttrIdx, Attribute::Returned); + isSwiftSelf = CS->paramHasAttr(AttrIdx, Attribute::SwiftSelf); + isSwiftError = CS->paramHasAttr(AttrIdx, Attribute::SwiftError); Alignment = CS->getParamAlignment(AttrIdx); } diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp index db757508594a2..8987c213ff5c6 100644 --- a/llvm/lib/IR/AsmWriter.cpp +++ b/llvm/lib/IR/AsmWriter.cpp @@ -319,6 +319,7 @@ static void PrintCallingConv(unsigned cc, raw_ostream &Out) { case CallingConv::X86_64_Win64: Out << "x86_64_win64cc"; break; case CallingConv::SPIR_FUNC: Out << "spir_func"; break; case CallingConv::SPIR_KERNEL: Out << "spir_kernel"; break; + case CallingConv::Swift: Out << "swiftcc"; break; case CallingConv::HHVM: Out << "hhvmcc"; break; case CallingConv::HHVM_C: Out << "hhvm_ccc"; break; } @@ -3033,6 +3034,8 @@ void AssemblyWriter::printInstruction(const Instruction &I) { Out << ' '; if (AI->isUsedWithInAlloca()) Out << "inalloca "; + if (AI->isSwiftError()) + Out << "swifterror "; TypePrinter.print(AI->getAllocatedType(), Out); // Explicitly write the array size if the code is broken, if it's an array diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp index 2586cb54c3ee8..f437aa08f5b60 100644 --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -198,6 +198,10 @@ std::string Attribute::getAsString(bool InAttrGrp) const { return "byval"; if (hasAttribute(Attribute::Convergent)) return "convergent"; + if (hasAttribute(Attribute::SwiftError)) + return "swifterror"; + if (hasAttribute(Attribute::SwiftSelf)) + return "swiftself"; if (hasAttribute(Attribute::InAlloca)) return "inalloca"; if (hasAttribute(Attribute::InlineHint)) @@ -442,6 +446,8 @@ uint64_t AttributeImpl::getAttrMask(Attribute::AttrKind Val) { case Attribute::JumpTable: return 1ULL << 45; case Attribute::Convergent: return 1ULL << 46; case Attribute::SafeStack: return 1ULL << 47; + case Attribute::SwiftSelf: return 1ULL << 48; + case Attribute::SwiftError: return 1ULL << 49; case Attribute::Dereferenceable: llvm_unreachable("dereferenceable attribute not supported in raw format"); break; diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp index 3a1c7a4ca3616..5a0c3cb1e39a5 100644 --- a/llvm/lib/IR/Function.cpp +++ b/llvm/lib/IR/Function.cpp @@ -93,6 +93,16 @@ bool Argument::hasByValAttr() const { hasAttribute(getArgNo()+1, Attribute::ByVal); } +bool Argument::hasSwiftSelfAttr() const { + return getParent()->getAttributes(). + hasAttribute(getArgNo()+1, Attribute::SwiftSelf); +} + +bool Argument::hasSwiftErrorAttr() const { + return getParent()->getAttributes(). + hasAttribute(getArgNo()+1, Attribute::SwiftError); +} + /// \brief Return true if this argument has the inalloca attribute on it in /// its containing function. bool Argument::hasInAllocaAttr() const { diff --git a/llvm/lib/IR/Instructions.cpp b/llvm/lib/IR/Instructions.cpp index dfd711f5c23aa..1db39497a925d 100644 --- a/llvm/lib/IR/Instructions.cpp +++ b/llvm/lib/IR/Instructions.cpp @@ -3900,6 +3900,7 @@ AllocaInst *AllocaInst::cloneImpl() const { AllocaInst *Result = new AllocaInst(getAllocatedType(), (Value *)getOperand(0), getAlignment()); Result->setUsedWithInAlloca(isUsedWithInAlloca()); + Result->setSwiftError(isSwiftError()); return Result; } diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index 813f9ca674485..ffe3307a0c55f 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -1269,9 +1269,12 @@ void Verifier::VerifyParameterAttrs(AttributeSet Attrs, unsigned Idx, Type *Ty, !Attrs.hasAttribute(Idx, Attribute::StructRet) && !Attrs.hasAttribute(Idx, Attribute::NoCapture) && !Attrs.hasAttribute(Idx, Attribute::Returned) && - !Attrs.hasAttribute(Idx, Attribute::InAlloca), - "Attributes 'byval', 'inalloca', 'nest', 'sret', 'nocapture', and " - "'returned' do not apply to return values!", + !Attrs.hasAttribute(Idx, Attribute::InAlloca) && + !Attrs.hasAttribute(Idx, Attribute::SwiftSelf) && + !Attrs.hasAttribute(Idx, Attribute::SwiftError), + "Attributes 'byval', 'inalloca', 'nest', 'sret', 'nocapture', " + "'returned', 'swiftself', and 'swifterror' do not apply to return " + "values!", V); // Check for mutually incompatible attributes. Only inreg is compatible with @@ -1348,6 +1351,8 @@ void Verifier::VerifyFunctionAttrs(FunctionType *FT, AttributeSet Attrs, bool SawNest = false; bool SawReturned = false; bool SawSRet = false; + bool SawSwiftSelf = false; + bool SawSwiftError = false; for (unsigned i = 0, e = Attrs.getNumSlots(); i != e; ++i) { unsigned Idx = Attrs.getSlotIndex(i); @@ -1387,6 +1392,17 @@ void Verifier::VerifyFunctionAttrs(FunctionType *FT, AttributeSet Attrs, SawSRet = true; } + if (Attrs.hasAttribute(Idx, Attribute::SwiftSelf)) { + Assert(!SawSwiftSelf, "Cannot have multiple 'swiftself' parameters!", V); + SawSwiftSelf = true; + } + + if (Attrs.hasAttribute(Idx, Attribute::SwiftError)) { + Assert(!SawSwiftError, "Cannot have multiple 'swifterror' parameters!", + V); + SawSwiftError = true; + } + if (Attrs.hasAttribute(Idx, Attribute::InAlloca)) { Assert(Idx == FT->getNumParams(), "inalloca isn't on the last parameter!", V); @@ -2263,6 +2279,16 @@ void Verifier::VerifyCallSite(CallSite CS) { "inalloca argument for call has mismatched alloca", AI, I); } + // For each argument of the callsite, if it has the swifterror argument, + // make sure the underlying alloca has swifterror as well. + for (unsigned i = 0, e = FTy->getNumParams(); i != e; ++i) + if (CS.paramHasAttr(i+1, Attribute::SwiftError)) { + Value *SwiftErrorArg = CS.getArgument(i); + if (auto AI = dyn_cast(SwiftErrorArg->stripInBoundsOffsets())) + Assert(AI->isSwiftError(), + "swifterror argument for call has mismatched alloca", AI, I); + } + if (FTy->isVarArg()) { // FIXME? is 'nest' even legal here? bool SawNest = false; @@ -2341,7 +2367,8 @@ static bool isTypeCongruent(Type *L, Type *R) { static AttrBuilder getParameterABIAttributes(int I, AttributeSet Attrs) { static const Attribute::AttrKind ABIAttrs[] = { Attribute::StructRet, Attribute::ByVal, Attribute::InAlloca, - Attribute::InReg, Attribute::Returned}; + Attribute::InReg, Attribute::Returned, Attribute::SwiftSelf, + Attribute::SwiftError}; AttrBuilder Copy; for (auto AK : ABIAttrs) { if (Attrs.hasAttribute(I + 1, AK)) diff --git a/llvm/lib/MC/MCAsmInfo.cpp b/llvm/lib/MC/MCAsmInfo.cpp index 36e10b3c6a073..fa7d438d15918 100644 --- a/llvm/lib/MC/MCAsmInfo.cpp +++ b/llvm/lib/MC/MCAsmInfo.cpp @@ -75,6 +75,7 @@ MCAsmInfo::MCAsmInfo() { HasSingleParameterDotFile = true; HasIdentDirective = false; HasNoDeadStrip = false; + HasAltEntry = false; WeakDirective = "\t.weak\t"; WeakRefDirective = nullptr; HasWeakDefDirective = false; diff --git a/llvm/lib/MC/MCAsmInfoDarwin.cpp b/llvm/lib/MC/MCAsmInfoDarwin.cpp index bb90ff2c350ae..200f5312a73bf 100644 --- a/llvm/lib/MC/MCAsmInfoDarwin.cpp +++ b/llvm/lib/MC/MCAsmInfoDarwin.cpp @@ -88,6 +88,7 @@ MCAsmInfoDarwin::MCAsmInfoDarwin() { HasDotTypeDotSizeDirective = false; HasNoDeadStrip = true; + HasAltEntry = true; DwarfUsesRelocationsAcrossSections = false; diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp index 4f2d1d2229d92..e35c062c9acca 100644 --- a/llvm/lib/MC/MCAsmStreamer.cpp +++ b/llvm/lib/MC/MCAsmStreamer.cpp @@ -453,6 +453,7 @@ bool MCAsmStreamer::EmitSymbolAttribute(MCSymbol *Symbol, OS << "\t.no_dead_strip\t"; break; case MCSA_SymbolResolver: OS << "\t.symbol_resolver\t"; break; + case MCSA_AltEntry: OS << "\t.alt_entry\t"; break; case MCSA_PrivateExtern: OS << "\t.private_extern\t"; break; diff --git a/llvm/lib/MC/MCELFStreamer.cpp b/llvm/lib/MC/MCELFStreamer.cpp index 06d161bccab48..5e680a8c788f5 100644 --- a/llvm/lib/MC/MCELFStreamer.cpp +++ b/llvm/lib/MC/MCELFStreamer.cpp @@ -283,6 +283,9 @@ bool MCELFStreamer::EmitSymbolAttribute(MCSymbol *S, MCSymbolAttr Attribute) { case MCSA_Internal: Symbol->setVisibility(ELF::STV_INTERNAL); break; + + case MCSA_AltEntry: + llvm_unreachable("ELF doesn't support this attribute"); } return true; diff --git a/llvm/lib/MC/MCMachOStreamer.cpp b/llvm/lib/MC/MCMachOStreamer.cpp index 52ecf9fcfbf3d..634265819b0da 100644 --- a/llvm/lib/MC/MCMachOStreamer.cpp +++ b/llvm/lib/MC/MCMachOStreamer.cpp @@ -345,6 +345,10 @@ bool MCMachOStreamer::EmitSymbolAttribute(MCSymbol *Sym, Symbol->setSymbolResolver(); break; + case MCSA_AltEntry: + Symbol->setAltEntry(); + break; + case MCSA_PrivateExtern: Symbol->setExternal(true); Symbol->setPrivateExtern(true); diff --git a/llvm/lib/MC/MCParser/AsmParser.cpp b/llvm/lib/MC/MCParser/AsmParser.cpp index 8903637678061..7df7d514d96fa 100644 --- a/llvm/lib/MC/MCParser/AsmParser.cpp +++ b/llvm/lib/MC/MCParser/AsmParser.cpp @@ -347,8 +347,8 @@ class AsmParser : public MCAsmParser { DK_BALIGNL, DK_P2ALIGN, DK_P2ALIGNW, DK_P2ALIGNL, DK_ORG, DK_FILL, DK_ENDR, DK_BUNDLE_ALIGN_MODE, DK_BUNDLE_LOCK, DK_BUNDLE_UNLOCK, DK_ZERO, DK_EXTERN, DK_GLOBL, DK_GLOBAL, - DK_LAZY_REFERENCE, DK_NO_DEAD_STRIP, DK_SYMBOL_RESOLVER, DK_PRIVATE_EXTERN, - DK_REFERENCE, DK_WEAK_DEFINITION, DK_WEAK_REFERENCE, + DK_LAZY_REFERENCE, DK_NO_DEAD_STRIP, DK_SYMBOL_RESOLVER, DK_ALT_ENTRY, + DK_PRIVATE_EXTERN, DK_REFERENCE, DK_WEAK_DEFINITION, DK_WEAK_REFERENCE, DK_WEAK_DEF_CAN_BE_HIDDEN, DK_COMM, DK_COMMON, DK_LCOMM, DK_ABORT, DK_INCLUDE, DK_INCBIN, DK_CODE16, DK_CODE16GCC, DK_REPT, DK_IRP, DK_IRPC, DK_IF, DK_IFEQ, DK_IFGE, DK_IFGT, DK_IFLE, DK_IFLT, DK_IFNE, DK_IFB, @@ -1569,6 +1569,8 @@ bool AsmParser::parseStatement(ParseStatementInfo &Info, return parseDirectiveSymbolAttribute(MCSA_NoDeadStrip); case DK_SYMBOL_RESOLVER: return parseDirectiveSymbolAttribute(MCSA_SymbolResolver); + case DK_ALT_ENTRY: + return parseDirectiveSymbolAttribute(MCSA_AltEntry); case DK_PRIVATE_EXTERN: return parseDirectiveSymbolAttribute(MCSA_PrivateExtern); case DK_REFERENCE: @@ -4266,6 +4268,7 @@ void AsmParser::initializeDirectiveKindMap() { DirectiveKindMap[".lazy_reference"] = DK_LAZY_REFERENCE; DirectiveKindMap[".no_dead_strip"] = DK_NO_DEAD_STRIP; DirectiveKindMap[".symbol_resolver"] = DK_SYMBOL_RESOLVER; + DirectiveKindMap[".alt_entry"] = DK_ALT_ENTRY; DirectiveKindMap[".private_extern"] = DK_PRIVATE_EXTERN; DirectiveKindMap[".reference"] = DK_REFERENCE; DirectiveKindMap[".weak_definition"] = DK_WEAK_DEFINITION; diff --git a/llvm/lib/Target/AArch64/AArch64CallingConvention.td b/llvm/lib/Target/AArch64/AArch64CallingConvention.td index 948b9ddb5df6d..1def59ce76516 100644 --- a/llvm/lib/Target/AArch64/AArch64CallingConvention.td +++ b/llvm/lib/Target/AArch64/AArch64CallingConvention.td @@ -86,6 +86,8 @@ def RetCC_AArch64_AAPCS : CallingConv<[ CCIfType<[v2f32], CCBitConvertToType>, CCIfType<[v2f64, v4f32], CCBitConvertToType>, + CCIfSwiftError>>, + // Big endian vectors must be passed as if they were 1-element vectors so that // their lanes are in a consistent order. CCIfBigEndian>, + // An SwiftSelf is passed in X9. + CCIfSwiftSelf>>, + + // A SwiftError is passed in X19. + CCIfSwiftError>>, + CCIfConsecutiveRegs>, // Handle i1, i8, i16, i32, i64, f32, f64 and v2f64 by passing in registers, @@ -270,6 +278,11 @@ def CSR_AArch64_AAPCS : CalleeSavedRegs<(add LR, FP, X19, X20, X21, X22, // case) def CSR_AArch64_AAPCS_ThisReturn : CalleeSavedRegs<(add CSR_AArch64_AAPCS, X0)>; +def CSR_AArch64_AAPCS_SwiftError : CalleeSavedRegs<(add LR, FP, X20, X21, X22, + X23, X24, X25, X26, X27, X28, + D8, D9, D10, D11, + D12, D13, D14, D15)>; + // The function used by Darwin to obtain the address of a thread-local variable // guarantees more than a normal AAPCS function. x16 and x17 are used on the // fast path for calculation, but other registers except X0 (argument/return) diff --git a/llvm/lib/Target/AArch64/AArch64FastISel.cpp b/llvm/lib/Target/AArch64/AArch64FastISel.cpp index 2f50480efbe23..ddae47a6cf956 100644 --- a/llvm/lib/Target/AArch64/AArch64FastISel.cpp +++ b/llvm/lib/Target/AArch64/AArch64FastISel.cpp @@ -1904,6 +1904,17 @@ bool AArch64FastISel::selectLoad(const Instruction *I) { cast(I)->isAtomic()) return false; + const Value *SV = I->getOperand(0); + if (const Argument *Arg = dyn_cast(SV)) { + if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) + return false; + } + + if (const AllocaInst *Alloca = dyn_cast(SV)) { + if (Alloca->isSwiftError() && TLI.supportSwiftError()) + return false; + } + // See if we can handle this address. Address Addr; if (!computeAddress(I->getOperand(0), Addr, I->getType())) @@ -2068,6 +2079,17 @@ bool AArch64FastISel::selectStore(const Instruction *I) { cast(I)->isAtomic()) return false; + const Value *PtrV = I->getOperand(1); + if (const Argument *Arg = dyn_cast(PtrV)) { + if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) + return false; + } + + if (const AllocaInst *Alloca = dyn_cast(PtrV)) { + if (Alloca->isSwiftError() && TLI.supportSwiftError()) + return false; + } + // Get the value to be stored into a register. Use the zero register directly // when possible to avoid an unnecessary copy and a wasted register. unsigned SrcReg = 0; @@ -2845,6 +2867,8 @@ bool AArch64FastISel::fastLowerArguments() { if (F->getAttributes().hasAttribute(Idx, Attribute::ByVal) || F->getAttributes().hasAttribute(Idx, Attribute::InReg) || F->getAttributes().hasAttribute(Idx, Attribute::StructRet) || + F->getAttributes().hasAttribute(Idx, Attribute::SwiftSelf) || + F->getAttributes().hasAttribute(Idx, Attribute::SwiftError) || F->getAttributes().hasAttribute(Idx, Attribute::Nest)) return false; @@ -3096,7 +3120,8 @@ bool AArch64FastISel::fastLowerCall(CallLoweringInfo &CLI) { return false; for (auto Flag : CLI.OutFlags) - if (Flag.isInReg() || Flag.isSRet() || Flag.isNest() || Flag.isByVal()) + if (Flag.isInReg() || Flag.isSRet() || Flag.isNest() || Flag.isByVal() || + Flag.isSwiftSelf() || Flag.isSwiftError()) return false; // Set up the argument vectors. @@ -3678,6 +3703,10 @@ bool AArch64FastISel::selectRet(const Instruction *I) { if (F.isVarArg()) return false; + if (F.getAttributes().hasAttrSomewhere(Attribute::SwiftError) && + TLI.supportSwiftError()) + return false; + // Build a list of return value registers. SmallVector RetRegs; diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp index c5b7d0c168e00..edf3e7d0ef529 100644 --- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp +++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp @@ -723,31 +723,80 @@ static unsigned getPrologueDeath(MachineFunction &MF, unsigned Reg) { return getKillRegState(LRKill); } +/// The register order of CSI list is controlled by getCalleeSavedRegs, and the +/// frame index is controlled by getCalleeSavedRegsForLayout. We produce a +/// vector of register pairs that need storing in the RegPairs argument. The +/// value 0 indicates no register needs storing in a paired slot. +static void +getCSRPairs(const std::vector &CSI, + const AArch64RegisterInfo *RegInfo, const MachineFunction *MF, + SmallVectorImpl> &RegPairs) { + const MCPhysReg *CSRegs = RegInfo->getCalleeSavedRegsForLayout(MF); + + auto CSIPos = CSI.begin(); + auto CSIEnd = CSI.end(); + // We assume that getCalleeSavedRegsForLayout has the same ordering as + // getCalleeSavedRegs. So we can advance CSI vector while advancing + // getCalleeSavedRegsForLayout. + for (unsigned i = 0; CSRegs[i]; i += 2) { + assert(CSRegs[i + 1] && "Odd number of callee-saved registers!"); + auto FirstPos = + std::find_if(CSIPos, CSIEnd, [&](const CalleeSavedInfo &I) { + return I.getReg() == CSRegs[i]; + }); + auto SecondPos = + std::find_if(CSIPos, CSIEnd, [&](const CalleeSavedInfo &I) { + return I.getReg() == CSRegs[i + 1]; + }); + if (FirstPos == CSIEnd && SecondPos == CSIEnd) + // Neither of these regs need storing. + continue; + + unsigned FirstReg = FirstPos == CSIEnd ? AArch64::XZR : CSRegs[i]; + unsigned SecondReg = SecondPos == CSIEnd ? AArch64::XZR : CSRegs[i + 1]; + + if (FirstPos != CSIEnd && SecondPos != CSIEnd) { + // GPRs and FPRs are saved in pairs of 64-bit regs. We expect the CSI + // list to come in sorted by frame index so that we can issue the store + // pair instructions directly. Assert if we see anything otherwise. + assert(FirstPos->getFrameIdx() + 1 == SecondPos->getFrameIdx() && + "Out of order callee saved regs!"); + } + + DEBUG(dbgs() << "CSR pair: (" << RegInfo->getName(FirstReg) << ", " + << RegInfo->getName(SecondReg) << ") -> fi#(" + << (FirstPos == CSIEnd ? SecondPos->getFrameIdx() + : FirstPos->getFrameIdx()) + << ")\n"); + + RegPairs.push_back(std::make_pair(FirstReg, SecondReg)); + CSIPos = SecondPos == CSIEnd ? FirstPos : SecondPos; + } +} + bool AArch64FrameLowering::spillCalleeSavedRegisters( MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, const std::vector &CSI, const TargetRegisterInfo *TRI) const { MachineFunction &MF = *MBB.getParent(); const TargetInstrInfo &TII = *MF.getSubtarget().getInstrInfo(); - unsigned Count = CSI.size(); DebugLoc DL; - assert((Count & 1) == 0 && "Odd number of callee-saved regs to spill!"); - - for (unsigned i = 0; i < Count; i += 2) { - unsigned idx = Count - i - 2; - unsigned Reg1 = CSI[idx].getReg(); - unsigned Reg2 = CSI[idx + 1].getReg(); - // GPRs and FPRs are saved in pairs of 64-bit regs. We expect the CSI - // list to come in sorted by frame index so that we can issue the store - // pair instructions directly. Assert if we see anything otherwise. - // + + const AArch64RegisterInfo *RegInfo = static_cast( + MF.getSubtarget().getRegisterInfo()); + SmallVector, 4> RegPairs; + + // First gather information about the paris we'd like to store. + getCSRPairs(CSI, RegInfo, &MF, RegPairs); + unsigned Count = RegPairs.size(); + + for (unsigned i = 0; i < Count; ++i) { + unsigned idx = Count - i - 1; + unsigned Reg1 = RegPairs[idx].first; + unsigned Reg2 = RegPairs[idx].second; // The order of the registers in the list is controlled by // getCalleeSavedRegs(), so they will always be in-order, as well. - assert(CSI[idx].getFrameIdx() + 1 == CSI[idx + 1].getFrameIdx() && - "Out of order callee saved regs!"); unsigned StrOpc; - assert((Count & 1) == 0 && "Odd number of callee-saved regs to spill!"); - assert((i & 1) == 0 && "Odd index for callee-saved reg spill!"); // Issue sequence of non-sp increment and pi sp spills for cs regs. The // first spill is a pre-increment that allocates the stack. // For example: @@ -780,7 +829,7 @@ bool AArch64FrameLowering::spillCalleeSavedRegisters( << ", " << CSI[idx + 1].getFrameIdx() << ")\n"); // Compute offset: i = 0 => offset = -Count; // i = 2 => offset = -(Count - 2) + Count = 2 = i; etc. - const int Offset = (i == 0) ? -Count : i; + const int Offset = (i == 0) ? -2 * Count : 2 * i; assert((Offset >= -64 && Offset <= 63) && "Offset out of bounds for STP immediate"); MachineInstrBuilder MIB = BuildMI(MBB, MI, DL, TII.get(StrOpc)); @@ -804,21 +853,22 @@ bool AArch64FrameLowering::restoreCalleeSavedRegisters( const TargetRegisterInfo *TRI) const { MachineFunction &MF = *MBB.getParent(); const TargetInstrInfo &TII = *MF.getSubtarget().getInstrInfo(); - unsigned Count = CSI.size(); DebugLoc DL; - assert((Count & 1) == 0 && "Odd number of callee-saved regs to spill!"); if (MI != MBB.end()) DL = MI->getDebugLoc(); - for (unsigned i = 0; i < Count; i += 2) { - unsigned Reg1 = CSI[i].getReg(); - unsigned Reg2 = CSI[i + 1].getReg(); - // GPRs and FPRs are saved in pairs of 64-bit regs. We expect the CSI - // list to come in sorted by frame index so that we can issue the store - // pair instructions directly. Assert if we see anything otherwise. - assert(CSI[i].getFrameIdx() + 1 == CSI[i + 1].getFrameIdx() && - "Out of order callee saved regs!"); + const AArch64RegisterInfo *RegInfo = static_cast( + MF.getSubtarget().getRegisterInfo()); + SmallVector, 4> RegPairs; + + // First gather information about the paris we'd like to store. + getCSRPairs(CSI, RegInfo, &MF, RegPairs); + unsigned Count = RegPairs.size(); + + for (unsigned i = 0; i < Count; ++i) { + unsigned Reg1 = RegPairs[i].first; + unsigned Reg2 = RegPairs[i].second; // Issue sequence of non-sp increment and sp-pi restores for cs regs. Only // the last load is sp-pi post-increment and de-allocates the stack: // For example: @@ -828,19 +878,17 @@ bool AArch64FrameLowering::restoreCalleeSavedRegisters( // Note: see comment in spillCalleeSavedRegisters() unsigned LdrOpc; - assert((Count & 1) == 0 && "Odd number of callee-saved regs to spill!"); - assert((i & 1) == 0 && "Odd index for callee-saved reg spill!"); if (AArch64::GPR64RegClass.contains(Reg1)) { assert(AArch64::GPR64RegClass.contains(Reg2) && "Expected GPR64 callee-saved register pair!"); - if (i == Count - 2) + if (i == Count - 1) LdrOpc = AArch64::LDPXpost; else LdrOpc = AArch64::LDPXi; } else if (AArch64::FPR64RegClass.contains(Reg1)) { assert(AArch64::FPR64RegClass.contains(Reg2) && "Expected FPR64 callee-saved register pair!"); - if (i == Count - 2) + if (i == Count - 1) LdrOpc = AArch64::LDPDpost; else LdrOpc = AArch64::LDPDi; @@ -852,7 +900,7 @@ bool AArch64FrameLowering::restoreCalleeSavedRegisters( // Compute offset: i = 0 => offset = Count - 2; i = 2 => offset = Count - 4; // etc. - const int Offset = (i == Count - 2) ? Count : Count - i - 2; + const int Offset = (i == Count - 1) ? 2 * Count : 2 * (Count - i - 1); assert((Offset >= -64 && Offset <= 63) && "Offset out of bounds for LDP immediate"); MachineInstrBuilder MIB = BuildMI(MBB, MI, DL, TII.get(LdrOpc)); @@ -903,7 +951,7 @@ void AArch64FrameLowering::determineCalleeSaves(MachineFunction &MF, bool ExtraCSSpill = false; bool CanEliminateFrame = true; DEBUG(dbgs() << "*** determineCalleeSaves\nUsed CSRs:"); - const MCPhysReg *CSRegs = RegInfo->getCalleeSavedRegs(&MF); + const MCPhysReg *CSRegs = RegInfo->getCalleeSavedRegsForLayout(&MF); // Check pairs of consecutive callee-saved registers. for (unsigned i = 0; CSRegs[i]; i += 2) { @@ -1010,3 +1058,61 @@ void AArch64FrameLowering::determineCalleeSaves(MachineFunction &MF, } } } + +bool AArch64FrameLowering::assignCalleeSavedSpillSlots( + MachineFunction &MF, const TargetRegisterInfo *TRI, + std::vector &CSI, + unsigned &MinCSFrameIndex, unsigned &MaxCSFrameIndex) const { + const AArch64RegisterInfo *RegInfo = static_cast( + MF.getSubtarget().getRegisterInfo()); + + if (!MF.getFunction()->getAttributes().hasAttrSomewhere( + Attribute::SwiftError)) + return false; + + if (CSI.empty()) + return true; // Early exit if no callee saved registers are modified! + + MachineFrameInfo *MFI = MF.getFrameInfo(); + + // Simplify the logic here since AArch64 does not have fixed or reserved + // spill slots. + + // Now that we know which registers need to be saved and restored, allocate + // stack slots according to getCalleeSavedRegsForLayout. + const MCPhysReg *CSRegs = RegInfo->getCalleeSavedRegsForLayout(&MF); + + for (unsigned i = 0; CSRegs[i]; ++i) { + unsigned Reg = CSRegs[i]; + + // Find the corresponding entry in CSI for Reg. + std::vector::iterator CSInfo, E; + for (CSInfo = CSI.begin(), E = CSI.end(); CSInfo != E; ++CSInfo) + if (CSInfo->getReg() == Reg) + break; + // Assign a slot for functions which call __builtin_unwind_init. + if (CSInfo == CSI.end() && !MF.getMMI().callsUnwindInit()) + continue; + + DEBUG(dbgs() << "try to allocate stack slot for reg " << Reg << '\n'); + const TargetRegisterClass *RC = RegInfo->getMinimalPhysRegClass(Reg); + + // Just spill it anywhere convenient. + unsigned Align = RC->getAlignment(); + unsigned StackAlign = getStackAlignment(); + + // We may not be able to satisfy the desired alignment specification of + // the TargetRegisterClass if the stack alignment is smaller. Use the + // min. + Align = std::min(Align, StackAlign); + int FrameIdx = MFI->CreateStackObject(RC->getSize(), Align, true); + if ((unsigned)FrameIdx < MinCSFrameIndex) MinCSFrameIndex = FrameIdx; + if ((unsigned)FrameIdx > MaxCSFrameIndex) MaxCSFrameIndex = FrameIdx; + + // Find the corresponding entry in CSI for Reg. + if (CSInfo != CSI.end()) + CSInfo->setFrameIdx(FrameIdx); + } + + return true; +} diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.h b/llvm/lib/Target/AArch64/AArch64FrameLowering.h index 16dab2f5754f3..1a847d22e51f4 100644 --- a/llvm/lib/Target/AArch64/AArch64FrameLowering.h +++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.h @@ -60,6 +60,14 @@ class AArch64FrameLowering : public TargetFrameLowering { void determineCalleeSaves(MachineFunction &MF, BitVector &SavedRegs, RegScavenger *RS) const override; + + + bool + assignCalleeSavedSpillSlots(MachineFunction &MF, + const TargetRegisterInfo *TRI, + std::vector &CSI, + unsigned &MinCSFrameIndex, + unsigned &MaxCSFrameIndex) const override; }; } // End llvm namespace diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.h b/llvm/lib/Target/AArch64/AArch64ISelLowering.h index 64a2934d1d52d..c7d3d7ff67b3f 100644 --- a/llvm/lib/Target/AArch64/AArch64ISelLowering.h +++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.h @@ -377,6 +377,10 @@ class AArch64TargetLowering : public TargetLowering { void addDRTypeForNEON(MVT VT); void addQRTypeForNEON(MVT VT); + bool supportSwiftError() const override { + return true; + } + SDValue LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv, bool isVarArg, const SmallVectorImpl &Ins, SDLoc DL, diff --git a/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp b/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp index 1aef31baad203..97dbbdcb82051 100644 --- a/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp +++ b/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp @@ -40,6 +40,23 @@ AArch64RegisterInfo::AArch64RegisterInfo(const Triple &TT) const MCPhysReg * AArch64RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const { assert(MF && "Invalid MachineFunction pointer."); + if (MF->getFunction()->getCallingConv() == CallingConv::GHC) + // GHC set of callee saved regs is empty as all those regs are + // used for passing STG regs around + return CSR_AArch64_NoRegs_SaveList; + if (MF->getFunction()->getCallingConv() == CallingConv::AnyReg) + return CSR_AArch64_AllRegs_SaveList; + if (MF->getFunction()->getAttributes().hasAttrSomewhere( + Attribute::SwiftError)) + return CSR_AArch64_AAPCS_SwiftError_SaveList; + else + return CSR_AArch64_AAPCS_SaveList; +} + +const MCPhysReg * +AArch64RegisterInfo::getCalleeSavedRegsForLayout( + const MachineFunction *MF) const { + assert(MF && "Invalid MachineFunction pointer."); if (MF->getFunction()->getCallingConv() == CallingConv::GHC) // GHC set of callee saved regs is empty as all those regs are // used for passing STG regs around @@ -58,6 +75,8 @@ AArch64RegisterInfo::getCallPreservedMask(const MachineFunction &MF, return CSR_AArch64_NoRegs_RegMask; if (CC == CallingConv::AnyReg) return CSR_AArch64_AllRegs_RegMask; + if (MF.getFunction()->getAttributes().hasAttrSomewhere(Attribute::SwiftError)) + return CSR_AArch64_AAPCS_SwiftError_RegMask; else return CSR_AArch64_AAPCS_RegMask; } diff --git a/llvm/lib/Target/AArch64/AArch64RegisterInfo.h b/llvm/lib/Target/AArch64/AArch64RegisterInfo.h index c01bfa5ea70b2..8b95e6c812fc3 100644 --- a/llvm/lib/Target/AArch64/AArch64RegisterInfo.h +++ b/llvm/lib/Target/AArch64/AArch64RegisterInfo.h @@ -60,6 +60,11 @@ struct AArch64RegisterInfo : public AArch64GenRegisterInfo { const uint32_t *getThisReturnPreservedMask(const MachineFunction &MF, CallingConv::ID) const; + /// Return callee-saved registers for stack layout purpose. When we use + /// SwiftError CSR, we still need to use the standard CSR for layout purpose, + /// since compact unwinding expects the layout according to standard CSR. + const MCPhysReg *getCalleeSavedRegsForLayout(const MachineFunction *MF) const; + BitVector getReservedRegs(const MachineFunction &MF) const override; const TargetRegisterClass * getPointerRegClass(const MachineFunction &MF, diff --git a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp index 419717c85a79d..9a53d92cf90a4 100644 --- a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp +++ b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp @@ -87,6 +87,10 @@ ARMBaseRegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const { } } + if (STI.isTargetDarwin() && + F->getAttributes().hasAttrSomewhere(Attribute::SwiftError)) + return CSR_iOS_SwiftError_SaveList; + return RegList; } @@ -97,6 +101,11 @@ ARMBaseRegisterInfo::getCallPreservedMask(const MachineFunction &MF, if (CC == CallingConv::GHC) // This is academic becase all GHC calls are (supposed to be) tail calls return CSR_NoRegs_RegMask; + + if (STI.isTargetDarwin() && + MF.getFunction()->getAttributes().hasAttrSomewhere(Attribute::SwiftError)) + return CSR_iOS_SwiftError_RegMask; + return STI.isTargetDarwin() ? CSR_iOS_RegMask : CSR_AAPCS_RegMask; } diff --git a/llvm/lib/Target/ARM/ARMCallingConv.td b/llvm/lib/Target/ARM/ARMCallingConv.td index 233516415149d..8a30a4aaf8ea9 100644 --- a/llvm/lib/Target/ARM/ARMCallingConv.td +++ b/llvm/lib/Target/ARM/ARMCallingConv.td @@ -23,6 +23,12 @@ def CC_ARM_APCS : CallingConv<[ CCIfType<[i1, i8, i16], CCPromoteToType>, + // An SwiftSelf is passed in R9. + CCIfSwiftSelf>>, + + // An SwiftError is passed in R6. + CCIfSwiftError>>, + // Handle all vector types as either f64 or v2f64. CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, @@ -42,6 +48,9 @@ def RetCC_ARM_APCS : CallingConv<[ CCIfType<[i1, i8, i16], CCPromoteToType>, CCIfType<[f32], CCBitConvertToType>, + // An SwiftError is returned in R6. + CCIfSwiftError>>, + // Handle all vector types as either f64 or v2f64. CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, @@ -151,6 +160,12 @@ def CC_ARM_AAPCS : CallingConv<[ CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, + // An SwiftSelf is passed in R9. + CCIfSwiftSelf>>, + + // An SwiftError is passed in R6. + CCIfSwiftError>>, + CCIfType<[f64, v2f64], CCCustom<"CC_ARM_AAPCS_Custom_f64">>, CCIfType<[f32], CCBitConvertToType>, CCDelegateTo @@ -161,6 +176,9 @@ def RetCC_ARM_AAPCS : CallingConv<[ CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, + // An SwiftError is returned in R6. + CCIfSwiftError>>, + CCIfType<[f64, v2f64], CCCustom<"RetCC_ARM_AAPCS_Custom_f64">>, CCIfType<[f32], CCBitConvertToType>, CCDelegateTo @@ -179,6 +197,12 @@ def CC_ARM_AAPCS_VFP : CallingConv<[ CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, + // An SwiftSelf is passed in R9. + CCIfSwiftSelf>>, + + // An SwiftError is passed in R6. + CCIfSwiftError>>, + // HFAs are passed in a contiguous block of registers, or on the stack CCIfConsecutiveRegs>, @@ -194,6 +218,9 @@ def RetCC_ARM_AAPCS_VFP : CallingConv<[ CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, + // An SwiftError is returned in R6. + CCIfSwiftError>>, + CCIfType<[v2f64], CCAssignToReg<[Q0, Q1, Q2, Q3]>>, CCIfType<[f64], CCAssignToReg<[D0, D1, D2, D3, D4, D5, D6, D7]>>, CCIfType<[f32], CCAssignToReg<[S0, S1, S2, S3, S4, S5, S6, S7, S8, @@ -222,6 +249,9 @@ def CSR_AAPCS_ThisReturn : CalleeSavedRegs<(add LR, R11, R10, R9, R8, R7, R6, // Also save R7-R4 first to match the stack frame fixed spill areas. def CSR_iOS : CalleeSavedRegs<(add LR, R7, R6, R5, R4, (sub CSR_AAPCS, R9))>; +// R6 is used to pass swifterror, remove it from CSR. +def CSR_iOS_SwiftError : CalleeSavedRegs<(sub CSR_iOS, R6)>; + def CSR_iOS_ThisReturn : CalleeSavedRegs<(add LR, R7, R6, R5, R4, (sub CSR_AAPCS_ThisReturn, R9))>; diff --git a/llvm/lib/Target/ARM/ARMFastISel.cpp b/llvm/lib/Target/ARM/ARMFastISel.cpp index 175107450fc0a..48fe89edb108e 100644 --- a/llvm/lib/Target/ARM/ARMFastISel.cpp +++ b/llvm/lib/Target/ARM/ARMFastISel.cpp @@ -1062,6 +1062,17 @@ bool ARMFastISel::SelectLoad(const Instruction *I) { if (cast(I)->isAtomic()) return false; + const Value *SV = I->getOperand(0); + if (const Argument *Arg = dyn_cast(SV)) { + if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) + return false; + } + + if (const AllocaInst *Alloca = dyn_cast(SV)) { + if (Alloca->isSwiftError() && TLI.supportSwiftError()) + return false; + } + // Verify we have a legal type before going any further. MVT VT; if (!isLoadTypeLegal(I->getType(), VT)) @@ -1177,6 +1188,17 @@ bool ARMFastISel::SelectStore(const Instruction *I) { if (cast(I)->isAtomic()) return false; + const Value *PtrV = I->getOperand(1); + if (const Argument *Arg = dyn_cast(PtrV)) { + if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) + return false; + } + + if (const AllocaInst *Alloca = dyn_cast(PtrV)) { + if (Alloca->isSwiftError() && TLI.supportSwiftError()) + return false; + } + // Verify we have a legal type before going any further. MVT VT; if (!isLoadTypeLegal(I->getOperand(0)->getType(), VT)) @@ -1839,6 +1861,7 @@ CCAssignFn *ARMFastISel::CCAssignFnForCall(CallingConv::ID CC, default: llvm_unreachable("Unsupported calling convention"); case CallingConv::Fast: + case CallingConv::Swift: if (Subtarget->hasVFP2() && !isVarArg) { if (!Subtarget->isAAPCS_ABI()) return (Return ? RetFastCC_ARM_APCS : FastCC_ARM_APCS); @@ -2083,6 +2106,10 @@ bool ARMFastISel::SelectRet(const Instruction *I) { if (!FuncInfo.CanLowerReturn) return false; + if (F.getAttributes().hasAttrSomewhere(Attribute::SwiftError) && + TLI.supportSwiftError()) + return false; + // Build a list of return value registers. SmallVector RetRegs; @@ -2342,6 +2369,8 @@ bool ARMFastISel::SelectCall(const Instruction *I, // FIXME: Only handle *easy* calls for now. if (CS.paramHasAttr(AttrInd, Attribute::InReg) || CS.paramHasAttr(AttrInd, Attribute::StructRet) || + CS.paramHasAttr(AttrInd, Attribute::SwiftSelf) || + CS.paramHasAttr(AttrInd, Attribute::SwiftError) || CS.paramHasAttr(AttrInd, Attribute::Nest) || CS.paramHasAttr(AttrInd, Attribute::ByVal)) return false; @@ -3003,6 +3032,7 @@ bool ARMFastISel::fastLowerArguments() { case CallingConv::ARM_AAPCS_VFP: case CallingConv::ARM_AAPCS: case CallingConv::ARM_APCS: + case CallingConv::Swift: break; } @@ -3016,6 +3046,8 @@ bool ARMFastISel::fastLowerArguments() { if (F->getAttributes().hasAttribute(Idx, Attribute::InReg) || F->getAttributes().hasAttribute(Idx, Attribute::StructRet) || + F->getAttributes().hasAttribute(Idx, Attribute::SwiftSelf) || + F->getAttributes().hasAttribute(Idx, Attribute::SwiftError) || F->getAttributes().hasAttribute(Idx, Attribute::ByVal)) return false; diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp index 992902d6f6eb1..5254e6183e628 100644 --- a/llvm/lib/Target/ARM/ARMISelLowering.cpp +++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp @@ -1380,6 +1380,7 @@ ARMTargetLowering::getEffectiveCallingConv(CallingConv::ID CC, else return CallingConv::ARM_AAPCS; case CallingConv::Fast: + case CallingConv::Swift: if (!Subtarget->isAAPCS_ABI()) { if (Subtarget->hasVFP2() && !Subtarget->isThumb1Only() && !isVarArg) return CallingConv::Fast; diff --git a/llvm/lib/Target/ARM/ARMISelLowering.h b/llvm/lib/Target/ARM/ARMISelLowering.h index 852a36b0c1212..4112b2a14c8d1 100644 --- a/llvm/lib/Target/ARM/ARMISelLowering.h +++ b/llvm/lib/Target/ARM/ARMISelLowering.h @@ -567,6 +567,10 @@ namespace llvm { SmallVectorImpl &InVals, bool isThisReturn, SDValue ThisVal) const; + bool supportSwiftError() const override { + return true; + } + SDValue LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv, bool isVarArg, diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp index 7a52a1c9eaecb..c6c5e6f2b5465 100644 --- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp +++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp @@ -1193,7 +1193,8 @@ static void dump_registers(BitVector &Regs, const TargetRegisterInfo &TRI) { bool HexagonFrameLowering::assignCalleeSavedSpillSlots(MachineFunction &MF, - const TargetRegisterInfo *TRI, std::vector &CSI) const { + const TargetRegisterInfo *TRI, std::vector &CSI, + unsigned &MinCSFrameIndex, unsigned &MaxCSFrameIndex) const { DEBUG(dbgs() << LLVM_FUNCTION_NAME << " on " << MF.getFunction()->getName() << '\n'); MachineFrameInfo *MFI = MF.getFrameInfo(); diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.h b/llvm/lib/Target/Hexagon/HexagonFrameLowering.h index 683b303d43ead..d9725c0240f0f 100644 --- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.h +++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.h @@ -70,7 +70,8 @@ class HexagonFrameLowering : public TargetFrameLowering { } bool assignCalleeSavedSpillSlots(MachineFunction &MF, - const TargetRegisterInfo *TRI, std::vector &CSI) + const TargetRegisterInfo *TRI, std::vector &CSI, + unsigned &MinCSFrameIndex, unsigned &MaxCSFrameIndex) const override; bool needsAligna(const MachineFunction &MF) const; diff --git a/llvm/lib/Target/X86/X86CallingConv.td b/llvm/lib/Target/X86/X86CallingConv.td index aaabf7a59f5c1..4e9c771c23be0 100644 --- a/llvm/lib/Target/X86/X86CallingConv.td +++ b/llvm/lib/Target/X86/X86CallingConv.td @@ -161,6 +161,9 @@ def RetCC_X86_64_C : CallingConv<[ // MMX vector types are always returned in XMM0. CCIfType<[x86mmx], CCAssignToReg<[XMM0, XMM1]>>, + + CCIfSwiftError>>, + CCDelegateTo ]>; @@ -191,6 +194,20 @@ def RetCC_X86_64_WebKit_JS : CallingConv<[ CCIfType<[i64], CCAssignToReg<[RAX]>> ]>; +def RetCC_X86_64_Swift : CallingConv<[ + CCIfType<[i8] , CCAssignToReg<[AL, DL, CL, R8B]>>, + CCIfType<[i16], CCAssignToReg<[AX, DX, CX, R8W]>>, + CCIfType<[i32], CCAssignToReg<[EAX, EDX, ECX, R8D]>>, + CCIfType<[i64], CCAssignToReg<[RAX, RDX, RCX, R8]>>, + + CCIfType<[f32], CCAssignToReg<[XMM0, XMM1, XMM2, XMM3]>>, + CCIfType<[f64], CCAssignToReg<[XMM0, XMM1, XMM2, XMM3]>>, + + // MMX vector types are always returned in XMM0. + CCIfType<[x86mmx], CCAssignToReg<[XMM0, XMM1, XMM2, XMM3]>>, + CCDelegateTo +]>; + // X86-64 AnyReg return-value convention. No explicit register is specified for // the return-value. The register allocator is allowed and expected to choose // any free register. @@ -233,6 +250,9 @@ def RetCC_X86_64 : CallingConv<[ CCIfCC<"CallingConv::WebKit_JS", CCDelegateTo>, CCIfCC<"CallingConv::AnyReg", CCDelegateTo>, + // Handle Swift calls. + CCIfCC<"CallingConv::Swift", CCDelegateTo>, + // Handle explicit CC selection CCIfCC<"CallingConv::X86_64_Win64", CCDelegateTo>, CCIfCC<"CallingConv::X86_64_SysV", CCDelegateTo>, @@ -272,6 +292,12 @@ def CC_X86_64_C : CallingConv<[ CCIfNest>>, CCIfNest>, + // An SwiftSelf is passed in R10. + CCIfSwiftSelf>>, + + // An SwiftError is passed in R12. + CCIfSwiftError>>, + // The first 6 integer arguments are passed in integer registers. CCIfType<[i32], CCAssignToReg<[EDI, ESI, EDX, ECX, R8D, R9D]>>, CCIfType<[i64], CCAssignToReg<[RDI, RSI, RDX, RCX, R8 , R9 ]>>, @@ -790,6 +816,8 @@ def CSR_NoRegs : CalleeSavedRegs<(add)>; def CSR_32 : CalleeSavedRegs<(add ESI, EDI, EBX, EBP)>; def CSR_64 : CalleeSavedRegs<(add RBX, R12, R13, R14, R15, RBP)>; +def CSR_64_SwiftError : CalleeSavedRegs<(add RBX, R13, R14, R15, RBP)>; + def CSR_32EHRet : CalleeSavedRegs<(add EAX, EDX, CSR_32)>; def CSR_64EHRet : CalleeSavedRegs<(add RAX, RDX, CSR_64)>; diff --git a/llvm/lib/Target/X86/X86FastISel.cpp b/llvm/lib/Target/X86/X86FastISel.cpp index 914fd04ad6b7f..efcd18fbf1d4f 100644 --- a/llvm/lib/Target/X86/X86FastISel.cpp +++ b/llvm/lib/Target/X86/X86FastISel.cpp @@ -972,6 +972,17 @@ bool X86FastISel::X86SelectStore(const Instruction *I) { if (S->isAtomic()) return false; + const Value *PtrV = I->getOperand(1); + if (const Argument *Arg = dyn_cast(PtrV)) { + if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) + return false; + } + + if (const AllocaInst *Alloca = dyn_cast(PtrV)) { + if (Alloca->isSwiftError() && TLI.supportSwiftError()) + return false; + } + const Value *Val = S->getValueOperand(); const Value *Ptr = S->getPointerOperand(); @@ -1002,6 +1013,10 @@ bool X86FastISel::X86SelectRet(const Instruction *I) { if (!FuncInfo.CanLowerReturn) return false; + if (F.getAttributes().hasAttrSomewhere(Attribute::SwiftError) && + TLI.supportSwiftError()) + return false; + CallingConv::ID CC = F.getCallingConv(); if (CC != CallingConv::C && CC != CallingConv::Fast && @@ -1131,6 +1146,17 @@ bool X86FastISel::X86SelectLoad(const Instruction *I) { if (LI->isAtomic()) return false; + const Value *SV = I->getOperand(0); + if (const Argument *Arg = dyn_cast(SV)) { + if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) + return false; + } + + if (const AllocaInst *Alloca = dyn_cast(SV)) { + if (Alloca->isSwiftError() && TLI.supportSwiftError()) + return false; + } + MVT VT; if (!isTypeLegal(LI->getType(), VT, /*AllowI1=*/true)) return false; @@ -2740,6 +2766,8 @@ bool X86FastISel::fastLowerArguments() { if (F->getAttributes().hasAttribute(Idx, Attribute::ByVal) || F->getAttributes().hasAttribute(Idx, Attribute::InReg) || F->getAttributes().hasAttribute(Idx, Attribute::StructRet) || + F->getAttributes().hasAttribute(Idx, Attribute::SwiftSelf) || + F->getAttributes().hasAttribute(Idx, Attribute::SwiftError) || F->getAttributes().hasAttribute(Idx, Attribute::Nest)) return false; @@ -2847,6 +2875,7 @@ bool X86FastISel::fastLowerCall(CallLoweringInfo &CLI) { case CallingConv::C: case CallingConv::Fast: case CallingConv::WebKit_JS: + case CallingConv::Swift: case CallingConv::X86_FastCall: case CallingConv::X86_64_Win64: case CallingConv::X86_64_SysV: @@ -2871,6 +2900,10 @@ bool X86FastISel::fastLowerCall(CallLoweringInfo &CLI) { if (CLI.CS && CLI.CS->hasInAllocaArgument()) return false; + for (auto Flag : CLI.OutFlags) + if (Flag.isSwiftError()) + return false; + // Fast-isel doesn't know about callee-pop yet. if (X86::isCalleePop(CC, Subtarget->is64Bit(), IsVarArg, TM.Options.GuaranteedTailCallOpt)) diff --git a/llvm/lib/Target/X86/X86FrameLowering.cpp b/llvm/lib/Target/X86/X86FrameLowering.cpp index ad83344b32762..60e8f41099ede 100644 --- a/llvm/lib/Target/X86/X86FrameLowering.cpp +++ b/llvm/lib/Target/X86/X86FrameLowering.cpp @@ -1441,7 +1441,8 @@ int X86FrameLowering::getFrameIndexReferenceFromSP(const MachineFunction &MF, bool X86FrameLowering::assignCalleeSavedSpillSlots( MachineFunction &MF, const TargetRegisterInfo *TRI, - std::vector &CSI) const { + std::vector &CSI, + unsigned &MinCSFrameIndex, unsigned &MaxCSFrameIndex) const { MachineFrameInfo *MFI = MF.getFrameInfo(); X86MachineFunctionInfo *X86FI = MF.getInfo(); diff --git a/llvm/lib/Target/X86/X86FrameLowering.h b/llvm/lib/Target/X86/X86FrameLowering.h index 35bafb532b41a..ec3e5ed6e0cb0 100644 --- a/llvm/lib/Target/X86/X86FrameLowering.h +++ b/llvm/lib/Target/X86/X86FrameLowering.h @@ -74,7 +74,9 @@ class X86FrameLowering : public TargetFrameLowering { bool assignCalleeSavedSpillSlots(MachineFunction &MF, const TargetRegisterInfo *TRI, - std::vector &CSI) const override; + std::vector &CSI, + unsigned &MinCSFrameIndex, + unsigned &MaxCSFrameIndex) const override; bool spillCalleeSavedRegisters(MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp index d13d0510a0c9a..27c6b50b9bdae 100644 --- a/llvm/lib/Target/X86/X86ISelLowering.cpp +++ b/llvm/lib/Target/X86/X86ISelLowering.cpp @@ -2248,7 +2248,7 @@ X86TargetLowering::LowerReturn(SDValue Chain, // false, then an sret argument may be implicitly inserted in the SelDAG. In // either case FuncInfo->setSRetReturnReg() will have been called. if (unsigned SRetReg = FuncInfo->getSRetReturnReg()) { - SDValue Val = DAG.getCopyFromReg(Chain, dl, SRetReg, + SDValue Val = DAG.getCopyFromReg(RetOps[0], dl, SRetReg, getPointerTy(MF.getDataLayout())); unsigned RetValReg diff --git a/llvm/lib/Target/X86/X86ISelLowering.h b/llvm/lib/Target/X86/X86ISelLowering.h index 714e6b1bd345b..bfc3c4b9abd33 100644 --- a/llvm/lib/Target/X86/X86ISelLowering.h +++ b/llvm/lib/Target/X86/X86ISelLowering.h @@ -1029,6 +1029,10 @@ namespace llvm { SDValue LowerGC_TRANSITION_START(SDValue Op, SelectionDAG &DAG) const; SDValue LowerGC_TRANSITION_END(SDValue Op, SelectionDAG &DAG) const; + bool supportSwiftError() const override { + return true; + } + SDValue LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv, bool isVarArg, diff --git a/llvm/lib/Target/X86/X86RegisterInfo.cpp b/llvm/lib/Target/X86/X86RegisterInfo.cpp index b1147877753d6..3f4e8464227b6 100644 --- a/llvm/lib/Target/X86/X86RegisterInfo.cpp +++ b/llvm/lib/Target/X86/X86RegisterInfo.cpp @@ -277,6 +277,9 @@ X86RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const { return CSR_Win64_SaveList; if (CallsEHReturn) return CSR_64EHRet_SaveList; + if (MF->getFunction()->getAttributes().hasAttrSomewhere( + Attribute::SwiftError)) + return CSR_64_SwiftError_SaveList; return CSR_64_SaveList; } if (CallsEHReturn) @@ -337,6 +340,9 @@ X86RegisterInfo::getCallPreservedMask(const MachineFunction &MF, if (Is64Bit) { if (IsWin64) return CSR_Win64_RegMask; + if (MF.getFunction()->getAttributes().hasAttrSomewhere( + Attribute::SwiftError)) + return CSR_64_SwiftError_RegMask; return CSR_64_RegMask; } return CSR_32_RegMask; diff --git a/llvm/lib/Transforms/Utils/SSAUpdater.cpp b/llvm/lib/Transforms/Utils/SSAUpdater.cpp index 88b39dd7f6640..8eea38e11fb6b 100644 --- a/llvm/lib/Transforms/Utils/SSAUpdater.cpp +++ b/llvm/lib/Transforms/Utils/SSAUpdater.cpp @@ -216,6 +216,11 @@ class SSAUpdaterTraits { static BlkSucc_iterator BlkSucc_begin(BlkT *BB) { return succ_begin(BB); } static BlkSucc_iterator BlkSucc_end(BlkT *BB) { return succ_end(BB); } + /// Iterator over phis in a block. + typedef BlkT::iterator PhiItT; + static PhiItT PhiItT_begin(BlkT *BB) { return BB->begin(); } + static PhiItT PhiItT_end(BlkT *BB) { return BB->end(); } + class PHI_iterator { private: PHINode *PHI; diff --git a/llvm/test/Bitcode/swifterror.ll b/llvm/test/Bitcode/swifterror.ll new file mode 100644 index 0000000000000..ac2a657368bd8 --- /dev/null +++ b/llvm/test/Bitcode/swifterror.ll @@ -0,0 +1,8 @@ +; RUN: llvm-as < %s | llvm-dis | FileCheck %s +; RUN: verify-uselistorder < %s + +define i32 @test(i8** swifterror) +; CHECK: define i32 @test(i8** swifterror) +{ + ret i32 0 +} diff --git a/llvm/test/Bitcode/swiftself.ll b/llvm/test/Bitcode/swiftself.ll new file mode 100644 index 0000000000000..e9293ec98b3dd --- /dev/null +++ b/llvm/test/Bitcode/swiftself.ll @@ -0,0 +1,8 @@ +; RUN: llvm-as < %s | llvm-dis | FileCheck %s +; RUN: verify-uselistorder < %s + +define void @test(i8* swiftself) +; CHECK: define void @test(i8* swiftself) +{ + ret void; +} diff --git a/llvm/test/CodeGen/AArch64/swifterror.ll b/llvm/test/CodeGen/AArch64/swifterror.ll new file mode 100644 index 0000000000000..0106bb30a3d0b --- /dev/null +++ b/llvm/test/CodeGen/AArch64/swifterror.ll @@ -0,0 +1,371 @@ +; RUN: llc -verify-machineinstrs < %s -mtriple=aarch64-apple-ios | FileCheck --check-prefix=CHECK-APPLE %s +; RUN: llc -verify-machineinstrs -O0 < %s -mtriple=aarch64-apple-ios | FileCheck --check-prefix=CHECK-O0 %s + +declare i8* @malloc(i64) +declare void @free(i8*) +%swift_error = type {i64, i8} + +define float @foo(%swift_error** swifterror %error_ptr_ref) { +; CHECK-APPLE-LABEL: foo: +; CHECK-APPLE: orr w0, wzr, #0x10 +; CHECK-APPLE: malloc +; CHECK-APPLE: orr [[ID:w[0-9]+]], wzr, #0x1 +; CHECK-APPLE: strb [[ID]], [x0, #8] +; CHECK-APPLE: mov x19, x0 +; CHECK-APPLE-NOT: x19 + +; CHECK-O0-LABEL: foo: +; CHECK-O0: orr w{{.*}}, wzr, #0x10 +; CHECK-O0: malloc +; CHECK-O0: mov [[ID2:x[0-9]+]], x0 +; CHECK-O0: orr [[ID:w[0-9]+]], wzr, #0x1 +; CHECK-O0: strb [[ID]], [x0, #8] +; CHECK-O0: mov x19, [[ID2]] +; CHECK-O0-NOT: x19 +entry: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + ret float 1.0 +} + +define float @caller(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller: +; CHECK-APPLE: mov [[ID:x[0-9]+]], x0 +; CHECK-APPLE: mov x19, xzr +; CHECK-APPLE: bl {{.*}}foo +; CHECK-APPLE: cbnz x19 +; Access part of the error object and save it to error_ref +; CHECK-APPLE: ldrb [[CODE:w[0-9]+]], [x19, #8] +; CHECK-APPLE: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK-APPLE: mov x0, x19 +; CHECK_APPLE: bl {{.*}}free + +; CHECK-O0-LABEL: caller: +; CHECK-O0: mov x19 +; CHECK-O0: bl {{.*}}foo +; CHECK-O0: mov [[ID:x[0-9]+]], x19 +; CHECK-O0: cbnz [[ID]] +entry: + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + %call = call float @foo(%swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} + +define float @caller2(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller2: +; CHECK-APPLE: mov [[ID:x[0-9]+]], x0 +; CHECK-APPLE: fmov [[CMP:s[0-9]+]], #1.0 +; CHECK-APPLE: mov x19, xzr +; CHECK-APPLE: bl {{.*}}foo +; CHECK-APPLE: cbnz x19 +; CHECK-APPLE: fcmp s0, [[CMP]] +; CHECK-APPLE: b.le +; Access part of the error object and save it to error_ref +; CHECK-APPLE: ldrb [[CODE:w[0-9]+]], [x19, #8] +; CHECK-APPLE: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK-APPLE: mov x0, x19 +; CHECK_APPLE: bl {{.*}}free + +; CHECK-O0-LABEL: caller2: +; CHECK-O0: mov x19 +; CHECK-O0: bl {{.*}}foo +; CHECK-O0: mov [[ID:x[0-9]+]], x19 +; CHECK-O0: cbnz [[ID]] +entry: + %error_ptr_ref = alloca swifterror %swift_error* + br label %bb_loop +bb_loop: + store %swift_error* null, %swift_error** %error_ptr_ref + %call = call float @foo(%swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %cmp = fcmp ogt float %call, 1.000000e+00 + br i1 %cmp, label %bb_end, label %bb_loop +bb_end: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} + +define float @foo_if(%swift_error** swifterror %error_ptr_ref, i32 %cc) { +; CHECK-APPLE-LABEL: foo_if: +; CHECK-APPLE: cbz w0 +; CHECK-APPLE: orr w0, wzr, #0x10 +; CHECK-APPLE: malloc +; CHECK-APPLE: orr [[ID:w[0-9]+]], wzr, #0x1 +; CHECK-APPLE: strb [[ID]], [x0, #8] +; CHECK-APPLE: mov x19, x0 +; CHECK-APPLE-NOT: x19 +; CHECK-APPLE: ret + +; CHECK-O0-LABEL: foo_if: +; spill x19 +; CHECK-O0: str x19 +; CHECK-O0: cbz w0 +; CHECK-O0: orr w{{.*}}, wzr, #0x10 +; CHECK-O0: malloc +; CHECK-O0: mov [[ID:x[0-9]+]], x0 +; CHECK-O0: orr [[ID2:w[0-9]+]], wzr, #0x1 +; CHECK-O0: strb [[ID2]], [x0, #8] +; CHECK-O0: mov x19, [[ID]] +; CHECK-O0: ret +; reload from stack +; CHECK-O0: ldr x19 +; CHECK-O0: ret +entry: + %cond = icmp ne i32 %cc, 0 + br i1 %cond, label %gen_error, label %normal + +gen_error: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + ret float 1.0 + +normal: + ret float 0.0 +} + +define float @foo_loop(%swift_error** swifterror %error_ptr_ref, i32 %cc, float %cc2) { +; CHECK-APPLE-LABEL: foo_loop: +; CHECK-APPLE: mov x0, x19 +; CHECK-APPLE: cbz +; CHECK-APPLE: orr w0, wzr, #0x10 +; CHECK-APPLE: malloc +; CHECK-APPLE: strb w{{.*}}, [x0, #8] +; CHECK-APPLE: fcmp +; CHECK-APPLE: b.le +; CHECK-APPLE: mov x19, x0 +; CHECK-APPLE: ret + +; CHECK-O0-LABEL: foo_loop: +; spill x19 +; CHECK-O0: str x19 +; CHECk-O0: cbz +; CHECK-O0: orr w{{.*}}, wzr, #0x10 +; CHECK-O0: malloc +; CHECK-O0: mov [[ID:x[0-9]+]], x0 +; CHECK-O0: strb w{{.*}}, [{{.*}}[[ID]], #8] +; spill x0 +; CHECK-O0: str x0 +; CHECK-O0: fcmp +; CHECK-O0: b.le +; reload from stack +; CHECK-O0: ldr x19 +; CHECK-O0: ret +entry: + br label %bb_loop + +bb_loop: + %cond = icmp ne i32 %cc, 0 + br i1 %cond, label %gen_error, label %bb_cont + +gen_error: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + br label %bb_cont + +bb_cont: + %cmp = fcmp ogt float %cc2, 1.000000e+00 + br i1 %cmp, label %bb_end, label %bb_loop +bb_end: + ret float 0.0 +} + +%struct.S = type { i32, i32, i32, i32, i32, i32 } + +define void @foo_sret(%struct.S* sret %agg.result, i32 %val1, %swift_error** swifterror %error_ptr_ref) { +; CHECK-APPLE-LABEL: foo_sret: +; CHECK-APPLE: mov [[SRET:x[0-9]+]], x8 +; CHECK-APPLE: orr w0, wzr, #0x10 +; CHECK-APPLE: malloc +; CHECK-APPLE: orr [[ID:w[0-9]+]], wzr, #0x1 +; CHECK-APPLE: strb [[ID]], [x0, #8] +; CHECK-APPLE: str w{{.*}}, [{{.*}}[[SRET]], #4] +; CHECK-APPLE: mov x19, x0 +; CHECK-APPLE-NOT: x19 + +; CHECK-O0-LABEL: foo_sret: +; CHECK-O0: orr w{{.*}}, wzr, #0x10 +; spill x8 +; CHECK-O0-DAG: str x8 +; spill x19 +; CHECK-O0-DAG: str x19 +; CHECK-O0: malloc +; CHECK-O0: orr [[ID:w[0-9]+]], wzr, #0x1 +; CHECK-O0: strb [[ID]], [x0, #8] +; reload from stack +; CHECK-O0: ldr [[SRET:x[0-9]+]] +; CHECK-O0: str w{{.*}}, [{{.*}}[[SRET]], #4] +; CHECK-O0: mov x19 +; CHECK-O0-NOT: x19 +entry: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + %v2 = getelementptr inbounds %struct.S, %struct.S* %agg.result, i32 0, i32 1 + store i32 %val1, i32* %v2 + ret void +} + +define float @caller3(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller3: +; CHECK-APPLE: mov [[ID:x[0-9]+]], x0 +; CHECK-APPLE: mov x19, xzr +; CHECK-APPLE: bl {{.*}}foo_sret +; CHECK-APPLE: cbnz x19 +; Access part of the error object and save it to error_ref +; CHECK-APPLE: ldrb [[CODE:w[0-9]+]], [x19, #8] +; CHECK-APPLE: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK-APPLE: mov x0, x19 +; CHECK_APPLE: bl {{.*}}free + +; CHECK-O0-LABEL: caller3: +; spill x0 +; CHECK-O0: str x0 +; CHECK-O0: mov x19 +; CHECK-O0: bl {{.*}}foo_sret +; CHECK-O0: mov [[ID2:x[0-9]+]], x19 +; CHECK-O0: cbnz [[ID2]] +; Access part of the error object and save it to error_ref +; reload from stack +; CHECK-O0: ldrb [[CODE:w[0-9]+]] +; CHECK-O0: ldr [[ID:x[0-9]+]] +; CHECK-O0: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK_O0: bl {{.*}}free +entry: + %s = alloca %struct.S, align 8 + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + call void @foo_sret(%struct.S* sret %s, i32 1, %swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} + +declare void @llvm.va_start(i8*) nounwind +define float @foo_vararg(%swift_error** swifterror %error_ptr_ref, ...) { +; CHECK-APPLE-LABEL: foo_vararg: +; CHECK-APPLE: orr w0, wzr, #0x10 +; CHECK-APPLE: malloc +; CHECK-APPLE: orr [[ID:w[0-9]+]], wzr, #0x1 +; CHECK-APPLE: add [[ARGS:x[0-9]+]], [[TMP:x[0-9]+]], #16 +; CHECK-APPLE: strb [[ID]], [x0, #8] + +; First vararg +; CHECK-APPLE-DAG: orr {{x[0-9]+}}, [[ARGS]], #0x8 +; CHECK-APPLE-DAG: ldr {{w[0-9]+}}, [{{.*}}[[TMP]], #16] +; CHECK-APPLE: add {{x[0-9]+}}, {{x[0-9]+}}, #8 +; Second vararg +; CHECK-APPLE: ldr {{w[0-9]+}}, [{{x[0-9]+}}] +; CHECK-APPLE: add {{x[0-9]+}}, {{x[0-9]+}}, #8 +; Third vararg +; CHECK-APPLE: ldr {{w[0-9]+}}, [{{x[0-9]+}}] + +; CHECK-APPLE: mov x19, x0 +; CHECK-APPLE-NOT: x19 +entry: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + + %args = alloca i8*, align 8 + %a10 = alloca i32, align 4 + %a11 = alloca i32, align 4 + %a12 = alloca i32, align 4 + %v10 = bitcast i8** %args to i8* + call void @llvm.va_start(i8* %v10) + %v11 = va_arg i8** %args, i32 + store i32 %v11, i32* %a10, align 4 + %v12 = va_arg i8** %args, i32 + store i32 %v12, i32* %a11, align 4 + %v13 = va_arg i8** %args, i32 + store i32 %v13, i32* %a12, align 4 + + ret float 1.0 +} + +define float @caller4(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller4: + +; CHECK-APPLE: mov [[ID:x[0-9]+]], x0 +; CHECK-APPLE: stp {{x[0-9]+}}, {{x[0-9]+}}, [sp, #8] +; CHECK-APPLE: str {{x[0-9]+}}, [sp] + +; CHECK-APPLE: mov x19, xzr +; CHECK-APPLE: bl {{.*}}foo_vararg +; CHECK-APPLE: cbnz x19 +; Access part of the error object and save it to error_ref +; CHECK-APPLE: ldrb [[CODE:w[0-9]+]], [x19, #8] +; CHECK-APPLE: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK-APPLE: mov x0, x19 +; CHECK_APPLE: bl {{.*}}free +entry: + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + + %a10 = alloca i32, align 4 + %a11 = alloca i32, align 4 + %a12 = alloca i32, align 4 + store i32 10, i32* %a10, align 4 + store i32 11, i32* %a11, align 4 + store i32 12, i32* %a12, align 4 + %v10 = load i32, i32* %a10, align 4 + %v11 = load i32, i32* %a11, align 4 + %v12 = load i32, i32* %a12, align 4 + + %call = call float (%swift_error**, ...) @foo_vararg(%swift_error** swifterror %error_ptr_ref, i32 %v10, i32 %v11, i32 %v12) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont + +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} diff --git a/llvm/test/CodeGen/AArch64/swiftself.ll b/llvm/test/CodeGen/AArch64/swiftself.ll new file mode 100644 index 0000000000000..f93f8f3982259 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/swiftself.ll @@ -0,0 +1,29 @@ +; RUN: llc -verify-machineinstrs < %s -mtriple=aarch64-apple-ios | FileCheck --check-prefix=CHECK-APPLE %s +; RUN: llc -O0 -verify-machineinstrs < %s -mtriple=aarch64-apple-ios | FileCheck --check-prefix=CHECK-O0 %s + +; Parameter with swiftself should be allocated to x9. +define void @check_swiftself(i32* swiftself %addr0) { +; CHECK-APPLE-LABEL: check_swiftself: +; CHECK-O0-LABEL: check_swiftself: + + %val0 = load volatile i32, i32* %addr0 +; CHECK-APPLE: ldr w{{.*}}, [x9] +; CHECK-O0: ldr w{{.*}}, [x9] + ret void +} + +@var8_3 = global i8 0 +declare void @take_swiftself(i8* swiftself %addr0) + +define void @simple_args() { +; CHECK-APPLE-LABEL: simple_args: +; CHECK-O0-LABEL: simple_args: + + call void @take_swiftself(i8* @var8_3) +; CHECK-APPLE: add x9, +; CHECK-APPLE: bl {{_?}}take_swiftself +; CHECK-O0: add x9, +; CHECK-O0: bl {{_?}}take_swiftself + + ret void +} diff --git a/llvm/test/CodeGen/ARM/swift-ios.ll b/llvm/test/CodeGen/ARM/swift-ios.ll new file mode 100644 index 0000000000000..8ff75cd52b595 --- /dev/null +++ b/llvm/test/CodeGen/ARM/swift-ios.ll @@ -0,0 +1,73 @@ +; RUN: llc -mtriple=armv7-apple-ios < %s | FileCheck %s + +define swiftcc float @t1(float %a, float %b) { +entry: +; CHECK: t1 +; CHECK-NOT: vmov +; CHECK: vadd.f32 + %a.addr = alloca float, align 4 + %b.addr = alloca float, align 4 + store float %a, float* %a.addr, align 4 + store float %b, float* %b.addr, align 4 + %0 = load float, float* %a.addr, align 4 + %1 = load float, float* %b.addr, align 4 + %add = fadd float %0, %1 + ret float %add +} + +define swiftcc double @t2(double %a, double %b) { +entry: +; CHECK: t2 +; CHECK-NOT: vmov +; CHECK: vadd.f64 + %a.addr = alloca double, align 8 + %b.addr = alloca double, align 8 + store double %a, double* %a.addr, align 8 + store double %b, double* %b.addr, align 8 + %0 = load double, double* %a.addr, align 8 + %1 = load double, double* %b.addr, align 8 + %add = fadd double %0, %1 + ret double %add +} + +define swiftcc double @t9(double %d0, double %d1, double %d2, double %d3, double %d4, double %d5, double %d6, double %d7, float %a, float %b) { +entry: +; CHECK-LABEL: t9: +; CHECK-NOT: vmov +; CHECK: vldr + %add = fadd float %a, %b + %conv = fpext float %add to double + ret double %conv +} + +define swiftcc double @t10(double %d0, double %d1, double %d2, double %d3, double %d4, double %d5, double %a, float %b, double %c) { +entry: +; CHECK-LABEL: t10: +; CHECK-NOT: vmov +; CHECK: vldr + %add = fadd double %a, %c + ret double %add +} + +define swiftcc float @t11(double %d0, double %d1, double %d2, double %d3, double %d4, double %d5, double %d6, float %a, double %b, float %c) { +entry: +; CHECK-LABEL: t11: +; CHECK: vldr + %add = fadd float %a, %c + ret float %add +} + +define swiftcc double @t12(double %a, double %b) { +entry: +; CHECK-LABEL: t12: +; CHECK: vstr + %add = fadd double %a, %b + %sub = fsub double %a, %b + %call = tail call swiftcc double @x(double 0.000000e+00, double 0.000000e+00, double 0.000000e+00, double 0.000000e+00, double 0.000000e+00, double 0.000000e+00, double %add, float 0.000000e+00, double %sub) + ret double %call +} + +declare swiftcc double @x(double, double, double, double, double, double, double, float, double) + +attributes #0 = { readnone } +attributes #1 = { readonly } diff --git a/llvm/test/CodeGen/ARM/swift-return.ll b/llvm/test/CodeGen/ARM/swift-return.ll new file mode 100644 index 0000000000000..c307f0e3f5112 --- /dev/null +++ b/llvm/test/CodeGen/ARM/swift-return.ll @@ -0,0 +1,130 @@ +; RUN: llc -mtriple=armv7k-apple-ios8.0 -mcpu=cortex-a7 -verify-machineinstrs < %s | FileCheck %s +; RUN: llc -mtriple=armv7k-apple-ios8.0 -mcpu=cortex-a7 -verify-machineinstrs < %s -O0 | FileCheck --check-prefix=CHECK-O0 %s + +; RUN: llc -mtriple=armv7-apple-ios -verify-machineinstrs < %s | FileCheck %s +; RUN: llc -mtriple=armv7-apple-ios -verify-machineinstrs < %s -O0 | FileCheck --check-prefix=CHECK-O0 %s + +; Test how llvm handles return type of {i16, i8}. The return value will be passed in %r0 and %r1. +; CHECK-LABEL: test: +; CHECK: bl {{.*}}gen +; CHECK: sxth {{.*}}, r0 +; CHECK: sxtab r0, {{.*}}, r1 +; CHECK-O0-LABEL: test: +; CHECK-O0: bl {{.*}}gen +; CHECK-O0: sxth r0, r0 +; CHECK-O0: sxtb r1, r1 +; CHECK-O0: add r0, r0, r1 +define i16 @test(i32 %key) { +entry: + %key.addr = alloca i32, align 4 + store i32 %key, i32* %key.addr, align 4 + %0 = load i32, i32* %key.addr, align 4 + %call = call swiftcc { i16, i8 } @gen(i32 %0) + %v3 = extractvalue { i16, i8 } %call, 0 + %v1 = sext i16 %v3 to i32 + %v5 = extractvalue { i16, i8 } %call, 1 + %v2 = sext i8 %v5 to i32 + %add = add nsw i32 %v1, %v2 + %conv = trunc i32 %add to i16 + ret i16 %conv +} + +declare swiftcc { i16, i8 } @gen(i32) + +; We can't pass every return value in register, instead, pass everything in memroy. +; The caller provides space for the return value and passes the address in %r0. +; The first input argument will be in %r1. +; CHECK-LABEL: test2: +; CHECK: mov r1, r0 +; CHECK: mov r0, sp +; CHECK: bl {{.*}}gen2 +; CHECK-DAG: add +; CHECK-DAG: ldr {{.*}}, [sp, #16] +; CHECK-DAG: add +; CHECK-DAG: add +; CHECK-DAG: add +; CHECK-O0-LABEL: test2: +; CHECK-O0: str r0 +; CHECK-O0: mov r0, sp +; CHECK-O0: bl {{.*}}gen2 +; CHECK-O0-DAG: ldr {{.*}}, [sp] +; CHECK-O0-DAG: ldr {{.*}}, [sp, #4] +; CHECK-O0-DAG: ldr {{.*}}, [sp, #8] +; CHECK-O0-DAG: ldr {{.*}}, [sp, #12] +; CHECK-O0-DAG: ldr {{.*}}, [sp, #16] +; CHECK-O0-DAG: add +; CHECK-O0-DAG: add +; CHECK-O0-DAG: add +; CHECK-O0-DAG: add +define i32 @test2(i32 %key) #0 { +entry: + %key.addr = alloca i32, align 4 + store i32 %key, i32* %key.addr, align 4 + %0 = load i32, i32* %key.addr, align 4 + %call = call swiftcc { i32, i32, i32, i32, i32 } @gen2(i32 %0) + + %v3 = extractvalue { i32, i32, i32, i32, i32 } %call, 0 + %v5 = extractvalue { i32, i32, i32, i32, i32 } %call, 1 + %v6 = extractvalue { i32, i32, i32, i32, i32 } %call, 2 + %v7 = extractvalue { i32, i32, i32, i32, i32 } %call, 3 + %v8 = extractvalue { i32, i32, i32, i32, i32 } %call, 4 + + %add = add nsw i32 %v3, %v5 + %add1 = add nsw i32 %add, %v6 + %add2 = add nsw i32 %add1, %v7 + %add3 = add nsw i32 %add2, %v8 + ret i32 %add3 +} + +; The address of the return value is passed in %r0. +; CHECK-LABEL: gen2: +; CHECK-DAG: str r1, [r0] +; CHECK-DAG: str r1, [r0, #4] +; CHECK-DAG: str r1, [r0, #8] +; CHECK-DAG: str r1, [r0, #12] +; CHECK-DAG: str r1, [r0, #16] +; CHECK-O0-LABEL: gen2: +; CHECK-O0-DAG: str r1, [r0] +; CHECK-O0-DAG: str r1, [r0, #4] +; CHECK-O0-DAG: str r1, [r0, #8] +; CHECK-O0-DAG: str r1, [r0, #12] +; CHECK-O0-DAG: str r1, [r0, #16] +define swiftcc { i32, i32, i32, i32, i32 } @gen2(i32 %key) { + %Y = insertvalue { i32, i32, i32, i32, i32 } undef, i32 %key, 0 + %Z = insertvalue { i32, i32, i32, i32, i32 } %Y, i32 %key, 1 + %Z2 = insertvalue { i32, i32, i32, i32, i32 } %Z, i32 %key, 2 + %Z3 = insertvalue { i32, i32, i32, i32, i32 } %Z2, i32 %key, 3 + %Z4 = insertvalue { i32, i32, i32, i32, i32 } %Z3, i32 %key, 4 + ret { i32, i32, i32, i32, i32 } %Z4 +} + +; The return value {i32, i32, i32, i32} will be returned via registers %r0, %r1, %r2, %r3. +; CHECK-LABEL: test3: +; CHECK: bl {{.*}}gen3 +; CHECK: add r0, r0, r1 +; CHECK: add r0, r0, r2 +; CHECK: add r0, r0, r3 +; CHECK-O0-LABEL: test3: +; CHECK-O0: bl {{.*}}gen3 +; CHECK-O0: add r0, r0, r1 +; CHECK-O0: add r0, r0, r2 +; CHECK-O0: add r0, r0, r3 +define i32 @test3(i32 %key) #0 { +entry: + %key.addr = alloca i32, align 4 + store i32 %key, i32* %key.addr, align 4 + %0 = load i32, i32* %key.addr, align 4 + %call = call swiftcc { i32, i32, i32, i32 } @gen3(i32 %0) + + %v3 = extractvalue { i32, i32, i32, i32 } %call, 0 + %v5 = extractvalue { i32, i32, i32, i32 } %call, 1 + %v6 = extractvalue { i32, i32, i32, i32 } %call, 2 + %v7 = extractvalue { i32, i32, i32, i32 } %call, 3 + + %add = add nsw i32 %v3, %v5 + %add1 = add nsw i32 %add, %v6 + %add2 = add nsw i32 %add1, %v7 + ret i32 %add2 +} + +declare swiftcc { i32, i32, i32, i32 } @gen3(i32 %key) diff --git a/llvm/test/CodeGen/ARM/swifterror.ll b/llvm/test/CodeGen/ARM/swifterror.ll new file mode 100644 index 0000000000000..99a531cd706c8 --- /dev/null +++ b/llvm/test/CodeGen/ARM/swifterror.ll @@ -0,0 +1,368 @@ +; RUN: llc -verify-machineinstrs < %s -mtriple=armv7-apple-ios | FileCheck --check-prefix=CHECK-APPLE %s +; RUN: llc -verify-machineinstrs -O0 < %s -mtriple=armv7-apple-ios | FileCheck --check-prefix=CHECK-O0 %s + +declare i8* @malloc(i64) +declare void @free(i8*) +%swift_error = type {i64, i8} + +define float @foo(%swift_error** swifterror %error_ptr_ref) { +; CHECK-APPLE-LABEL: foo: +; CHECK-APPLE: mov r0, #16 +; CHECK-APPLE: malloc +; CHECK-APPLE-DAG: mov [[ID:r[0-9]+]], #1 +; CHECK-APPLE-DAG: mov r6, r{{.*}} +; CHECK-APPLE-DAG: strb [[ID]], [r{{.*}}, #8] + +; CHECK-O0-LABEL: foo: +; CHECK-O0: mov r{{.*}}, #16 +; CHECK-O0: malloc +; CHECK-O0: mov [[ID2:r[0-9]+]], r0 +; CHECK-O0: mov [[ID:r[0-9]+]], #1 +; CHECK-O0: strb [[ID]], [r0, #8] +; CHECK-O0: mov r6, [[ID2]] +entry: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + ret float 1.0 +} + +define float @caller(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller: +; CHECK-APPLE-DAG: mov [[ID:r[0-9]+]], r0 +; CHECK-APPLE-DAG: mov r6, #0 +; CHECK-APPLE: bl {{.*}}foo +; CHECK-APPLE: cmp r6, #0 +; Access part of the error object and save it to error_ref +; CHECK-APPLE: ldrbeq [[CODE:r[0-9]+]], [r6, #8] +; CHECK-APPLE: strbeq [[CODE]], [{{.*}}[[ID]]] +; CHECK-APPLE: mov r0, r6 +; CHECK_APPLE: bl {{.*}}free + +; CHECK-O0-LABEL: caller: +; spill r0 +; CHECK-O0-DAG: str r0, +; CHECK-O0-DAG: mov r6, #0 +; CHECK-O0: bl {{.*}}foo +; CHECK-O0: mov r{{.*}}, r6 +; CHECK-O0: bne +; CHECK-O0: ldrb [[CODE:r[0-9]+]], [r0, #8] +; reload r0 +; CHECK-O0: ldr [[ID:r[0-9]+]], +; CHECK-O0: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK-O0: mov r0, +; CHECK-O0: free +entry: + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + %call = call float @foo(%swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} + +define float @caller2(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller2: +; CHECK-APPLE-DAG: mov [[ID:r[0-9]+]], r0 +; CHECK-APPLE-DAG: mov r6, #0 +; CHECK-APPLE: bl {{.*}}foo +; CHECK-APPLE: cmp r6, #0 +; CHECK-APPLE: bne +; Access part of the error object and save it to error_ref +; CHECK-APPLE: ldrb [[CODE:r[0-9]+]], [r6, #8] +; CHECK-APPLE: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK-APPLE: mov r0, r6 +; CHECK_APPLE: bl {{.*}}free + +; CHECK-O0-LABEL: caller2: +; spill r0 +; CHECK-O0-DAG: str r0, +; CHECK-O0-DAG: mov r6, #0 +; CHECK-O0: bl {{.*}}foo +; CHECK-O0: mov r{{.*}}, r6 +; CHECK-O0: bne +; CHECK-O0: ble +; CHECK-O0: ldrb [[CODE:r[0-9]+]], [r0, #8] +; reload r0 +; CHECK-O0: ldr [[ID:r[0-9]+]], +; CHECK-O0: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK-O0: mov r0, +; CHECK-O0: free +entry: + %error_ptr_ref = alloca swifterror %swift_error* + br label %bb_loop +bb_loop: + store %swift_error* null, %swift_error** %error_ptr_ref + %call = call float @foo(%swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %cmp = fcmp ogt float %call, 1.000000e+00 + br i1 %cmp, label %bb_end, label %bb_loop +bb_end: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} + +define float @foo_if(%swift_error** swifterror %error_ptr_ref, i32 %cc) { +; CHECK-APPLE-LABEL: foo_if: +; CHECK-APPLE: cmp r0, #0 +; CHECK-APPLE: eq +; CHECK-APPLE: mov r0, #16 +; CHECK-APPLE: malloc +; CHECK-APPLE: mov [[ID:r[0-9]+]], #1 +; CHECK-APPLE-DAG: mov r6, r{{.*}} +; CHECK-APPLE-DAG: strb [[ID]], [r{{.*}}, #8] + +; CHECK-O0-LABEL: foo_if: +; CHECK-O0: cmp r0, #0 +; spill to stack +; CHECK-O0: str r6 +; CHECK-O0: beq +; CHECK-O0: mov r0, #16 +; CHECK-O0: malloc +; CHECK-O0: mov [[ID:r[0-9]+]], r0 +; CHECK-O0: mov [[ID2:r[0-9]+]], #1 +; CHECK-O0: strb [[ID2]], [r0, #8] +; CHECK-O0: mov r6, [[ID]] +; reload from stack +; CHECK-O0: ldr r6 +entry: + %cond = icmp ne i32 %cc, 0 + br i1 %cond, label %gen_error, label %normal + +gen_error: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + ret float 1.0 + +normal: + ret float 0.0 +} + +define float @foo_loop(%swift_error** swifterror %error_ptr_ref, i32 %cc, float %cc2) { +; CHECK-APPLE-LABEL: foo_loop: +; CHECK-APPLE: mov [[CODE:r[0-9]+]], r0 +; swifterror is kept in a register +; CHECK-APPLE: mov [[ID:r[0-9]+]], r6 +; CHECK-APPLE: cmp [[CODE]], #0 +; CHECK-APPLE: beq +; CHECK-APPLE: mov r0, #16 +; CHECK-APPLE: malloc +; CHECK-APPLE: strb r{{.*}}, [{{.*}}[[ID]], #8] +; CHECK-APPLE: ble +; CHECK-APPLE: mov r6, [[ID]] + +; CHECK-O0-LABEL: foo_loop: +; CHECK-O0: mov r{{.*}}, r6 +; CHECK-O0: cmp r{{.*}}, #0 +; CHECK-O0: beq +; CHECK-O0-DAG: movw r{{.*}}, #1 +; CHECK-O0-DAG: mov r{{.*}}, #16 +; CHECK-O0: malloc +; CHECK-O0-DAG: mov [[ID:r[0-9]+]], r0 +; CHECK-O0-DAG: ldr [[ID2:r[0-9]+]], [sp{{.*}}] +; CHECK-O0: strb [[ID2]], [{{.*}}[[ID]], #8] +; spill r0 +; CHECK-O0: str r0, [sp{{.*}}] +; CHECK-O0: vcmpe +; CHECK-O0: ble +; reload from stack +; CHECK-O0: ldr r6 +entry: + br label %bb_loop + +bb_loop: + %cond = icmp ne i32 %cc, 0 + br i1 %cond, label %gen_error, label %bb_cont + +gen_error: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + br label %bb_cont + +bb_cont: + %cmp = fcmp ogt float %cc2, 1.000000e+00 + br i1 %cmp, label %bb_end, label %bb_loop +bb_end: + ret float 0.0 +} + +%struct.S = type { i32, i32, i32, i32, i32, i32 } + +define void @foo_sret(%struct.S* sret %agg.result, i32 %val1, %swift_error** swifterror %error_ptr_ref) { +; CHECK-APPLE-LABEL: foo_sret: +; CHECK-APPLE: mov [[SRET:r[0-9]+]], r0 +; CHECK-APPLE: mov r0, #16 +; CHECK-APPLE: malloc +; CHECK-APPLE: mov [[REG:r[0-9]+]], #1 +; CHECK-APPLE-DAG: mov r6, r0 +; CHECK-APPLE-DAG: strb [[REG]], [r0, #8] +; CHECK-APPLE-DAG: str r{{.*}}, [{{.*}}[[SRET]], #4] + +; CHECK-O0-LABEL: foo_sret: +; CHECK-O0: mov r{{.*}}, #16 +; spill to stack: sret and val1 +; CHECK-O0-DAG: str r0 +; CHECK-O0-DAG: str r1 +; CHECK-O0: malloc +; CHECK-O0: mov [[ID:r[0-9]+]], #1 +; CHECK-O0: strb [[ID]], [r0, #8] +; reload from stack: sret and val1 +; CHECK-O0: ldr +; CHECK-O0: ldr +; CHECK-O0: str r{{.*}}, [{{.*}}, #4] +; CHECK-O0: mov r6 +entry: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + %v2 = getelementptr inbounds %struct.S, %struct.S* %agg.result, i32 0, i32 1 + store i32 %val1, i32* %v2 + ret void +} + +define float @caller3(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller3: +; CHECK-APPLE: mov [[ID:r[0-9]+]], r0 +; CHECK-APPLE: mov r6, #0 +; CHECK-APPLE: bl {{.*}}foo_sret +; CHECK-APPLE: cmp r6, #0 +; Access part of the error object and save it to error_ref +; CHECK-APPLE: ldrbeq [[CODE:r[0-9]+]], [r6, #8] +; CHECK-APPLE: strbeq [[CODE]], [{{.*}}[[ID]]] +; CHECK-APPLE: mov r0, r6 +; CHECK_APPLE: bl {{.*}}free + +; CHECK-O0-LABEL: caller3: +; CHECK-O0-DAG: mov r6, #0 +; CHECK-O0-DAG: mov r0 +; CHECK-O0-DAG: mov r1 +; CHECK-O0: bl {{.*}}foo_sret +; CHECK-O0: mov [[ID2:r[0-9]+]], r6 +; CHECK-O0: cmp [[ID2]] +; CHECK-O0: bne +; Access part of the error object and save it to error_ref +; CHECK-O0: ldrb [[CODE:r[0-9]+]] +; CHECK-O0: ldr [[ID:r[0-9]+]] +; CHECK-O0: strb [[CODE]], [{{.*}}[[ID]]] +; CHECK-O0: mov r0, +; CHECK_O0: bl {{.*}}free +entry: + %s = alloca %struct.S, align 8 + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + call void @foo_sret(%struct.S* sret %s, i32 1, %swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} + +declare void @llvm.va_start(i8*) nounwind +define float @foo_vararg(%swift_error** swifterror %error_ptr_ref, ...) { +; CHECK-APPLE-LABEL: foo_vararg: +; CHECK-APPLE: mov r0, #16 +; CHECK-APPLE: malloc +; CHECK-APPLE: mov [[REG:r[0-9]+]], r0 +; CHECK-APPLE: mov [[ID:r[0-9]+]], #1 +; CHECK-APPLE-DAG: strb [[ID]], [{{.*}}[[REG]], #8] +; CHECK-APPLE-DAG: mov r6, [[REG]] + +entry: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + + %args = alloca i8*, align 8 + %a10 = alloca i32, align 4 + %a11 = alloca i32, align 4 + %a12 = alloca i32, align 4 + %v10 = bitcast i8** %args to i8* + call void @llvm.va_start(i8* %v10) + %v11 = va_arg i8** %args, i32 + store i32 %v11, i32* %a10, align 4 + %v12 = va_arg i8** %args, i32 + store i32 %v12, i32* %a11, align 4 + %v13 = va_arg i8** %args, i32 + store i32 %v13, i32* %a12, align 4 + + ret float 1.0 +} + +define float @caller4(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller4: +; CHECK-APPLE: mov [[ID:r[0-9]+]], r0 +; CHECK-APPLE: mov r6, #0 +; CHECK-APPLE: bl {{.*}}foo_vararg +; CHECK-APPLE: cmp r6, #0 +; Access part of the error object and save it to error_ref +; CHECK-APPLE: ldrbeq [[CODE:r[0-9]+]], [r6, #8] +; CHECK-APPLE: strbeq [[CODE]], [{{.*}}[[ID]]] +; CHECK-APPLE: mov r0, r6 +; CHECK_APPLE: bl {{.*}}free +entry: + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + + %a10 = alloca i32, align 4 + %a11 = alloca i32, align 4 + %a12 = alloca i32, align 4 + store i32 10, i32* %a10, align 4 + store i32 11, i32* %a11, align 4 + store i32 12, i32* %a12, align 4 + %v10 = load i32, i32* %a10, align 4 + %v11 = load i32, i32* %a11, align 4 + %v12 = load i32, i32* %a12, align 4 + + %call = call float (%swift_error**, ...) @foo_vararg(%swift_error** swifterror %error_ptr_ref, i32 %v10, i32 %v11, i32 %v12) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont + +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} diff --git a/llvm/test/CodeGen/ARM/swiftself.ll b/llvm/test/CodeGen/ARM/swiftself.ll new file mode 100644 index 0000000000000..cbeedc410661c --- /dev/null +++ b/llvm/test/CodeGen/ARM/swiftself.ll @@ -0,0 +1,32 @@ +; RUN: llc -verify-machineinstrs < %s -mtriple=armv7k-apple-ios8.0 -mcpu=cortex-a7 | FileCheck --check-prefix=CHECK-APPLE %s +; RUN: llc -O0 -verify-machineinstrs < %s -mtriple=armv7k-apple-ios8.0 -mcpu=cortex-a7 | FileCheck --check-prefix=CHECK-O0 %s + +; RUN: llc -verify-machineinstrs < %s -mtriple=armv7-apple-ios | FileCheck --check-prefix=CHECK-APPLE %s +; RUN: llc -O0 -verify-machineinstrs < %s -mtriple=armv7-apple-ios | FileCheck --check-prefix=CHECK-O0 %s + +; Parameter with swiftself should be allocated to r9. +define void @check_swiftself(i32* swiftself %addr0) { +; CHECK-APPLE-LABEL: check_swiftself: +; CHECK-O0-LABEL: check_swiftself: + + %val0 = load volatile i32, i32* %addr0 +; CHECK-APPLE: ldr r{{.*}}, [r9] +; CHECK-O0: ldr r{{.*}}, [r9] + ret void +} + +@var8_3 = global i8 0 +declare void @take_swiftself(i8* swiftself %addr0) + +define void @simple_args() { +; CHECK-APPLE-LABEL: simple_args: +; CHECK-O0-LABEL: simple_args: + + call void @take_swiftself(i8* @var8_3) +; CHECK-APPLE: add r9, pc +; CHECK-APPLE: bl {{_?}}take_swiftself +; CHECK-O0: add r9, pc +; CHECK-O0: bl {{_?}}take_swiftself + + ret void +} diff --git a/llvm/test/CodeGen/X86/alias-gep.ll b/llvm/test/CodeGen/X86/alias-gep.ll new file mode 100644 index 0000000000000..5ecf20ba78ed5 --- /dev/null +++ b/llvm/test/CodeGen/X86/alias-gep.ll @@ -0,0 +1,22 @@ +; RUN: llc < %s -mtriple=x86_64-apple-darwin | FileCheck --check-prefix=MACHO %s +; RUN: llc < %s -mtriple=x86_64-pc-linux | FileCheck --check-prefix=ELF %s + +;MACHO: .globl _offsetSym0 +;MACHO-NOT: .alt_entry +;MACHO: _offsetSym0 = _s +;MACHO: .globl _offsetSym1 +;MACHO: .alt_entry _offsetSym1 +;MACHO: _offsetSym1 = _s+8 + +;ELF: .globl offsetSym0 +;ELF-NOT: .alt_entry +;ELF: offsetSym0 = s +;ELF: .globl offsetSym1 +;ELF-NOT: .alt_entry +;ELF: offsetSym1 = s+8 + +%struct.S1 = type { i32, i32, i32 } + +@s = global %struct.S1 { i32 31, i32 32, i32 33 }, align 4 +@offsetSym0 = alias i32, i32* getelementptr inbounds (%struct.S1, %struct.S1* @s, i64 0, i32 0) +@offsetSym1 = alias i32, i32* getelementptr inbounds (%struct.S1, %struct.S1* @s, i64 0, i32 2) diff --git a/llvm/test/CodeGen/X86/swift-return.ll b/llvm/test/CodeGen/X86/swift-return.ll new file mode 100644 index 0000000000000..e4e069700c7c9 --- /dev/null +++ b/llvm/test/CodeGen/X86/swift-return.ll @@ -0,0 +1,135 @@ +; RUN: llc -verify-machineinstrs < %s -mtriple=x86_64-unknown-unknown | FileCheck %s +; RUN: llc -verify-machineinstrs < %s -mtriple=x86_64-unknown-unknown -O0 | FileCheck --check-prefix=CHECK-O0 %s + +; Test how llvm handles return type of {i16, i8}. The return value will be passed in %eax and %dl. +; clang actually returns i32 instead of {i16, i8}. +; CHECK-LABEL: test: +; CHECK: movl %edi +; CHECK: callq gen +; CHECK: movsbl %dl +; CHECK: addl %{{.*}}, %eax +; CHECK-O0-LABEL: test +; CHECK-O0: movl %edi +; CHECK-O0: callq gen +; CHECK-O0: movswl %ax +; CHECK-O0: movsbl %dl +; CHECK-O0: addl +; CHECK-O0: movw %{{.*}}, %ax +define i16 @test(i32 %key) { +entry: + %key.addr = alloca i32, align 4 + store i32 %key, i32* %key.addr, align 4 + %0 = load i32, i32* %key.addr, align 4 + %call = call swiftcc { i16, i8 } @gen(i32 %0) + %v3 = extractvalue { i16, i8 } %call, 0 + %v1 = sext i16 %v3 to i32 + %v5 = extractvalue { i16, i8 } %call, 1 + %v2 = sext i8 %v5 to i32 + %add = add nsw i32 %v1, %v2 + %conv = trunc i32 %add to i16 + ret i16 %conv +} + +declare swiftcc { i16, i8 } @gen(i32) + +; We can't pass every return value in register, instead, pass everything in memroy. +; The caller provides space for the return value and passes the address in %rdi. +; The first input argument will be in %rsi. +; CHECK-LABEL: test2: +; CHECK: leaq (%rsp), %rdi +; CHECK: movl %{{.*}}, %esi +; CHECK: callq gen2 +; CHECK: movl (%rsp) +; CHECK-DAG: addl 4(%rsp) +; CHECK-DAG: addl 8(%rsp) +; CHECK-DAG: addl 12(%rsp) +; CHECK-DAG: addl 16(%rsp) +; CHECK-O0-LABEL: test2: +; CHECK-O0-DAG: leaq (%rsp), %rdi +; CHECK-O0-DAG: movl {{.*}}, %esi +; CHECK-O0: callq gen2 +; CHECK-O0-DAG: movl (%rsp) +; CHECK-O0-DAG: movl 4(%rsp) +; CHECK-O0-DAG: movl 8(%rsp) +; CHECK-O0-DAG: movl 12(%rsp) +; CHECK-O0-DAG: movl 16(%rsp) +; CHECK-O0: addl +; CHECK-O0: addl +; CHECK-O0: addl +; CHECK-O0: addl +; CHECK-O0: movl %{{.*}}, %eax +define i32 @test2(i32 %key) #0 { +entry: + %key.addr = alloca i32, align 4 + store i32 %key, i32* %key.addr, align 4 + %0 = load i32, i32* %key.addr, align 4 + %call = call swiftcc { i32, i32, i32, i32, i32 } @gen2(i32 %0) + + %v3 = extractvalue { i32, i32, i32, i32, i32 } %call, 0 + %v5 = extractvalue { i32, i32, i32, i32, i32 } %call, 1 + %v6 = extractvalue { i32, i32, i32, i32, i32 } %call, 2 + %v7 = extractvalue { i32, i32, i32, i32, i32 } %call, 3 + %v8 = extractvalue { i32, i32, i32, i32, i32 } %call, 4 + + %add = add nsw i32 %v3, %v5 + %add1 = add nsw i32 %add, %v6 + %add2 = add nsw i32 %add1, %v7 + %add3 = add nsw i32 %add2, %v8 + ret i32 %add3 +} + +; The address of the return value is passed in %rdi. +; On return, %rax will contain the adddress that has been passed in by the caller in %rdi. +; CHECK-LABEL: gen2: +; CHECK: movl %esi, 16(%rdi) +; CHECK: movl %esi, 12(%rdi) +; CHECK: movl %esi, 8(%rdi) +; CHECK: movl %esi, 4(%rdi) +; CHECK: movl %esi, (%rdi) +; CHECK: movq %rdi, %rax +; CHECK-O0-LABEL: gen2: +; CHECK-O0-DAG: movl %esi, 16(%rdi) +; CHECK-O0-DAG: movl %esi, 12(%rdi) +; CHECK-O0-DAG: movl %esi, 8(%rdi) +; CHECK-O0-DAG: movl %esi, 4(%rdi) +; CHECK-O0-DAG: movl %esi, (%rdi) +; CHECK-O0-DAG: movq %rdi, %rax +define swiftcc { i32, i32, i32, i32, i32 } @gen2(i32 %key) { + %Y = insertvalue { i32, i32, i32, i32, i32 } undef, i32 %key, 0 + %Z = insertvalue { i32, i32, i32, i32, i32 } %Y, i32 %key, 1 + %Z2 = insertvalue { i32, i32, i32, i32, i32 } %Z, i32 %key, 2 + %Z3 = insertvalue { i32, i32, i32, i32, i32 } %Z2, i32 %key, 3 + %Z4 = insertvalue { i32, i32, i32, i32, i32 } %Z3, i32 %key, 4 + ret { i32, i32, i32, i32, i32 } %Z4 +} + +; The return value {i32, i32, i32, i32} will be returned via registers %eax, %edx, %ecx, %r8d. +; CHECK-LABEL: test3: +; CHECK: callq gen3 +; CHECK: addl %edx, %eax +; CHECK: addl %ecx, %eax +; CHECK: addl %r8d, %eax +; CHECK-O0-LABEL: test3: +; CHECK-O0: callq gen3 +; CHECK-O0: addl %edx, %eax +; CHECK-O0: addl %ecx, %eax +; CHECK-O0: addl %r8d, %eax +define i32 @test3(i32 %key) #0 { +entry: + %key.addr = alloca i32, align 4 + store i32 %key, i32* %key.addr, align 4 + %0 = load i32, i32* %key.addr, align 4 + %call = call swiftcc { i32, i32, i32, i32 } @gen3(i32 %0) + + %v3 = extractvalue { i32, i32, i32, i32 } %call, 0 + %v5 = extractvalue { i32, i32, i32, i32 } %call, 1 + %v6 = extractvalue { i32, i32, i32, i32 } %call, 2 + %v7 = extractvalue { i32, i32, i32, i32 } %call, 3 + + %add = add nsw i32 %v3, %v5 + %add1 = add nsw i32 %add, %v6 + %add2 = add nsw i32 %add1, %v7 + ret i32 %add2 +} + +declare swiftcc { i32, i32, i32, i32 } @gen3(i32 %key) diff --git a/llvm/test/CodeGen/X86/swifterror.ll b/llvm/test/CodeGen/X86/swifterror.ll new file mode 100644 index 0000000000000..484a5f29a3b3e --- /dev/null +++ b/llvm/test/CodeGen/X86/swifterror.ll @@ -0,0 +1,276 @@ +; RUN: llc -verify-machineinstrs < %s -mtriple=x86_64-apple-darwin | FileCheck --check-prefix=CHECK-APPLE %s +; RUN: llc -verify-machineinstrs -O0 < %s -mtriple=x86_64-apple-darwin | FileCheck --check-prefix=CHECK-O0 %s + +declare i8* @malloc(i64) +declare void @free(i8*) +%swift_error = type {i64, i8} + +define float @foo(%swift_error** swifterror %error_ptr_ref) { +; CHECK-APPLE-LABEL: foo: +; CHECK-APPLE: movl $16, %edi +; CHECK-APPLE: malloc +; CHECK-APPLE: movb $1, 8(%rax) +; CHECK-APPLE: movq %rax, %r12 + +; CHECK-O0-LABEL: foo: +; CHECK-O0: movl $16 +; CHECK-O0: malloc +; CHECK-O0: movb $1, 8(%rax) +; CHECK-O0: movq %{{.*}}, %r12 +entry: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + ret float 1.0 +} + +define float @caller(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller: +; CHECK-APPLE: xorl %r12d, %r12d +; CHECK-APPLE: callq {{.*}}foo +; CHECK-APPLE: testq %r12, %r12 +; CHECK-APPLE: jne +; Access part of the error object and save it to error_ref +; CHECK-APPLE: movb 8(%r12) +; CHECK-APPLE: movq %r12, %rdi +; CHECK_APPLE: callq {{.*}}free + +; CHECK-O0-LABEL: caller: +; CHECK-O0: xorl +; CHECK-O0: movl %{{.*}}, %r12d +; CHECK-O0: callq {{.*}}foo +; CHECK-O0: jne +entry: + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + %call = call float @foo(%swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} + +define float @caller2(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller2: +; CHECK-APPLE: xorl %r12d, %r12d +; CHECK-APPLE: callq {{.*}}foo +; CHECK-APPLE: testq %r12, %r12 +; CHECK-APPLE: jne +; CHECK-APPLE: ucomiss +; CHECK-APPLE: jbe +; Access part of the error object and save it to error_ref +; CHECK-APPLE: movb 8(%r12) +; CHECK-APPLE: movq %r12, %rdi +; CHECK_APPLE: callq {{.*}}free + +; CHECK-O0-LABEL: caller2: +; CHECK-O0: xorl +; CHECK-O0: movl %{{.*}}, %r12d +; CHECK-O0: callq {{.*}}foo +; CHECK-O0: movq %r12, [[ID:%[a-z]+]] +; CHECK-O0: cmpq $0, [[ID]] +; CHECK-O0: jne +entry: + %error_ptr_ref = alloca swifterror %swift_error* + br label %bb_loop +bb_loop: + store %swift_error* null, %swift_error** %error_ptr_ref + %call = call float @foo(%swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %cmp = fcmp ogt float %call, 1.000000e+00 + br i1 %cmp, label %bb_end, label %bb_loop +bb_end: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} + +define float @foo_if(%swift_error** swifterror %error_ptr_ref, i32 %cc) { +; CHECK-APPLE-LABEL: foo_if: +; CHECK-APPLE: testl %edi, %edi +; CHECK-APPLE: je +; CHECK-APPLE: movl $16, %edi +; CHECK-APPLE: malloc +; CHECK-APPLE: movb $1, 8(%rax) +; CHECK-APPLE: movq %rax, %r12 +; CHECK-APPLE-NOT: %r12 +; CHECK-APPLE: ret + +; CHECK-O0-LABEL: foo_if: +; CHECK-O0: cmpl $0 +; spill to stack +; CHECK-O0: movq %r12, {{.*}}(%rsp) +; CHECK-O0: je +; CHECK-O0: movl $16, +; CHECK-O0: malloc +; CHECK-O0: movq %rax, [[ID:%[a-z]+]] +; CHECK-O0-DAG: movb $1, 8(%rax) +; CHECK-O0-DAG: movq [[ID]], %r12 +; CHECK-O0: ret +; reload from stack +; CHECK-O0: movq {{.*}}(%rsp), %r12 +; CHECK-O0: ret +entry: + %cond = icmp ne i32 %cc, 0 + br i1 %cond, label %gen_error, label %normal + +gen_error: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + ret float 1.0 + +normal: + ret float 0.0 +} + +define float @foo_loop(%swift_error** swifterror %error_ptr_ref, i32 %cc, float %cc2) { +; CHECK-APPLE-LABEL: foo_loop: +; CHECK-APPLE: movq %r12, %rax +; CHECK-APPLE: testl +; CHECK-APPLE: je +; CHECK-APPLE: movl $16, %edi +; CHECK-APPLE: malloc +; CHECK-APPLE: movb $1, 8(%rax) +; CHECK-APPLE: ucomiss +; CHECK-APPLE: jbe +; CHECK-APPLE: movq %rax, %r12 +; CHECK-APPLE: ret + +; CHECK-O0-LABEL: foo_loop: +; spill to stack +; CHECK-O0: movq %r12, {{.*}}(%rsp) +; CHECK-O0: cmpl $0 +; CHECK-O0: je +; CHECK-O0: movl $16, +; CHECK-O0: malloc +; CHECK-O0: movq %rax, [[ID:%[a-z]+]] +; CHECK-O0: movb $1, 8([[ID]]) +; CHECK-O0: jbe +; reload from stack +; CHECK-O0: movq {{.*}}(%rsp), %r12 +; CHECK-O0: ret +entry: + br label %bb_loop + +bb_loop: + %cond = icmp ne i32 %cc, 0 + br i1 %cond, label %gen_error, label %bb_cont + +gen_error: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + br label %bb_cont + +bb_cont: + %cmp = fcmp ogt float %cc2, 1.000000e+00 + br i1 %cmp, label %bb_end, label %bb_loop +bb_end: + ret float 0.0 +} + +%struct.S = type { i32, i32, i32, i32, i32, i32 } + +define void @foo_sret(%struct.S* sret %agg.result, i32 %val1, %swift_error** swifterror %error_ptr_ref) { +; CHECK-APPLE-LABEL: foo_sret: +; CHECK-APPLE: movq %rdi, %{{.*}} +; CHECK-APPLE: movl $16, %edi +; CHECK-APPLE: malloc +; CHECK-APPLE: movb $1, 8(%rax) +; CHECK-APPLE: movl %{{.*}}, 4(%{{.*}}) +; CHECK-APPLE: movq %rax, %r12 +; CHECK-APPLE: movq %{{.*}}, %rax +; CHECK-APPLE-NOT: x19 + +; CHECK-O0-LABEL: foo_sret: +; CHECK-O0: movl $16, +; spill sret to stack +; CHECK-O0: movq %rdi, +; CHECK-O0: movq {{.*}}, %rdi +; CHECK-O0: malloc +; CHECK-O0: movb $1, 8(%rax) +; CHECK-O0: movl %{{.*}}, 4(%{{.*}}) +; CHECK-O0: movq %{{.*}}, %r12 +; reload sret from stack +; CHECK-O0: movq {{.*}}(%rsp), %rax +; CHECK-O0: ret +entry: + %call = call i8* @malloc(i64 16) + %call.0 = bitcast i8* %call to %swift_error* + store %swift_error* %call.0, %swift_error** %error_ptr_ref + %0 = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %0 + %v2 = getelementptr inbounds %struct.S, %struct.S* %agg.result, i32 0, i32 1 + store i32 %val1, i32* %v2 + ret void +} + +define float @caller3(i8* %error_ref) { +; CHECK-APPLE-LABEL: caller3: +; CHECK-APPLE: movl $1, %esi +; CHECK-APPLE: xorl %r12d, %r12d +; CHECK-APPLE: callq {{.*}}foo_sret +; CHECK-APPLE: testq %r12, %r12 +; CHECK-APPLE: jne +; Access part of the error object and save it to error_ref +; CHECK-APPLE: movb 8(%r12), +; CHECK-APPLE: movb %{{.*}}, +; CHECK-APPLE: movq %r12, %rdi +; CHECK_APPLE: callq {{.*}}free + +; CHECK-O0-LABEL: caller3: +; CHECK-O0: xorl +; CHECK-O0: movl {{.*}}, %r12d +; CHECK-O0: movl $1, %esi +; CHECK-O0: movq {{.*}}, %rdi +; CHECK-O0: callq {{.*}}foo_sret +; CHECK-O0: movq %r12, +; CHECK-O0: cmpq $0 +; CHECK-O0: jne +; Access part of the error object and save it to error_ref +; CHECK-O0: movb 8(%{{.*}}), +; CHECK-O0: movb %{{.*}}, +; reload from stack +; CHECK-O0: movq {{.*}}(%rsp), %rdi +; CHECK-O0: callq {{.*}}free +entry: + %s = alloca %struct.S, align 8 + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + call void @foo_sret(%struct.S* sret %s, i32 1, %swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %0 = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %0) + ret float 1.0 +} diff --git a/llvm/test/CodeGen/X86/swiftself.ll b/llvm/test/CodeGen/X86/swiftself.ll new file mode 100644 index 0000000000000..4886fe3860aa9 --- /dev/null +++ b/llvm/test/CodeGen/X86/swiftself.ll @@ -0,0 +1,29 @@ +; RUN: llc -verify-machineinstrs < %s -mtriple=x86_64-unknown-unknown | FileCheck %s +; RUN: llc -O0 -verify-machineinstrs < %s -mtriple=x86_64-unknown-unknown | FileCheck --check-prefix=CHECK-O0 %s + +; Parameter with swiftself should be allocated to r10. +define void @check_swiftself(i32* swiftself %addr0) { +; CHECK-LABEL: check_swiftself: +; CHECK-O0-LABEL: check_swiftself: + + %val0 = load volatile i32, i32* %addr0 +; CHECK: movl (%r10), +; CHECK-O0: movl (%r10), + ret void +} + +@var8_3 = global i8 0 +declare void @take_swiftself(i8* swiftself %addr0) + +define void @simple_args() { +; CHECK-LABEL: simple_args: +; CHECK-O0-LABEL: simple_args: + + call void @take_swiftself(i8* @var8_3) +; CHECK: movl {{.*}}, %r10d +; CHECK: callq {{_?}}take_swiftself +; CHECK-O0: movabsq {{.*}}, %r10 +; CHECK-O0: callq {{_?}}take_swiftself + + ret void +} diff --git a/llvm/test/Transforms/GlobalOpt/alias-used.ll b/llvm/test/Transforms/GlobalOpt/alias-used.ll index 9ced3974ee87d..02136a0644718 100644 --- a/llvm/test/Transforms/GlobalOpt/alias-used.ll +++ b/llvm/test/Transforms/GlobalOpt/alias-used.ll @@ -45,3 +45,22 @@ define i8* @g2() { define i8* @h() { ret i8* @ca } + +; Check that GlobalOpt doesn't try to resolve aliases with GEP operands. + +%struct.S = type { i32, i32, i32 } +@s = global %struct.S { i32 1, i32 2, i32 3 }, align 4 + +@alias1 = alias i32, i32* getelementptr inbounds (%struct.S, %struct.S* @s, i64 0, i32 1) +@alias2 = alias i32, i32* getelementptr inbounds (%struct.S, %struct.S* @s, i64 0, i32 2) + +; CHECK: load i32, i32* @alias1, align 4 +; CHECK: load i32, i32* @alias2, align 4 + +define i32 @foo1() { +entry: + %0 = load i32, i32* @alias1, align 4 + %1 = load i32, i32* @alias2, align 4 + %add = add nsw i32 %1, %0 + ret i32 %add +} diff --git a/llvm/test/Verifier/swifterror.ll b/llvm/test/Verifier/swifterror.ll new file mode 100644 index 0000000000000..843ca268c58c2 --- /dev/null +++ b/llvm/test/Verifier/swifterror.ll @@ -0,0 +1,4 @@ +; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s + +declare void @a(i32** swifterror %a, i32** swifterror %b) +; CHECK: Cannot have multiple 'swifterror' parameters! diff --git a/llvm/test/Verifier/swiftself.ll b/llvm/test/Verifier/swiftself.ll new file mode 100644 index 0000000000000..18789e11c8d72 --- /dev/null +++ b/llvm/test/Verifier/swiftself.ll @@ -0,0 +1,4 @@ +; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s + +declare void @a(i32* swiftself %a, i32* swiftself %b) +; CHECK: Cannot have multiple 'swiftself' parameters! From 87073d987b5bb070c06c4f6ef3c11004f7bf3944 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Sat, 7 Nov 2015 12:48:19 -0600 Subject: [PATCH 003/585] Make upstream-with-swift build. This is a fix that was not properly propagated into the repo. apple-llvm-split-commit: 5411fec22d0f6a8688e0d2eeeb5f0045e0019a0e apple-llvm-split-dir: llvm/ --- llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp index 17a8ab722ce3e..d69de31146a35 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp @@ -1122,7 +1122,7 @@ static void setupSwiftErrorVals(const Function &Fn, const TargetLowering *TLI, for (Function::const_arg_iterator AI = Fn.arg_begin(), AE = Fn.arg_end(); AI != AE; ++AI) if (AI->hasSwiftErrorAttr()) - FuncInfo->SwiftErrorVals.push_back(AI); + FuncInfo->SwiftErrorVals.push_back(&*AI); for (const auto &LLVMBB : Fn) for (const auto &Inst : LLVMBB) { From 94631e6ed2a52463cc2a3f6d6483c867beac82ab Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Sat, 7 Nov 2015 12:49:16 -0600 Subject: [PATCH 004/585] Fix bad merge. apple-llvm-split-commit: 3a38cf86fbd78b94e2f1f843b3c5082fb3b8e5e7 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/AttrDocs.td | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 6b991d05c32d3..cf1811a11e7e2 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1646,6 +1646,7 @@ All of these conventions except ``none`` require the function to have an error p * ``swift_error(nonnull_error)`` means that calls to the function should be considered to have thrown if they leave a non-null error in the error parameter. The return type is left unmodified. }]; +} def NotTailCalledDocs : Documentation { let Category = DocCatFunction; From aeabcd027b92187436bf1408268822fcbfd74e1c Mon Sep 17 00:00:00 2001 From: Frederic Riss Date: Wed, 11 Nov 2015 09:26:01 -0800 Subject: [PATCH 005/585] Simplify expression apple-llvm-split-commit: c351d95e90f493ac3b49f5a21387bae8e7ba7b10 apple-llvm-split-dir: llvm/ --- llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp index d69de31146a35..17a8ab722ce3e 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp @@ -1122,7 +1122,7 @@ static void setupSwiftErrorVals(const Function &Fn, const TargetLowering *TLI, for (Function::const_arg_iterator AI = Fn.arg_begin(), AE = Fn.arg_end(); AI != AE; ++AI) if (AI->hasSwiftErrorAttr()) - FuncInfo->SwiftErrorVals.push_back(&*AI); + FuncInfo->SwiftErrorVals.push_back(AI); for (const auto &LLVMBB : Fn) for (const auto &Inst : LLVMBB) { From c1449f84e6493c04a37439f189b99213537a44c0 Mon Sep 17 00:00:00 2001 From: David Farler Date: Tue, 17 Mar 2015 12:00:39 -0700 Subject: [PATCH 006/585] Add APINotes cases for TvOS and WatchOS rdar://problem/20192169 apple-llvm-split-commit: a112670eba7ff17002233c9575108a68bc5a8425 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesYAMLCompiler.h | 2 ++ clang/tools/driver/apinotes_main.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/clang/include/clang/APINotes/APINotesYAMLCompiler.h b/clang/include/clang/APINotes/APINotesYAMLCompiler.h index e897b5cf90496..f459e079d25bd 100644 --- a/clang/include/clang/APINotes/APINotesYAMLCompiler.h +++ b/clang/include/clang/APINotes/APINotesYAMLCompiler.h @@ -35,6 +35,8 @@ namespace api_notes { enum class OSType { OSX, IOS, + TvOS, + WatchOS, Absent }; diff --git a/clang/tools/driver/apinotes_main.cpp b/clang/tools/driver/apinotes_main.cpp index 76892b69ba4d7..cbd8045922d2b 100644 --- a/clang/tools/driver/apinotes_main.cpp +++ b/clang/tools/driver/apinotes_main.cpp @@ -104,6 +104,12 @@ int cc1apinotes_main(ArrayRef Argv, const char *Argv0, case llvm::Triple::IOS: targetOS = api_notes::OSType::IOS; break; + case llvm::Triple::WatchOS: + targetOS = api_notes::OSType::WatchOS; + break; + case llvm::Triple::TvOS: + targetOS = api_notes::OSType::TvOS; + break; default: errs() << "target is not supported\n"; return 1; From ab7aea0ca0556522dfb5dd743048dbca4198a4fb Mon Sep 17 00:00:00 2001 From: Greg Clayton Date: Tue, 26 May 2015 10:02:30 -0700 Subject: [PATCH 007/585] LLDB needs access to RecordDecl::LoadedFieldsFromExternalStorage member variable. Adding accessors to this variable. Needed for module debug info: apple-llvm-split-commit: 70949ee8bfb57a1c546b9d4e4001aed94a1f10c9 apple-llvm-split-dir: clang/ --- clang/include/clang/AST/Decl.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index cc778a550a7ba..434b45651ba77 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -3284,6 +3284,9 @@ class RecordDecl : public TagDecl { bool hasFlexibleArrayMember() const { return HasFlexibleArrayMember; } void setHasFlexibleArrayMember(bool V) { HasFlexibleArrayMember = V; } + bool hasLoadedFieldsFromExternalStorage() const { return LoadedFieldsFromExternalStorage; } + void setHasLoadedFieldsFromExternalStorage(bool V) { LoadedFieldsFromExternalStorage = V; } + /// isAnonymousStructOrUnion - Whether this is an anonymous struct /// or union. To be an anonymous struct or union, it must have been /// declared without a name and there must be no objects of this From b84dbfff739f5db81fbbdb39c8edf9f5078802be Mon Sep 17 00:00:00 2001 From: Ben Langmuir Date: Tue, 10 Nov 2015 13:31:33 -0800 Subject: [PATCH 008/585] Allow modules specified by -fmodule-map-file to shadow implicitly found ones When modules come from module map files explicitly specified by -fmodule-map-file= arguments, allow those to override/shadow modules with the same name that are found implicitly by header search. If such a module is looked up by name (e.g. @import), we will always find the one from -fmodule-map-file. If we try to use a shadowed module by including one of its headers report an error. This enables developers to force use of a specific copy of their module to be used if there are multiple copies that would otherwise be visible, for example if they develop modules that are installed in the default search paths. rdar://problem/23467372 apple-llvm-split-commit: ead19a9fbe2c8884b9cf292579244e41a997f86a apple-llvm-split-dir: clang/ --- .../clang/Basic/DiagnosticCommonKinds.td | 2 + clang/include/clang/Basic/Module.h | 18 ++++-- clang/include/clang/Lex/ModuleMap.h | 29 +++++++++- clang/lib/Basic/Module.cpp | 7 ++- clang/lib/Frontend/CompilerInstance.cpp | 7 ++- clang/lib/Frontend/FrontendAction.cpp | 7 +++ clang/lib/Frontend/FrontendActions.cpp | 7 ++- clang/lib/Lex/ModuleMap.cpp | 58 ++++++++++++++----- clang/lib/Lex/PPDirectives.cpp | 6 +- clang/test/Modules/Inputs/shadow/A1/A.h | 1 + .../Modules/Inputs/shadow/A1/module.modulemap | 5 ++ clang/test/Modules/Inputs/shadow/A2/A.h | 1 + .../Modules/Inputs/shadow/A2/module.modulemap | 5 ++ clang/test/Modules/shadow.m | 21 +++++++ 14 files changed, 151 insertions(+), 23 deletions(-) create mode 100644 clang/test/Modules/Inputs/shadow/A1/A.h create mode 100644 clang/test/Modules/Inputs/shadow/A1/module.modulemap create mode 100644 clang/test/Modules/Inputs/shadow/A2/A.h create mode 100644 clang/test/Modules/Inputs/shadow/A2/module.modulemap create mode 100644 clang/test/Modules/shadow.m diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index dc28200da94a2..e7d9354ae1d02 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -88,6 +88,8 @@ def err_module_unavailable : Error< "module '%0' %select{is incompatible with|requires}1 feature '%2'">; def err_module_header_missing : Error< "%select{|umbrella }0header '%1' not found">; +def err_module_shadowed : Error< + "import of shadowed module '%0'">; def err_module_lock_failure : Error< "could not acquire lock file for module '%0'">, DefaultFatal; def err_module_lock_timeout : Error< diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h index fa032e9eb6695..087320bd2c36d 100644 --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -149,6 +149,9 @@ class Module { /// will be false to indicate that this (sub)module is not available. SmallVector Requirements; + /// \brief A module with the same name that shadows this module. + Module *ShadowingModule = nullptr; + /// \brief Whether this module is missing a feature from \c Requirements. unsigned IsMissingRequirement : 1; @@ -323,13 +326,20 @@ class Module { /// /// \param Target The target options used for the current translation unit. /// - /// \param Req If this module is unavailable, this parameter - /// will be set to one of the requirements that is not met for use of - /// this module. + /// \param Req If this module is unavailable because of a missing requirement, + /// this parameter will be set to one of the requirements that is not met for + /// use of this module. + /// + /// \param MissingHeader If this module is unavailable because of a missing + /// header, this parameter will be set to one of the missing headers. + /// + /// \param ShadowingModule If this module is unavailable because it is + /// shadowed, this parameter will be set to the shadowing module. bool isAvailable(const LangOptions &LangOpts, const TargetInfo &Target, Requirement &Req, - UnresolvedHeaderDirective &MissingHeader) const; + UnresolvedHeaderDirective &MissingHeader, + Module *&ShadowingModule) const; /// \brief Determine whether this module is a submodule. bool isSubModule() const { return Parent != nullptr; } diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h index 155943e5453c8..6d171b24f2469 100644 --- a/clang/include/clang/Lex/ModuleMap.h +++ b/clang/include/clang/Lex/ModuleMap.h @@ -79,7 +79,7 @@ class ModuleMap { std::string SourceModuleName; private: - /// \brief The top-level modules that are known. + /// \brief The unshadowed top-level modules that are known. llvm::StringMap Modules; /// \brief The number of modules we have created in total. @@ -155,6 +155,15 @@ class ModuleMap { /// header. llvm::DenseMap UmbrellaDirs; + /// \brief A generation counter that is used to test whether modules of the + /// same name may shadow or are illegal redefintions. + /// + /// Modules from earlier scopes may shadow modules from later ones. + /// Modules from the same scope may not have the same name. + unsigned CurrentModuleScopeID = 0; + + llvm::DenseMap ModuleScopeIDs; + /// \brief The set of attributes that can be attached to a module. struct Attributes { Attributes() : IsSystem(), IsExternC(), IsExhaustive() {} @@ -388,6 +397,24 @@ class ModuleMap { Module *inferFrameworkModule(const DirectoryEntry *FrameworkDir, bool IsSystem, Module *Parent); + /// \brief Create a new top-level module that is shadowed by + /// \p ShadowingModule. + Module *createShadowedModule(StringRef Name, bool IsFramework, + Module *ShadowingModule); + + /// \brief Creates a new declaration scope for module names, allowing + /// previously defined modules to shadow definitions from the new scope. + /// + /// \note Module names from earlier scopes will shadow names from the new + /// scope, which is the opposite of how shadowing works for variables. + void finishModuleDeclarationScope() { CurrentModuleScopeID += 1; } + + bool mayShadowNewModule(Module *ExistingModule) { + assert(!ExistingModule->Parent && "expected top-level module"); + assert(ModuleScopeIDs.count(ExistingModule) && "unknown module"); + return ModuleScopeIDs[ExistingModule] < CurrentModuleScopeID; + } + /// \brief Retrieve the module map file containing the definition of the given /// module. /// diff --git a/clang/lib/Basic/Module.cpp b/clang/lib/Basic/Module.cpp index 0b78326369439..a8656749907f7 100644 --- a/clang/lib/Basic/Module.cpp +++ b/clang/lib/Basic/Module.cpp @@ -79,11 +79,16 @@ static bool hasFeature(StringRef Feature, const LangOptions &LangOpts, bool Module::isAvailable(const LangOptions &LangOpts, const TargetInfo &Target, Requirement &Req, - UnresolvedHeaderDirective &MissingHeader) const { + UnresolvedHeaderDirective &MissingHeader, + Module *&ShadowingModule) const { if (IsAvailable) return true; for (const Module *Current = this; Current; Current = Current->Parent) { + if (Current->ShadowingModule) { + ShadowingModule = Current->ShadowingModule; + return false; + } for (unsigned I = 0, N = Current->Requirements.size(); I != N; ++I) { if (hasFeature(Current->Requirements[I].first, LangOpts, Target) != Current->Requirements[I].second) { diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index c0cf64cecbe80..57ec9c47a0529 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -1605,8 +1605,13 @@ CompilerInstance::loadModule(SourceLocation ImportLoc, // Check whether this module is available. clang::Module::Requirement Requirement; clang::Module::UnresolvedHeaderDirective MissingHeader; + clang::Module *ShadowingModule = nullptr; if (!Module->isAvailable(getLangOpts(), getTarget(), Requirement, - MissingHeader)) { + MissingHeader, ShadowingModule)) { + + assert(!ShadowingModule && + "lookup of module by name should never find shadowed module"); + if (MissingHeader.FileNameLoc.isValid()) { getDiagnostics().Report(MissingHeader.FileNameLoc, diag::err_module_header_missing) diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp index ecef92e0a7dde..005bbf6e2745a 100644 --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/clang/lib/Frontend/FrontendAction.cpp @@ -394,6 +394,13 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI, CI.getDiagnostics().Report(diag::err_module_map_not_found) << Filename; } + // Add a module declaration scope so that modules from -fmodule-map-file + // arguments may shadow modules found implicitly in search paths. + CI.getPreprocessor() + .getHeaderSearchInfo() + .getModuleMap() + .finishModuleDeclarationScope(); + // If we were asked to load any module files, do so now. for (const auto &ModuleFile : CI.getFrontendOpts().ModuleFiles) if (!CI.loadModuleFile(ModuleFile)) diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp index 865fb474020f5..8b0eff2d6ed5f 100644 --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -321,8 +321,13 @@ bool GenerateModuleAction::BeginSourceFileAction(CompilerInstance &CI, // Check whether we can build this module at all. clang::Module::Requirement Requirement; clang::Module::UnresolvedHeaderDirective MissingHeader; + clang::Module *ShadowingModule = nullptr; if (!Module->isAvailable(CI.getLangOpts(), CI.getTarget(), Requirement, - MissingHeader)) { + MissingHeader, ShadowingModule)) { + + assert(!ShadowingModule && + "lookup of module by name should never find shadowed module"); + if (MissingHeader.FileNameLoc.isValid()) { CI.getDiagnostics().Report(MissingHeader.FileNameLoc, diag::err_module_header_missing) diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp index c66bd70487fd8..d997aedabc00e 100644 --- a/clang/lib/Lex/ModuleMap.cpp +++ b/clang/lib/Lex/ModuleMap.cpp @@ -562,6 +562,7 @@ ModuleMap::findOrCreateModule(StringRef Name, Module *Parent, bool IsFramework, } if (!Parent) { Modules[Name] = Result; + ModuleScopeIDs[Result] = CurrentModuleScopeID; if (!LangOpts.CurrentModule.empty() && !CompilingModule && Name == LangOpts.CurrentModule) { CompilingModule = Result; @@ -570,6 +571,20 @@ ModuleMap::findOrCreateModule(StringRef Name, Module *Parent, bool IsFramework, return std::make_pair(Result, true); } +Module *ModuleMap::createShadowedModule(StringRef Name, bool IsFramework, + Module *ShadowingModule) { + + // Create a new module with this name. + Module *Result = + new Module(Name, SourceLocation(), /*Parent=*/nullptr, IsFramework, + /*IsExplicit=*/false, NumCreatedModules++); + Result->ShadowingModule = ShadowingModule; + Result->IsAvailable = false; + ModuleScopeIDs[Result] = CurrentModuleScopeID; + + return Result; +} + /// \brief For a framework module, infer the framework against which we /// should link. static void inferFrameworkLink(Module *Mod, const DirectoryEntry *FrameworkDir, @@ -682,6 +697,8 @@ Module *ModuleMap::inferFrameworkModule(const DirectoryEntry *FrameworkDir, Module *Result = new Module(ModuleName, SourceLocation(), Parent, /*IsFramework=*/true, /*IsExplicit=*/false, NumCreatedModules++); + if (!Parent) + ModuleScopeIDs[Result] = CurrentModuleScopeID; InferredModuleAllowedBy[Result] = ModuleMapFile; Result->IsInferred = true; if (LangOpts.CurrentModule == ModuleName) { @@ -1412,6 +1429,7 @@ void ModuleMapParser::parseModuleDecl() { SourceLocation LBraceLoc = consumeToken(); // Determine whether this (sub)module has already been defined. + Module *ShadowingModule = nullptr; if (Module *Existing = Map.lookupModuleQualified(ModuleName, ActiveModule)) { if (Existing->DefinitionLoc.isInvalid() && !ActiveModule) { // Skip the module definition. @@ -1425,23 +1443,35 @@ void ModuleMapParser::parseModuleDecl() { } return; } - - Diags.Report(ModuleNameLoc, diag::err_mmap_module_redefinition) - << ModuleName; - Diags.Report(Existing->DefinitionLoc, diag::note_mmap_prev_definition); - - // Skip the module definition. - skipUntil(MMToken::RBrace); - if (Tok.is(MMToken::RBrace)) - consumeToken(); - - HadError = true; - return; + + if (!Existing->Parent && Map.mayShadowNewModule(Existing)) { + ShadowingModule = Existing; + } else { + // This is not a shawdowed module decl, it is an illegal redefinition. + Diags.Report(ModuleNameLoc, diag::err_mmap_module_redefinition) + << ModuleName; + Diags.Report(Existing->DefinitionLoc, diag::note_mmap_prev_definition); + + // Skip the module definition. + skipUntil(MMToken::RBrace); + if (Tok.is(MMToken::RBrace)) + consumeToken(); + + HadError = true; + return; + } } // Start defining this module. - ActiveModule = Map.findOrCreateModule(ModuleName, ActiveModule, Framework, - Explicit).first; + if (ShadowingModule) { + ActiveModule = + Map.createShadowedModule(ModuleName, Framework, ShadowingModule); + } else { + ActiveModule = + Map.findOrCreateModule(ModuleName, ActiveModule, Framework, Explicit) + .first; + } + ActiveModule->DefinitionLoc = ModuleNameLoc; if (Attrs.IsSystem || IsSystem) ActiveModule->IsSystem = true; diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index fd16168203a99..01f680c3429fb 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -1683,13 +1683,17 @@ void Preprocessor::HandleIncludeDirective(SourceLocation HashLoc, if (!SuggestedModule.getModule()->isAvailable()) { clang::Module::Requirement Requirement; clang::Module::UnresolvedHeaderDirective MissingHeader; + clang::Module *ShadowingModule = nullptr; Module *M = SuggestedModule.getModule(); // Identify the cause. (void)M->isAvailable(getLangOpts(), getTargetInfo(), Requirement, - MissingHeader); + MissingHeader, ShadowingModule); if (MissingHeader.FileNameLoc.isValid()) { Diag(MissingHeader.FileNameLoc, diag::err_module_header_missing) << MissingHeader.IsUmbrella << MissingHeader.FileName; + } else if (ShadowingModule) { + Diag(M->DefinitionLoc, diag::err_module_shadowed) << M->Name; + Diag(ShadowingModule->DefinitionLoc, diag::note_previous_definition); } else { Diag(M->DefinitionLoc, diag::err_module_unavailable) << M->getFullModuleName() << Requirement.second << Requirement.first; diff --git a/clang/test/Modules/Inputs/shadow/A1/A.h b/clang/test/Modules/Inputs/shadow/A1/A.h new file mode 100644 index 0000000000000..f07c681c2aa5c --- /dev/null +++ b/clang/test/Modules/Inputs/shadow/A1/A.h @@ -0,0 +1 @@ +#define A1_A_h diff --git a/clang/test/Modules/Inputs/shadow/A1/module.modulemap b/clang/test/Modules/Inputs/shadow/A1/module.modulemap new file mode 100644 index 0000000000000..9439a431b1dbe --- /dev/null +++ b/clang/test/Modules/Inputs/shadow/A1/module.modulemap @@ -0,0 +1,5 @@ +module A { + header "A.h" +} + +module A1 {} diff --git a/clang/test/Modules/Inputs/shadow/A2/A.h b/clang/test/Modules/Inputs/shadow/A2/A.h new file mode 100644 index 0000000000000..9880ed010f569 --- /dev/null +++ b/clang/test/Modules/Inputs/shadow/A2/A.h @@ -0,0 +1 @@ +#define A2_A_h diff --git a/clang/test/Modules/Inputs/shadow/A2/module.modulemap b/clang/test/Modules/Inputs/shadow/A2/module.modulemap new file mode 100644 index 0000000000000..935d89bb425e0 --- /dev/null +++ b/clang/test/Modules/Inputs/shadow/A2/module.modulemap @@ -0,0 +1,5 @@ +module A { + header "A.h" +} + +module A2 {} diff --git a/clang/test/Modules/shadow.m b/clang/test/Modules/shadow.m new file mode 100644 index 0000000000000..44320af2b0c66 --- /dev/null +++ b/clang/test/Modules/shadow.m @@ -0,0 +1,21 @@ +// RUN: rm -rf %t +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -I %S/Inputs/shadow/A1 -I %S/Inputs/shadow/A2 %s -fsyntax-only 2>&1 | FileCheck %s -check-prefix=REDEFINITION +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/shadow/A1/module.modulemap -fmodule-map-file=%S/Inputs/shadow/A2/module.modulemap %s -fsyntax-only 2>&1 | FileCheck %s -check-prefix=REDEFINITION +// REDEFINITION: error: redefinition of module 'A' +// REDEFINITION: note: previously defined + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/shadow/A1/module.modulemap -I %S/Inputs/shadow %s -verify + +@import A1; +@import A2; +@import A; + +#import "A2/A.h" // expected-note {{implicitly imported}} +// expected-error@A2/module.modulemap:1 {{import of shadowed module 'A'}} +// expected-note@A1/module.modulemap:1 {{previous definition}} + +#if defined(A2_A_h) +#error got the wrong definition of module A +#elif !defined(A1_A_h) +#error missing definition from A1 +#endif From 0d2beade7ce4891eca34428a2a73bc01820c797a Mon Sep 17 00:00:00 2001 From: Matthias Braun Date: Tue, 1 Dec 2015 11:30:12 -0800 Subject: [PATCH 009/585] Fix build problem caused by automatic merging apple-llvm-split-commit: 7dccaefc101ea7bd4b415add60cfc98c7c509bbb apple-llvm-split-dir: llvm/ --- llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp index ae6d1cb6a465a..a1dc92e717030 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp @@ -1126,7 +1126,7 @@ static void setupSwiftErrorVals(const Function &Fn, const TargetLowering *TLI, for (Function::const_arg_iterator AI = Fn.arg_begin(), AE = Fn.arg_end(); AI != AE; ++AI) if (AI->hasSwiftErrorAttr()) - FuncInfo->SwiftErrorVals.push_back(AI); + FuncInfo->SwiftErrorVals.push_back(&*AI); for (const auto &LLVMBB : Fn) for (const auto &Inst : LLVMBB) { From 002115512a6a6e65e3e9760811d4b8668d2f1701 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Sat, 5 Dec 2015 11:49:46 -0800 Subject: [PATCH 010/585] Add legal notice for pull requests and reference to contribution guidelines. GitHub will look for this file when a new pull request is opened and offer it to the user. apple-llvm-split-commit: c5f76a1c0c8dd8ba4b95b98ca06c140fdf4bef29 apple-llvm-split-dir: llvm/ --- llvm/CONTRIBUTING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 llvm/CONTRIBUTING.md diff --git a/llvm/CONTRIBUTING.md b/llvm/CONTRIBUTING.md new file mode 100644 index 0000000000000..a0c1644fc66a2 --- /dev/null +++ b/llvm/CONTRIBUTING.md @@ -0,0 +1,14 @@ +By submitting a pull request, you represent that you have the right to license +your contribution to Apple and the community, and agree by submitting the patch +that your contributions are licensed under the [Swift +license](https://swift.org/LICENSE.txt). + +--- + +Changes to this repository follow special considerations as described on +Swift.org under "[LLVM and Swift](https://swift.org/contributing/#llvm-and-swift)". +Please make sure your change is appropriate for this repository. + +Before submitting a pull request, please make sure you have tested your +changes and that they follow the Swift project [guidelines for contributing +code](https://swift.org/contributing/#contributing-code). From 34ce081b6367b4060a2f5608541bcd9d1b86245d Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Sat, 5 Dec 2015 11:51:53 -0800 Subject: [PATCH 011/585] Add legal notice for pull requests and reference to contribution guidelines. GitHub will look for this file when a new pull request is opened and offer it to the user. apple-llvm-split-commit: a855ffb05fe2b1dc0035a06346326feda2f7240c apple-llvm-split-dir: clang/ --- clang/CONTRIBUTING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 clang/CONTRIBUTING.md diff --git a/clang/CONTRIBUTING.md b/clang/CONTRIBUTING.md new file mode 100644 index 0000000000000..a0c1644fc66a2 --- /dev/null +++ b/clang/CONTRIBUTING.md @@ -0,0 +1,14 @@ +By submitting a pull request, you represent that you have the right to license +your contribution to Apple and the community, and agree by submitting the patch +that your contributions are licensed under the [Swift +license](https://swift.org/LICENSE.txt). + +--- + +Changes to this repository follow special considerations as described on +Swift.org under "[LLVM and Swift](https://swift.org/contributing/#llvm-and-swift)". +Please make sure your change is appropriate for this repository. + +Before submitting a pull request, please make sure you have tested your +changes and that they follow the Swift project [guidelines for contributing +code](https://swift.org/contributing/#contributing-code). From 4f1106e6aaa92dfde2b68e759c3e1b9244e7a90e Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Thu, 10 Dec 2015 18:20:34 -0800 Subject: [PATCH 012/585] Revert "LLDB needs access to RecordDecl::LoadedFieldsFromExternalStorage member variable. Adding accessors to this variable." This reverts commit 70949ee8bfb57a1c546b9d4e4001aed94a1f10c9. This was upstreamed in revision (r254451) into a different part of the file. By reverting this commit, we still have the functionality, no duplicate declarations, and hopefully less merge conflicts since the member variable that is in tree is in the place that it was upstreamed. apple-llvm-split-commit: 56faa3674d501cc8e934fe6aef9d4d8972f86134 apple-llvm-split-dir: clang/ --- clang/include/clang/AST/Decl.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index f70e12edf7dfc..a7203f828e066 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -3284,9 +3284,6 @@ class RecordDecl : public TagDecl { bool hasFlexibleArrayMember() const { return HasFlexibleArrayMember; } void setHasFlexibleArrayMember(bool V) { HasFlexibleArrayMember = V; } - bool hasLoadedFieldsFromExternalStorage() const { return LoadedFieldsFromExternalStorage; } - void setHasLoadedFieldsFromExternalStorage(bool V) { LoadedFieldsFromExternalStorage = V; } - /// isAnonymousStructOrUnion - Whether this is an anonymous struct /// or union. To be an anonymous struct or union, it must have been /// declared without a name and there must be no objects of this From cbd2ddc7239f05b2709a059158aa4ac6cb9f22d1 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 12 Dec 2015 12:54:57 -0800 Subject: [PATCH 013/585] Add __has_feature(generalized_swift_name). apple-llvm-split-commit: cf15d4961a3e7c113eea3dcb37d4a0405220e0b5 apple-llvm-split-dir: clang/ --- clang/lib/Lex/PPMacroExpansion.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp index a1b99545dd074..e168d0dea8f6b 100644 --- a/clang/lib/Lex/PPMacroExpansion.cpp +++ b/clang/lib/Lex/PPMacroExpansion.cpp @@ -1085,6 +1085,7 @@ static bool HasFeature(const Preprocessor &PP, const IdentifierInfo *II) { .Case("cxx_exceptions", LangOpts.CXXExceptions) .Case("cxx_rtti", LangOpts.RTTI && LangOpts.RTTIData) .Case("enumerator_attributes", true) + .Case("generalized_swift_name", true) .Case("nullability", true) .Case("memory_sanitizer", LangOpts.Sanitize.has(SanitizerKind::Memory)) .Case("thread_sanitizer", LangOpts.Sanitize.has(SanitizerKind::Thread)) From 8910c5c786886f17a75bd142fa967932ca3f54c1 Mon Sep 17 00:00:00 2001 From: Ahmed Bougacha Date: Mon, 21 Dec 2015 13:54:44 -0800 Subject: [PATCH 014/585] [AArch64] Add -disable-post-ra to swifterror tests. This was enabled by default in r256158. Like there, let's avoid test churn and disable it for the old tests. apple-llvm-split-commit: 971e110c2ed8c35356000dddde6bc543fbc6f33d apple-llvm-split-dir: llvm/ --- llvm/test/CodeGen/AArch64/swifterror.ll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llvm/test/CodeGen/AArch64/swifterror.ll b/llvm/test/CodeGen/AArch64/swifterror.ll index 0106bb30a3d0b..ec9d256fb82ee 100644 --- a/llvm/test/CodeGen/AArch64/swifterror.ll +++ b/llvm/test/CodeGen/AArch64/swifterror.ll @@ -1,5 +1,5 @@ -; RUN: llc -verify-machineinstrs < %s -mtriple=aarch64-apple-ios | FileCheck --check-prefix=CHECK-APPLE %s -; RUN: llc -verify-machineinstrs -O0 < %s -mtriple=aarch64-apple-ios | FileCheck --check-prefix=CHECK-O0 %s +; RUN: llc -verify-machineinstrs < %s -mtriple=aarch64-apple-ios -disable-post-ra | FileCheck --check-prefix=CHECK-APPLE %s +; RUN: llc -verify-machineinstrs -O0 < %s -mtriple=aarch64-apple-ios -disable-post-ra | FileCheck --check-prefix=CHECK-O0 %s declare i8* @malloc(i64) declare void @free(i8*) From 6b74c5b3de14bc3877f1b2ed2db6d547c1838a41 Mon Sep 17 00:00:00 2001 From: Ahmed Bougacha Date: Mon, 4 Jan 2016 09:58:27 -0800 Subject: [PATCH 015/585] [Bitcode] Update swift-related code for upstream (r256593) change. This was added in 3e2331faf. apple-llvm-split-commit: aae18979923e22fb65f23559cd9ebaf3f923420f apple-llvm-split-dir: llvm/ --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index 256e56b7df0e2..3bc5bdec85180 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2069,12 +2069,12 @@ std::error_code BitcodeReader::parseMetadata(bool ModuleLevel) { if (Record.size() != 6) return error("Invalid record"); - MDValueList.assignValue( + MetadataList.assignValue( GET_OR_DISTINCT(DIModule, Record[0], (Context, getMDOrNull(Record[4]), getMDString(Record[5]), nullptr, nullptr, nullptr)), - NextMDValueNo++); + NextMetadataNo++); break; } From 2bd6c3c512bf5f22ce2b0fb378b72165c69e30c7 Mon Sep 17 00:00:00 2001 From: Rafael Espindola Date: Wed, 20 Jan 2016 18:57:48 +0000 Subject: [PATCH 016/585] Accept subtractions involving a weak symbol. When a symbol S shows up in an expression in assembly there are two possible interpretations * The expression is referring to the value of S in this file. * The expression is referring to the value after symbol resolution. In the first case the assembler can reason about the value and try to produce a relocation. In the second case, that is only possible if the symbol cannot be preempted. Assemblers are not very consistent about which interpretation gets used. This changes MC to agree with GAS in the case of an expression of the form "Sym - WeakSym". git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@258329 91177308-0d34-0410-b5e6-96231b3b80d8 apple-llvm-split-commit: 46550e64cdfaa341709758f62064c04132336c85 apple-llvm-split-dir: llvm/ --- llvm/lib/MC/ELFObjectWriter.cpp | 6 ------ llvm/test/MC/AArch64/error-location.s | 3 --- llvm/test/MC/ARM/error-location.s | 3 --- llvm/test/MC/ELF/relocation.s | 6 ++++++ 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/llvm/lib/MC/ELFObjectWriter.cpp b/llvm/lib/MC/ELFObjectWriter.cpp index 23d0e28324c0d..20ad41659f48e 100644 --- a/llvm/lib/MC/ELFObjectWriter.cpp +++ b/llvm/lib/MC/ELFObjectWriter.cpp @@ -655,12 +655,6 @@ void ELFObjectWriter::recordRelocation(MCAssembler &Asm, return; } - if (::isWeak(SymB)) { - Ctx.reportError(Fixup.getLoc(), - "Cannot represent a subtraction with a weak symbol"); - return; - } - uint64_t SymBOffset = Layout.getSymbolOffset(SymB); uint64_t K = SymBOffset - FixupOffset; IsPCRel = true; diff --git a/llvm/test/MC/AArch64/error-location.s b/llvm/test/MC/AArch64/error-location.s index 02504368f004b..c629e0a50de8b 100644 --- a/llvm/test/MC/AArch64/error-location.s +++ b/llvm/test/MC/AArch64/error-location.s @@ -16,9 +16,6 @@ // CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Cannot represent a difference across sections .word x_a - y_a -// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Cannot represent a subtraction with a weak symbol - .word a - w - // CHECK: :0: error: expression could not be evaluated .set v1, -undef diff --git a/llvm/test/MC/ARM/error-location.s b/llvm/test/MC/ARM/error-location.s index 112acf318ed33..2f70f294b57d2 100644 --- a/llvm/test/MC/ARM/error-location.s +++ b/llvm/test/MC/ARM/error-location.s @@ -16,9 +16,6 @@ @ CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Cannot represent a difference across sections .word x_a - y_a -@ CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Cannot represent a subtraction with a weak symbol - .word a - w - @ CHECK: :0: error: expression could not be evaluated .set v1, -undef diff --git a/llvm/test/MC/ELF/relocation.s b/llvm/test/MC/ELF/relocation.s index 0fec76792818b..e0313904563a4 100644 --- a/llvm/test/MC/ELF/relocation.s +++ b/llvm/test/MC/ELF/relocation.s @@ -63,6 +63,11 @@ pr24486: .code16 call pr23771 + .weak weak_sym +weak_sym: + .long pr23272-weak_sym + + // CHECK: Section { // CHECK: Name: .rela.text // CHECK: Relocations [ @@ -101,5 +106,6 @@ pr24486: // CHECK-NEXT: 0xDC R_X86_64_PLT32 foo 0x0 // CHECK-NEXT: 0xF0 R_X86_64_32 .text 0xF0 // CHECK-NEXT: 0xF5 R_X86_64_PC16 pr23771 0xFFFFFFFFFFFFFFFE +// CHECK-NEXT: 0xF7 R_X86_64_PC32 pr23272 0x0 // CHECK-NEXT: ] // CHECK-NEXT: } From a14779f504b02ad0e4dbc39d6d10cadc7ed4cfd0 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Fri, 29 Jan 2016 15:53:43 -0800 Subject: [PATCH 017/585] Introduce ns_error_domain attribute. ns_error_domain can be used by, e.g. NS_ERROR_ENUM, in order to identify a global declaration representing the domain constant. Introduces the attribute, Sema handling, diagnostics, and test case. apple-llvm-split-commit: b624ed1eb8de755bf3958612a2aae79d9bcdc4ed apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Attr.td | 6 +++ clang/include/clang/Basic/AttrDocs.td | 7 ++++ .../clang/Basic/DiagnosticSemaKinds.td | 8 ++++ clang/lib/Sema/SemaDeclAttr.cpp | 38 +++++++++++++++++ clang/test/Analysis/ns_error_enum.m | 42 +++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 clang/test/Analysis/ns_error_enum.m diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index e94bb1ae24bbc..8b5eb257aa58d 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1115,6 +1115,12 @@ def ObjCBridgeRelated : InheritableAttr { let Documentation = [Undocumented]; } +def NSErrorDomain : Attr { + let Spellings = [GNU<"ns_error_domain">]; + let Args = [IdentifierArgument<"ErrorDomain">]; + let Documentation = [NSErrorDomainDocs]; +} + def NSReturnsRetained : InheritableAttr { let Spellings = [GNU<"ns_returns_retained">]; // let Subjects = SubjectList<[ObjCMethod, ObjCProperty, Function]>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 5c4473d08d91b..08cd99b9867f1 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1758,6 +1758,13 @@ arguments, with arbitrary offsets. }]; } +def NSErrorDomainDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``ns_error_domain`` attribute indicates a global constant representing the error domain. + }]; +} + def SwiftDocs : DocumentationCategory<"Controlling Swift Import"> { let Content = [{ Clang supports additional attributes for controlling how APIs are imported into Swift. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ec992c72ae55c..d2f2b5bf611fd 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7533,6 +7533,14 @@ def err_nsconsumed_attribute_mismatch : Error< def err_nsreturns_retained_attribute_mismatch : Error< "overriding method has mismatched ns_returns_%select{not_retained|retained}0" " attributes">; + +def err_nserrordomain_not_tagdecl : Error< + "ns_error_domain attribute only valid on " + "%select{enums, structs, and unions|enums, structs, unions, and classes}0">; +def err_nserrordomain_invalid_decl : Error< + "domain argument %0 does not refer to global constant">; +def err_nserrordomain_requires_identifier : Error< + "domain argument must be an identifier">; def note_getter_unavailable : Note< "or because setter is declared here, but no getter method %0 is found">; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index d03686130f9b8..5449bc8184ce8 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4263,6 +4263,40 @@ static void handleObjCRequiresSuperAttr(Sema &S, Decl *D, attr.getAttributeSpellingListIndex())); } +static void handleNSErrorDomain(Sema &S, Decl *D, const AttributeList &Attr) { + if (!isa(D)) { + S.Diag(D->getLocStart(), diag::err_nserrordomain_not_tagdecl) + << S.getLangOpts().CPlusPlus; + return; + } + IdentifierLoc *identLoc = + Attr.isArgIdent(0) ? Attr.getArgAsIdent(0) : nullptr; + if (!identLoc || !identLoc->Ident) { + // Try to locate the argument directly + SourceLocation loc = Attr.getLoc(); + if (Attr.isArgExpr(0) && Attr.getArgAsExpr(0)) + loc = Attr.getArgAsExpr(0)->getLocStart(); + + S.Diag(loc, diag::err_nserrordomain_requires_identifier); + return; + } + + // Verify that the identifier is a valid decl in the C decl namespace + LookupResult lookupResult(S, DeclarationName(identLoc->Ident), + SourceLocation(), + Sema::LookupNameKind::LookupOrdinaryName); + if (!S.LookupName(lookupResult, S.TUScope) || + !lookupResult.getAsSingle()) { + S.Diag(identLoc->Loc, diag::err_nserrordomain_invalid_decl) + << identLoc->Ident; + return; + } + + D->addAttr(::new (S.Context) + NSErrorDomainAttr(Attr.getRange(), S.Context, identLoc->Ident, + Attr.getAttributeSpellingListIndex())); +} + static void handleCFAuditedTransferAttr(Sema &S, Decl *D, const AttributeList &Attr) { if (checkAttrMutualExclusion(S, D, Attr.getRange(), @@ -5560,6 +5594,10 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_ObjCBoxable: handleObjCBoxable(S, D, Attr); break; + + case AttributeList::AT_NSErrorDomain: + handleNSErrorDomain(S, D, Attr); + break; case AttributeList::AT_CFAuditedTransfer: handleCFAuditedTransferAttr(S, D, Attr); diff --git a/clang/test/Analysis/ns_error_enum.m b/clang/test/Analysis/ns_error_enum.m new file mode 100644 index 0000000000000..bf616291578bd --- /dev/null +++ b/clang/test/Analysis/ns_error_enum.m @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -verify %s + +#define CF_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#define NS_ENUM(_type, _name) CF_ENUM(_type, _name) + +#define NS_ERROR_ENUM(_type, _name, _domain) \ + enum _name : _type _name; enum __attribute__((ns_error_domain(_domain))) _name : _type + +typedef NS_ENUM(unsigned, MyEnum) { + MyFirst, + MySecond, +}; + +typedef NS_ENUM(invalidType, MyInvalidEnum) { +// expected-error@-1{{unknown type name 'invalidType'}} +// expected-error@-2{{unknown type name 'invalidType'}} + MyFirstInvalid, + MySecondInvalid, +}; + +const char *MyErrorDomain; +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnum, MyErrorDomain) { + MyErrFirst, + MyErrSecond, +}; +struct __attribute__((ns_error_domain(MyErrorDomain))) MyStructErrorDomain {}; + +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalid, InvalidDomain) { + // expected-error@-1{{domain argument 'InvalidDomain' does not refer to global constant}} + MyErrFirstInvalid, + MyErrSecondInvalid, +}; + +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalid, "domain-string"); + // expected-error@-1{{domain argument must be an identifier}} + +int __attribute__((ns_error_domain(MyErrorDomain))) NotTagDecl; + // expected-error@-1{{ns_error_domain attribute only valid on enums, structs, and unions}} + +void foo() {} +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalidFunction, foo); + // expected-error@-1{{domain argument 'foo' does not refer to global constant}} From 96ad82d49e7f20d47c2e393416c7af58201e8ed7 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Fri, 12 Feb 2016 11:49:25 -0800 Subject: [PATCH 018/585] [ADT] Fix PointerEmbeddedInt when the underlying type is uintptr_t. ...and when you try to store negative values in it. apple-llvm-split-commit: caeff488e6b6e64bedcdcf2959a4489d617819ab apple-llvm-split-dir: llvm/ --- llvm/include/llvm/ADT/PointerEmbeddedInt.h | 27 ++++++++--- llvm/unittests/ADT/PointerEmbeddedIntTest.cpp | 46 +++++++++++++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/llvm/include/llvm/ADT/PointerEmbeddedInt.h b/llvm/include/llvm/ADT/PointerEmbeddedInt.h index 9562819f28351..432d9e1c125ce 100644 --- a/llvm/include/llvm/ADT/PointerEmbeddedInt.h +++ b/llvm/include/llvm/ADT/PointerEmbeddedInt.h @@ -11,6 +11,7 @@ #define LLVM_ADT_POINTEREMBEDDEDINT_H #include "llvm/ADT/DenseMapInfo.h" +#include "llvm/Support/MathExtras.h" #include "llvm/Support/PointerLikeTypeTraits.h" #include @@ -30,6 +31,8 @@ template class PointerEmbeddedInt { uintptr_t Value; + // Note: This '<' is correct; using '<=' would result in some shifts + // overflowing their storage types. static_assert(Bits < sizeof(uintptr_t) * CHAR_BIT, "Cannot embed more bits than we have in a pointer!"); @@ -42,26 +45,36 @@ class PointerEmbeddedInt { Mask = static_cast(-1) << Bits }; + enum RawValue_t { + RawValue + }; + friend class PointerLikeTypeTraits; - explicit PointerEmbeddedInt(uintptr_t Value) : Value(Value) {} + explicit PointerEmbeddedInt(uintptr_t Value, RawValue_t) : Value(Value) {} public: PointerEmbeddedInt() : Value(0) {} - PointerEmbeddedInt(IntT I) : Value(static_cast(I) << Shift) { - assert((I & Mask) == 0 && "Integer has bits outside those preserved!"); + PointerEmbeddedInt(IntT I) { + *this = I; } PointerEmbeddedInt &operator=(IntT I) { - assert((I & Mask) == 0 && "Integer has bits outside those preserved!"); + assert((std::is_signed::value ? llvm::isInt(I) + : llvm::isUInt(I)) && + "Integer has bits outside those preserved!"); Value = static_cast(I) << Shift; return *this; } // Note that this implicit conversion additionally allows all of the basic // comparison operators to work transparently, etc. - operator IntT() const { return static_cast(Value >> Shift); } + operator IntT() const { + if (std::is_signed::value) + return static_cast(static_cast(Value) >> Shift); + return static_cast(Value >> Shift); + } }; // Provide pointer like traits to support use with pointer unions and sum @@ -75,10 +88,10 @@ class PointerLikeTypeTraits> { return reinterpret_cast(P.Value); } static inline T getFromVoidPointer(void *P) { - return T(reinterpret_cast(P)); + return T(reinterpret_cast(P), T::RawValue); } static inline T getFromVoidPointer(const void *P) { - return T(reinterpret_cast(P)); + return T(reinterpret_cast(P), T::RawValue); } enum { NumLowBitsAvailable = T::Shift }; diff --git a/llvm/unittests/ADT/PointerEmbeddedIntTest.cpp b/llvm/unittests/ADT/PointerEmbeddedIntTest.cpp index b10365a2f618e..21ed560a8a40a 100644 --- a/llvm/unittests/ADT/PointerEmbeddedIntTest.cpp +++ b/llvm/unittests/ADT/PointerEmbeddedIntTest.cpp @@ -43,4 +43,50 @@ TEST(PointerEmbeddedIntTest, Basic) { EXPECT_FALSE(42 >= J); } +TEST(PointerEmbeddedIntTest, intptr_t) { + { + PointerEmbeddedInt I = 42, J = -42; + EXPECT_EQ(42, I); + EXPECT_EQ(-42, J); + } + + { + PointerEmbeddedInt I = 42, J = 255; + EXPECT_EQ(42U, I); + EXPECT_EQ(255U, J); + } + + { + PointerEmbeddedInt::digits> + I = std::numeric_limits::max() >> 1, + J = std::numeric_limits::min() >> 1; + EXPECT_EQ(std::numeric_limits::max() >> 1, I); + EXPECT_EQ(std::numeric_limits::min() >> 1, J); + } + + { + PointerEmbeddedInt::digits - 1> + I = std::numeric_limits::max() >> 1, + J = std::numeric_limits::min() >> 1; + EXPECT_EQ(std::numeric_limits::max() >> 1, I); + EXPECT_EQ(std::numeric_limits::min() >> 1, J); + } +} + +TEST(PointerEmbeddedIntTest, PointerLikeTypeTraits) { + { + PointerEmbeddedInt I = 42; + using Traits = PointerLikeTypeTraits; + EXPECT_EQ(42, Traits::getFromVoidPointer(Traits::getAsVoidPointer(I))); + } + + { + PointerEmbeddedInt::digits - 1> + I = std::numeric_limits::max() >> 1; + using Traits = PointerLikeTypeTraits; + EXPECT_EQ(std::numeric_limits::max() >> 1, + Traits::getFromVoidPointer(Traits::getAsVoidPointer(I))); + } +} + } // end anonymous namespace From 163144e01437fd6b7819190fa09a0bc7b911e5b8 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Feb 2016 10:16:29 -0800 Subject: [PATCH 019/585] Fix for compiling with MSVC, from @vitalyster apple-llvm-split-commit: 4fec28d8b60eba6769e5d1d391b7dbd681026e19 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/SourceMgrAdapter.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/SourceMgrAdapter.h b/clang/include/clang/Basic/SourceMgrAdapter.h index 6782aebbeff58..dd7b83f1a5146 100644 --- a/clang/include/clang/Basic/SourceMgrAdapter.h +++ b/clang/include/clang/Basic/SourceMgrAdapter.h @@ -70,7 +70,9 @@ class SourceMgrAdapter { void handleDiag(const llvm::SMDiagnostic &diag); /// Retrieve the diagnostic handler to use with the underlying SourceMgr. - llvm::SourceMgr::DiagHandlerTy getDiagHandler() { return &handleDiag; } + llvm::SourceMgr::DiagHandlerTy getDiagHandler() { + return &SourceMgrAdapter::handleDiag; + } /// Retrieve the context to use with the diagnostic handler produced by /// \c getDiagHandler(). From 7dba415bcfd23ff631bda2f398c8da1eb7b1c9e5 Mon Sep 17 00:00:00 2001 From: Tim Northover Date: Thu, 18 Feb 2016 14:46:26 -0800 Subject: [PATCH 020/585] Fix botched merge apple-llvm-split-commit: 8719de3fbcadd7d722baa8dba84b4685251c28ff apple-llvm-split-dir: llvm/ --- llvm/include/llvm/ADT/PointerEmbeddedInt.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/llvm/include/llvm/ADT/PointerEmbeddedInt.h b/llvm/include/llvm/ADT/PointerEmbeddedInt.h index 894106d753005..546597f4577a2 100644 --- a/llvm/include/llvm/ADT/PointerEmbeddedInt.h +++ b/llvm/include/llvm/ADT/PointerEmbeddedInt.h @@ -60,11 +60,7 @@ class PointerEmbeddedInt { PointerEmbeddedInt &operator=(IntT I) { assert((std::is_signed::value ? llvm::isInt(I) -<<<<<<< HEAD - : llvm::isUInt(I)) && -======= : llvm::isUInt(I)) && ->>>>>>> upstream/master "Integer has bits outside those preserved!"); Value = static_cast(I) << Shift; return *this; From 0ea15e7d7749be6dcbf4c33c3a22a047ddc1ed45 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Feb 2016 16:35:59 -0800 Subject: [PATCH 021/585] Add -iapinotes-modules search path option. This option adds a new module-centric search path to find the API notes file that applies to the current module that needs to be built. It costs us only 2 stats per search path at module construction time to determine if API notes are available, making it far more efficient (and easier to use) than the prior API notes searching mechanism. It also fits much better with the future direction of the Swift Clang importer, which will soon delegate its responsibilities to Clang's API notes infrastructure. This is part of rdar://problem/24447420. apple-llvm-split-commit: 86e45f96e98198758e6fcdca764aaa2821004322 apple-llvm-split-dir: clang/ --- .../include/clang/APINotes/APINotesManager.h | 32 ++- .../include/clang/APINotes/APINotesOptions.h | 37 ++++ clang/include/clang/APINotes/APINotesReader.h | 2 +- clang/include/clang/Driver/Options.td | 2 + .../include/clang/Frontend/CompilerInstance.h | 7 + .../clang/Frontend/CompilerInvocation.h | 9 + clang/lib/APINotes/APINotesManager.cpp | 200 +++++++++++------- clang/lib/Driver/Tools.cpp | 6 +- clang/lib/Frontend/CompilerInstance.cpp | 7 + clang/lib/Frontend/CompilerInvocation.cpp | 8 + clang/lib/Sema/Sema.cpp | 3 +- clang/lib/Sema/SemaAPINotes.cpp | 5 +- .../Inputs/APINotes/HeaderLib.apinotes | 17 ++ .../APINotes/Inputs/APINotes/SomeKit.apinotes | 34 +++ .../Modules/module.modulemap | 5 + .../Modules/module.private.modulemap | 8 + .../Modules/module_private.modulemap | 8 + .../APINotes/Inputs/Headers/module.modulemap | 3 + clang/test/APINotes/availability.m | 3 +- clang/test/APINotes/nullability.c | 2 +- clang/test/APINotes/nullability.m | 2 +- clang/test/APINotes/objc_designated_inits.m | 2 +- 22 files changed, 309 insertions(+), 93 deletions(-) create mode 100644 clang/include/clang/APINotes/APINotesOptions.h create mode 100644 clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes create mode 100644 clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module.modulemap create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module.private.modulemap create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module_private.modulemap create mode 100644 clang/test/APINotes/Inputs/Headers/module.modulemap diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h index 503b36536e621..69752a9c77e90 100644 --- a/clang/include/clang/APINotes/APINotesManager.h +++ b/clang/include/clang/APINotes/APINotesManager.h @@ -7,7 +7,7 @@ // //===----------------------------------------------------------------------===// // -// This file defines the HeaderSearch interface. +// This file defines the APINotesManager interface. // //===----------------------------------------------------------------------===// @@ -15,15 +15,18 @@ #define LLVM_CLANG_APINOTES_APINOTESMANAGER_H #include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/PointerUnion.h" #include "llvm/ADT/StringRef.h" #include +#include namespace clang { class DirectoryEntry; class FileEntry; +class LangOptions; class SourceManager; namespace api_notes { @@ -47,6 +50,13 @@ class APINotesManager { SourceManager &SourceMgr; + /// Whether to implicitly search for API notes files based on the + /// source file from which an entity was declared. + bool ImplicitAPINotes; + + /// The API notes reader for the current module. + std::unique_ptr CurrentModuleReader; + /// Whether we have already pruned the API notes cache. bool PrunedCache; @@ -56,6 +66,13 @@ class APINotesManager { /// reader for this directory. llvm::DenseMap Readers; + /// Load the API notes associated with the given file, whether it is + /// the binary or source form of API notes. + /// + /// \returns the API notes reader for this file, or null if there is + /// a failure. + std::unique_ptr loadAPINotes(const FileEntry *apiNotesFile); + /// Load the given API notes file for the given header directory. /// /// \param HeaderDir The directory at which we @@ -78,9 +95,20 @@ class APINotesManager { bool Public); public: - APINotesManager(SourceManager &SourceMgr); + APINotesManager(SourceManager &sourceMgr, const LangOptions &langOpts); ~APINotesManager(); + /// Load the API notes for the current module. + /// + /// \param moduleName The name of the current module. + /// \param searchPaths The paths in which we should search for API notes + /// for the current module. + /// + /// \returns the file entry for the API notes file loaded, or nullptr if + /// no API notes were found. + const FileEntry *loadCurrentModuleAPINotes(StringRef moduleName, + ArrayRef searchPaths); + /// Find the API notes reader that corresponds to the given source location. APINotesReader *findAPINotes(SourceLocation Loc); }; diff --git a/clang/include/clang/APINotes/APINotesOptions.h b/clang/include/clang/APINotes/APINotesOptions.h new file mode 100644 index 0000000000000..01c7513a1d317 --- /dev/null +++ b/clang/include/clang/APINotes/APINotesOptions.h @@ -0,0 +1,37 @@ +//===--- APINotesOptions.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the APINotesOptions class. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_APINOTES_APINOTESOPTIONS_H +#define LLVM_CLANG_APINOTES_APINOTESOPTIONS_H + +#include +#include + +namespace clang { + +/// APINotesOptions - Track various options which control how API +/// notes are found and handled. +class APINotesOptions { +public: + /// The set of search paths where we API notes can be found for + /// particular modules. + /// + /// The API notes in this directory are stored as + /// .apinotes or .apinotesc, and are only + /// applied when building the module . + std::vector ModuleSearchPaths; +}; + +} // end namespace clang + +#endif diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 77b5f16bb42f4..4759f85c1ab48 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -38,7 +38,7 @@ class APINotesReader { /// contains the contents of a binary API notes file. /// /// \returns the new API notes reader, or null if an error occurred. - static std::unique_ptr + static std::unique_ptr get(std::unique_ptr inputBuffer); ~APINotesReader(); diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index ec8a2c4b80c54..923eaa6d75177 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1216,6 +1216,8 @@ def help : Flag<["-", "--"], "help">, Flags<[CC1Option,CC1AsOption]>, HelpText<"Display available options">; def index_header_map : Flag<["-"], "index-header-map">, Flags<[CC1Option]>, HelpText<"Make the next included directory (-I or -F) an indexer header map">; +def iapinotes_modules : JoinedOrSeparate<["-"], "iapinotes-modules">, Group, Flags<[CC1Option]>, + HelpText<"Add directory to the API notes search path referenced by module name">, MetaVarName<"">; def idirafter : JoinedOrSeparate<["-"], "idirafter">, Group, Flags<[CC1Option]>, HelpText<"Add directory to AFTER include search path">; def iframework : JoinedOrSeparate<["-"], "iframework">, Group, Flags<[CC1Option]>, diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h index 83eed2cdc5921..798cc2ddec18f 100644 --- a/clang/include/clang/Frontend/CompilerInstance.h +++ b/clang/include/clang/Frontend/CompilerInstance.h @@ -291,6 +291,13 @@ class CompilerInstance : public ModuleLoader { return Invocation->getHeaderSearchOpts(); } + APINotesOptions &getAPINotesOpts() { + return Invocation->getAPINotesOpts(); + } + const APINotesOptions &getAPINotesOpts() const { + return Invocation->getAPINotesOpts(); + } + LangOptions &getLangOpts() { return *Invocation->getLangOpts(); } diff --git a/clang/include/clang/Frontend/CompilerInvocation.h b/clang/include/clang/Frontend/CompilerInvocation.h index 0b4a1e587e7e4..fa04af2762721 100644 --- a/clang/include/clang/Frontend/CompilerInvocation.h +++ b/clang/include/clang/Frontend/CompilerInvocation.h @@ -10,6 +10,7 @@ #ifndef LLVM_CLANG_FRONTEND_COMPILERINVOCATION_H_ #define LLVM_CLANG_FRONTEND_COMPILERINVOCATION_H_ +#include "clang/APINotes/APINotesOptions.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileSystemOptions.h" #include "clang/Basic/LangOptions.h" @@ -105,6 +106,9 @@ class CompilerInvocation : public CompilerInvocationBase { MigratorOptions MigratorOpts; + /// Options controlling API notes. + APINotesOptions APINotesOpts; + /// Options controlling IRgen and the backend. CodeGenOptions CodeGenOpts; @@ -173,6 +177,11 @@ class CompilerInvocation : public CompilerInvocationBase { const MigratorOptions &getMigratorOpts() const { return MigratorOpts; } + + APINotesOptions &getAPINotesOpts() { return APINotesOpts; } + const APINotesOptions &getAPINotesOpts() const { + return APINotesOpts; + } CodeGenOptions &getCodeGenOpts() { return CodeGenOpts; } const CodeGenOptions &getCodeGenOpts() const { diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index 50736939a60c1..3c3d5fdfc4101 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -12,10 +12,12 @@ //===----------------------------------------------------------------------===// #include "clang/APINotes/APINotesManager.h" +#include "clang/APINotes/APINotesOptions.h" #include "clang/APINotes/APINotesReader.h" #include "clang/APINotes/APINotesYAMLCompiler.h" #include "clang/Basic/DiagnosticIDs.h" #include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/SourceMgrAdapter.h" #include "clang/Basic/Version.h" @@ -49,9 +51,10 @@ STATISTIC(NumBinaryCacheMisses, STATISTIC(NumBinaryCacheRebuilds, "binary form cache rebuilds"); -APINotesManager::APINotesManager(SourceManager &SourceMgr) - : SourceMgr(SourceMgr), PrunedCache(false) { } - +APINotesManager::APINotesManager(SourceManager &sourceMgr, + const LangOptions &langOpts) + : SourceMgr(sourceMgr), ImplicitAPINotes(langOpts.APINotes), + PrunedCache(false) { } APINotesManager::~APINotesManager() { // Free the API notes readers. @@ -131,155 +134,145 @@ static void pruneAPINotesCache(StringRef APINotesCachePath) { } } -bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir, - const FileEntry *APINotesFile) { - assert(Readers.find(HeaderDir) == Readers.end()); - - FileManager &FileMgr = SourceMgr.getFileManager(); +std::unique_ptr +APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { + FileManager &fileMgr = SourceMgr.getFileManager(); // If the API notes file is already in the binary form, load it directly. - StringRef APINotesFileName = APINotesFile->getName(); - StringRef APINotesFileExt = llvm::sys::path::extension(APINotesFileName); - if (!APINotesFileExt.empty() && - APINotesFileExt.substr(1) == BINARY_APINOTES_EXTENSION) { + StringRef apiNotesFileName = apiNotesFile->getName(); + StringRef apiNotesFileExt = llvm::sys::path::extension(apiNotesFileName); + if (!apiNotesFileExt.empty() && + apiNotesFileExt.substr(1) == BINARY_APINOTES_EXTENSION) { // Load the file. - auto Buffer = FileMgr.getBufferForFile(APINotesFile); - if (!Buffer) { - Readers[HeaderDir] = nullptr; - return true; - } + auto buffer = fileMgr.getBufferForFile(apiNotesFile); + if (!buffer) return nullptr; // Load the binary form. - auto Reader = APINotesReader::get(std::move(Buffer.get())); - if (!Reader) { - Readers[HeaderDir] = nullptr; - return true; - } - - // Record the reader. - Readers[HeaderDir] = Reader.release(); - return false; + return APINotesReader::get(std::move(buffer.get())); } // If we haven't pruned the API notes cache yet during this execution, do // so now. if (!PrunedCache) { - pruneAPINotesCache(FileMgr.getFileSystemOpts().APINotesCachePath); + pruneAPINotesCache(fileMgr.getFileSystemOpts().APINotesCachePath); PrunedCache = true; } // Compute a hash of the API notes file's directory and the Clang version, // to be used as part of the filename for the cached binary copy. - auto code = llvm::hash_value(StringRef(APINotesFile->getDir()->getName())); + auto code = llvm::hash_value(StringRef(apiNotesFile->getDir()->getName())); code = hash_combine(code, getClangFullRepositoryVersion()); // Determine the file name for the cached binary form. - SmallString<128> CompiledFileName; - CompiledFileName += FileMgr.getFileSystemOpts().APINotesCachePath; - assert(!CompiledFileName.empty() && "No API notes cache path provided?"); - llvm::sys::path::append(CompiledFileName, - (llvm::Twine(llvm::sys::path::stem(APINotesFileName)) + "-" + SmallString<128> compiledFileName; + compiledFileName += fileMgr.getFileSystemOpts().APINotesCachePath; + assert(!compiledFileName.empty() && "No API notes cache path provided?"); + llvm::sys::path::append(compiledFileName, + (llvm::Twine(llvm::sys::path::stem(apiNotesFileName)) + "-" + llvm::APInt(64, code).toString(36, /*Signed=*/false) + "." + BINARY_APINOTES_EXTENSION)); // Try to open the cached binary form. - if (const FileEntry *CompiledFile = FileMgr.getFile(CompiledFileName, + if (const FileEntry *compiledFile = fileMgr.getFile(compiledFileName, /*openFile=*/true, /*cacheFailure=*/false)) { // Load the file contents. - if (auto Buffer = FileMgr.getBufferForFile(CompiledFile)) { + if (auto buffer = fileMgr.getBufferForFile(compiledFile)) { // Make sure the file is up-to-date. - if (CompiledFile->getModificationTime() - >= APINotesFile->getModificationTime()) { + if (compiledFile->getModificationTime() + >= apiNotesFile->getModificationTime()) { // Load the file. - if (auto Reader = APINotesReader::get(std::move(Buffer.get()))) { + if (auto reader = APINotesReader::get(std::move(buffer.get()))) { // Success. ++NumBinaryCacheHits; - Readers[HeaderDir] = Reader.release(); - return false; + return reader; } } } // The cache entry was somehow broken; delete this one so we can build a // new one below. - llvm::sys::fs::remove(CompiledFileName.str()); + llvm::sys::fs::remove(compiledFileName.str()); ++NumBinaryCacheRebuilds; } else { ++NumBinaryCacheMisses; } // Open the source file. - auto Buffer = FileMgr.getBufferForFile(APINotesFile); - if (!Buffer) { - Readers[HeaderDir] = nullptr; - return true; - } + auto buffer = fileMgr.getBufferForFile(apiNotesFile); + if (!buffer) return nullptr; // Compile the API notes source into a buffer. // FIXME: Either propagate OSType through or, better yet, improve the binary // APINotes format to maintain complete availability information. - llvm::SmallVector APINotesBuffer; + llvm::SmallVector apiNotesBuffer; { SourceMgrAdapter srcMgrAdapter(SourceMgr, SourceMgr.getDiagnostics(), diag::err_apinotes_message, diag::warn_apinotes_message, diag::note_apinotes_message, - APINotesFile); - llvm::raw_svector_ostream OS(APINotesBuffer); - if (api_notes::compileAPINotes(Buffer.get()->getBuffer(), + apiNotesFile); + llvm::raw_svector_ostream OS(apiNotesBuffer); + if (api_notes::compileAPINotes(buffer.get()->getBuffer(), OS, api_notes::OSType::Absent, srcMgrAdapter.getDiagHandler(), - srcMgrAdapter.getDiagContext())) { - Readers[HeaderDir] = nullptr; - return true; - } + srcMgrAdapter.getDiagContext())) + return nullptr; // Make a copy of the compiled form into the buffer. - Buffer = llvm::MemoryBuffer::getMemBufferCopy( - StringRef(APINotesBuffer.data(), APINotesBuffer.size())); + buffer = llvm::MemoryBuffer::getMemBufferCopy( + StringRef(apiNotesBuffer.data(), apiNotesBuffer.size())); } // Save the binary form into the cache. Perform this operation // atomically. - SmallString<64> TemporaryBinaryFileName = CompiledFileName.str(); - TemporaryBinaryFileName.erase( - TemporaryBinaryFileName.end() - - llvm::sys::path::extension(TemporaryBinaryFileName).size(), - TemporaryBinaryFileName.end()); - TemporaryBinaryFileName += "-%%%%%%."; - TemporaryBinaryFileName += BINARY_APINOTES_EXTENSION; - - int TemporaryFD; + SmallString<64> temporaryBinaryFileName = compiledFileName.str(); + temporaryBinaryFileName.erase( + temporaryBinaryFileName.end() + - llvm::sys::path::extension(temporaryBinaryFileName).size(), + temporaryBinaryFileName.end()); + temporaryBinaryFileName += "-%%%%%%."; + temporaryBinaryFileName += BINARY_APINOTES_EXTENSION; + + int temporaryFD; llvm::sys::fs::create_directories( - FileMgr.getFileSystemOpts().APINotesCachePath); - if (!llvm::sys::fs::createUniqueFile(TemporaryBinaryFileName.str(), - TemporaryFD, TemporaryBinaryFileName)) { + fileMgr.getFileSystemOpts().APINotesCachePath); + if (!llvm::sys::fs::createUniqueFile(temporaryBinaryFileName.str(), + temporaryFD, temporaryBinaryFileName)) { // Write the contents of the buffer. bool hadError; { - llvm::raw_fd_ostream Out(TemporaryFD, /*shouldClose=*/true); - Out.write(Buffer.get()->getBufferStart(), Buffer.get()->getBufferSize()); - Out.flush(); + llvm::raw_fd_ostream out(temporaryFD, /*shouldClose=*/true); + out.write(buffer.get()->getBufferStart(), buffer.get()->getBufferSize()); + out.flush(); - hadError = Out.has_error(); + hadError = out.has_error(); } if (!hadError) { // Rename the temporary file to the actual compiled file. - llvm::sys::fs::rename(TemporaryBinaryFileName.str(), - CompiledFileName.str()); + llvm::sys::fs::rename(temporaryBinaryFileName.str(), + compiledFileName.str()); } } // Load the binary form we just compiled. - auto Reader = APINotesReader::get(std::move(*Buffer)); - assert(Reader && "Could not load the API notes we just generated?"); + auto reader = APINotesReader::get(std::move(*buffer)); + assert(reader && "Could not load the API notes we just generated?"); + return reader; +} - // Record the reader. - Readers[HeaderDir] = Reader.release(); - return false; +bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir, + const FileEntry *APINotesFile) { + assert(Readers.find(HeaderDir) == Readers.end()); + if (auto reader = loadAPINotes(APINotesFile)) { + Readers[HeaderDir] = reader.release(); + return false; + } + + Readers[HeaderDir] = nullptr; + return true; } const DirectoryEntry *APINotesManager::loadFrameworkAPINotes( @@ -332,7 +325,56 @@ const DirectoryEntry *APINotesManager::loadFrameworkAPINotes( return HeaderDir; } +const FileEntry *APINotesManager::loadCurrentModuleAPINotes( + StringRef moduleName, + ArrayRef searchPaths) { + assert(!CurrentModuleReader && + "Already loaded API notes for the current module?"); + + FileManager &fileMgr = SourceMgr.getFileManager(); + + // Look for API notes for this module in the module search paths. + for (const auto &searchPath : searchPaths) { + // First, look for a binary API notes file. + llvm::SmallString<128> apiNotesFilePath; + apiNotesFilePath += searchPath; + llvm::sys::path::append( + apiNotesFilePath, + llvm::Twine(moduleName) + "." + BINARY_APINOTES_EXTENSION); + + // Try to open the binary API Notes file. + if (const FileEntry *binaryAPINotesFile + = fileMgr.getFile(apiNotesFilePath)) { + CurrentModuleReader = loadAPINotes(binaryAPINotesFile); + return CurrentModuleReader ? binaryAPINotesFile : nullptr; + } + + // Try to open the source API Notes file. + apiNotesFilePath = searchPath; + llvm::sys::path::append( + apiNotesFilePath, + llvm::Twine(moduleName) + "." + SOURCE_APINOTES_EXTENSION); + if (const FileEntry *sourceAPINotesFile + = fileMgr.getFile(apiNotesFilePath)) { + CurrentModuleReader = loadAPINotes(sourceAPINotesFile); + return CurrentModuleReader ? sourceAPINotesFile : nullptr; + } + } + + // Didn't find any API notes. + return nullptr; +} + APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) { + // If there is a reader for the current module, return it. + if (CurrentModuleReader) return CurrentModuleReader.get(); + + // If we're not allowed to implicitly load API notes files, we're done. + if (!ImplicitAPINotes) return nullptr; + + // If we don't have source location information, we're done. + if (Loc.isInvalid()) return nullptr; + // API notes are associated with the expansion location. Retrieve the // file for this location. SourceLocation ExpansionLoc = SourceMgr.getExpansionLoc(Loc); diff --git a/clang/lib/Driver/Tools.cpp b/clang/lib/Driver/Tools.cpp index eca5b03a7d562..50e3d7cd08025 100644 --- a/clang/lib/Driver/Tools.cpp +++ b/clang/lib/Driver/Tools.cpp @@ -4855,8 +4855,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back("-fno-assume-sane-operator-new"); if (Args.hasFlag(options::OPT_fapinotes, options::OPT_fno_apinotes, - false)) { - CmdArgs.push_back("-fapinotes"); + false) || + Args.hasArg(options::OPT_iapinotes_modules)) { + if (Args.hasFlag(options::OPT_fapinotes, options::OPT_fno_apinotes, false)) + CmdArgs.push_back("-fapinotes"); SmallString<128> APINotesCachePath; if (Arg *A = Args.getLastArg(options::OPT_fapinotes_cache_path)) { diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 7cb86cfa9f4b6..cd52cd408cdd1 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -531,6 +531,13 @@ void CompilerInstance::createSema(TranslationUnitKind TUKind, CodeCompleteConsumer *CompletionConsumer) { TheSema.reset(new Sema(getPreprocessor(), getASTContext(), getASTConsumer(), TUKind, CompletionConsumer)); + + // If we're building a module, notify the API notes manager. + if (!getLangOpts().CurrentModule.empty()) { + (void)TheSema->APINotes.loadCurrentModuleAPINotes( + getLangOpts().CurrentModule, + getAPINotesOpts().ModuleSearchPaths); + } } // Output Files diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index b0fe4c7a092f8..3d72900902cd6 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1332,6 +1332,12 @@ static void ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args) { Opts.AddVFSOverlayFile(A->getValue()); } +static void ParseAPINotesArgs(APINotesOptions &Opts, ArgList &Args) { + using namespace options; + for (const Arg *A : Args.filtered(OPT_iapinotes_modules)) + Opts.ModuleSearchPaths.push_back(A->getValue()); +} + void CompilerInvocation::setLangDefaults(LangOptions &Opts, InputKind IK, LangStandard::Kind LangStd) { // Set some properties which depend solely on the input kind; it would be nice @@ -2117,6 +2123,8 @@ bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Res, Success &= ParseCodeGenArgs(Res.getCodeGenOpts(), Args, DashX, Diags, Res.getTargetOpts()); ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args); + ParseAPINotesArgs(Res.getAPINotesOpts(), Args); + if (DashX == IK_AST || DashX == IK_LLVM_IR) { // ObjCAAutoRefCount and Sanitize LangOpts are used to setup the // PassManager in BackendUtil.cpp. They need to be initializd no matter diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 34261f2556cf7..5b93d73c847b5 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -77,7 +77,8 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, isMultiplexExternalSource(false), FPFeatures(pp.getLangOpts()), LangOpts(pp.getLangOpts()), PP(pp), Context(ctxt), Consumer(consumer), Diags(PP.getDiagnostics()), SourceMgr(PP.getSourceManager()), - APINotes(SourceMgr), CollectStats(false), CodeCompleter(CodeCompleter), + APINotes(SourceMgr, LangOpts), CollectStats(false), + CodeCompleter(CodeCompleter), CurContext(nullptr), OriginalLexicalContext(nullptr), PackContext(nullptr), MSStructPragmaOn(false), MSPointerToMemberRepresentationMethod( diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 3114805da208b..7817754c9f298 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -208,10 +208,7 @@ static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, /// Process API notes that are associated with this declaration, mapping them /// to attributes as appropriate. void Sema::ProcessAPINotes(Decl *D) { - if (!Context.getLangOpts().APINotes) - return; - - if (!D || D->getLocation().isInvalid()) + if (!D) return; // Globals. diff --git a/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes b/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes new file mode 100644 index 0000000000000..a4ddafe2892ec --- /dev/null +++ b/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes @@ -0,0 +1,17 @@ +Name: HeaderLib +Functions: + - Name: custom_realloc + NullabilityOfRet: N + Nullability: [ N, S ] + - Name: unavailable_function + Availability: none + AvailabilityMsg: "I beg you not to use this" + - Name: do_something_with_pointers + NullabilityOfRet: O + Nullability: [ N, O ] + +Globals: + - Name: global_int + Nullability: N + - Name: unavailable_global_int + Availability: none diff --git a/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes new file mode 100644 index 0000000000000..e700412012688 --- /dev/null +++ b/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes @@ -0,0 +1,34 @@ +Name: SomeKit +Classes: + - Name: A + Methods: + - Selector: "transform:" + MethodKind: Instance + Availability: none + AvailabilityMsg: "anything but this" + - Selector: "transform:integer:" + MethodKind: Instance + NullabilityOfRet: N + Nullability: [ N, S ] + - Selector: "privateTransform:input:" + MethodKind: Instance + NullabilityOfRet: N + Nullability: [ N, S ] + Properties: + - Name: intValue + Availability: none + AvailabilityMsg: "wouldn't work anyway" + - Name: internalProperty + Nullability: N + - Name: B + Availability: none + AvailabilityMsg: "just don't" + - Name: C + Methods: + - Selector: "initWithA:" + MethodKind: Instance + DesignatedInit: true +Protocols: + - Name: InternalProtocol + Availability: none + AvailabilityMsg: "not for you" diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module.modulemap b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module.modulemap new file mode 100644 index 0000000000000..3abee2df0be1b --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module SomeKit { + umbrella header "SomeKit.h" + export * + module * { export * } +} diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module.private.modulemap b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module.private.modulemap new file mode 100644 index 0000000000000..bbda9d08e3993 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module.private.modulemap @@ -0,0 +1,8 @@ +module SomeKit.Private { + header "SomeKit_Private.h" + export * + + explicit module NullAnnotation { + header "SomeKit_PrivateForNullAnnotation.h" + } +} diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module_private.modulemap b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module_private.modulemap new file mode 100644 index 0000000000000..e31034317cb82 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Modules/module_private.modulemap @@ -0,0 +1,8 @@ +explicit framework module SomeKit.Private { + header "SomeKit_Private.h" + explicit NullAnnotation { header "SomeKit_PrivateForNullAnnotation.h" } + export * + module * { export * } +syntax error + +} diff --git a/clang/test/APINotes/Inputs/Headers/module.modulemap b/clang/test/APINotes/Inputs/Headers/module.modulemap new file mode 100644 index 0000000000000..3e59efcf2c482 --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/module.modulemap @@ -0,0 +1,3 @@ +module HeaderLib { + header "HeaderLib.h" +} diff --git a/clang/test/APINotes/availability.m b/clang/test/APINotes/availability.m index 177700d63111d..5b996ec8c9497 100644 --- a/clang/test/APINotes/availability.m +++ b/clang/test/APINotes/availability.m @@ -1,4 +1,5 @@ -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: rm -rf %t +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #include "HeaderLib.h" #import diff --git a/clang/test/APINotes/nullability.c b/clang/test/APINotes/nullability.c index 940587e8e0a12..54b0df6cea94c 100644 --- a/clang/test/APINotes/nullability.c +++ b/clang/test/APINotes/nullability.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #include "HeaderLib.h" diff --git a/clang/test/APINotes/nullability.m b/clang/test/APINotes/nullability.m index 40901f2c4fd76..ba51cd64d825f 100644 --- a/clang/test/APINotes/nullability.m +++ b/clang/test/APINotes/nullability.m @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #import diff --git a/clang/test/APINotes/objc_designated_inits.m b/clang/test/APINotes/objc_designated_inits.m index 194d135b6c0da..5eb5fa103d5d5 100644 --- a/clang/test/APINotes/objc_designated_inits.m +++ b/clang/test/APINotes/objc_designated_inits.m @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #include "HeaderLib.h" #import From 68da16cdc07ee31751b5cae9fba3818766844d21 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Feb 2016 16:36:27 -0800 Subject: [PATCH 022/585] Map FactoryAsInit: C to a hidden attribute. The FactoryAsInit entry in API notes wasn't getting mapped to any Clang attributes. Since there is no use for such an attribute purely in Objective-C, map it to a new unspellable attribute (SwiftSuppressFactoryAsInitAttr) used only to appropriately annotate the declaration for consumption by the Swift Clang importer. Part of rdar://problem/24447420. apple-llvm-split-commit: 726aaee07233449a0ade9c09708eb3141d3af865 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Attr.td | 8 ++++++++ clang/lib/Sema/SemaAPINotes.cpp | 6 ++++++ clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes | 6 ++++++ .../Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h | 4 ++++ 4 files changed, 24 insertions(+) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 50fe90c2fc2e4..b4906b8c941c3 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1334,6 +1334,14 @@ def SwiftPrivate : InheritableAttr { let Documentation = [Undocumented]; } +def SwiftSuppressFactoryAsInit : InheritableAttr { + // This attribute has no spellings as it is only ever created implicitly + // from API notes. + let Spellings = []; + let SemaHandler = 0; + let Documentation = [Undocumented]; +} + def ReqdWorkGroupSize : InheritableAttr { let Spellings = [GNU<"reqd_work_group_size">]; let Args = [UnsignedArgument<"XDim">, UnsignedArgument<"YDim">, diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 7817754c9f298..8ca364eec198d 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -192,6 +192,12 @@ static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, } } + if (Info.getFactoryAsInitKind() + == api_notes::FactoryAsInitKind::AsClassMethod && + !D->getAttr()) { + D->addAttr(SwiftSuppressFactoryAsInitAttr::CreateImplicit(S.Context)); + } + // Handle common function information. ProcessAPINotes(S, FunctionOrMethod(D), static_cast(Info)); diff --git a/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes index e700412012688..d251491ed43db 100644 --- a/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes @@ -28,6 +28,12 @@ Classes: - Selector: "initWithA:" MethodKind: Instance DesignatedInit: true + - Name: ProcessInfo + Methods: + - Selector: "processInfo" + MethodKind: Class + FactoryAsInit: C + Protocols: - Name: InternalProtocol Availability: none diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h index 01b003d1eeef0..5e0d7e0b7ab26 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h @@ -20,4 +20,8 @@ __attribute__((objc_root_class)) - (instancetype)initWithA:(A*)a; @end +@interface ProcessInfo : A ++(instancetype)processInfo; +@end + #endif From 38ca04ce3ef9a72e0add8a693c15a86ffdf77429 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Feb 2016 16:39:30 -0800 Subject: [PATCH 023/585] Add "nonswift" availability mapping to Swift unavailability. The new "nonswift" availability maps to __attribute__((swift,unavailable)). Part of rdar://problem/24447420. apple-llvm-split-commit: c28028f35f1d0a2c573238d62ed3e969cc4aab05 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 16 ++++++++++++++-- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 4 +++- clang/lib/APINotes/APINotesWriter.cpp | 2 +- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 14 +++++++++++--- clang/lib/Sema/SemaAPINotes.cpp | 10 ++++++++++ 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 8173bf4f3a25b..3cf149357bb4a 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -67,12 +67,16 @@ class CommonEntityInfo { /// Whether this entity is marked unavailable. unsigned Unavailable : 1; - CommonEntityInfo() : Unavailable(0) { } + /// Whether this entity is marked unavailable in Swift. + unsigned UnavailableInSwift : 1; + + CommonEntityInfo() : Unavailable(0), UnavailableInSwift(0) { } friend bool operator==(const CommonEntityInfo &lhs, const CommonEntityInfo &rhs) { return lhs.UnavailableMsg == rhs.UnavailableMsg && - lhs.Unavailable == rhs.Unavailable; + lhs.Unavailable == rhs.Unavailable && + lhs.UnavailableInSwift == rhs.UnavailableInSwift; } friend bool operator!=(const CommonEntityInfo &lhs, @@ -91,6 +95,14 @@ class CommonEntityInfo { } } + if (rhs.UnavailableInSwift) { + lhs.UnavailableInSwift = true; + if (rhs.UnavailableMsg.length() != 0 && + lhs.UnavailableMsg.length() == 0) { + lhs.UnavailableMsg = rhs.UnavailableMsg; + } + } + return lhs; } diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 5462e5a558625..0801d2ceefd97 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -35,7 +35,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 6; +const uint16_t VERSION_MINOR = 7; using IdentifierID = Fixnum<31>; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index a84f2d70491fe..c768f3a3df7f7 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -30,7 +30,9 @@ using namespace llvm; namespace { /// Read serialized CommonEntityInfo. void readCommonEntityInfo(const uint8_t *&data, CommonEntityInfo &info) { - info.Unavailable = *data++; + uint8_t unavailableBits = *data++; + info.Unavailable = (unavailableBits >> 1) & 0x01; + info.UnavailableInSwift = unavailableBits & 0x01; unsigned msgLength = endian::readNext(data); info.UnavailableMsg diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 400bd08e01393..0e498a797a3ea 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -272,7 +272,7 @@ namespace { static void emitCommonEntityInfo(raw_ostream &out, const CommonEntityInfo &info) { endian::Writer writer(out); - writer.write(info.Unavailable); + writer.write(info.Unavailable << 1 | info.UnavailableInSwift); writer.write(info.UnavailableMsg.size()); out.write(info.UnavailableMsg.c_str(), info.UnavailableMsg.size()); } diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 1f299873ce31c..8c27a01e5edde 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -44,7 +44,7 @@ Availability: OSX # Optional: Specifies which platform the API is # available on. [OSX / iOS / none/ - # available] + # available / nonswift] AvailabilityMsg: "" # Optional: Custom availability message to display to # the user, when API is not available. @@ -143,6 +143,7 @@ namespace { OSX, IOS, None, + NonSwift, }; enum class MethodKind { @@ -264,6 +265,7 @@ namespace llvm { io.enumCase(value, "OSX", APIAvailability::OSX); io.enumCase(value, "iOS", APIAvailability::IOS); io.enumCase(value, "none", APIAvailability::None); + io.enumCase(value, "nonswift", APIAvailability::NonSwift); io.enumCase(value, "available", APIAvailability::Available); } }; @@ -410,9 +412,10 @@ static bool compile(const Module &module, bool convertAvailability(const AvailabilityItem &in, CommonEntityInfo &outInfo, llvm::StringRef apiName) { - // Populate the 'Unavailable' information. + // Populate the unavailability information. outInfo.Unavailable = (in.Mode == APIAvailability::None); - if (outInfo.Unavailable) { + outInfo.UnavailableInSwift = (in.Mode == APIAvailability::NonSwift); + if (outInfo.Unavailable || outInfo.UnavailableInSwift) { outInfo.UnavailableMsg = in.Msg; } else { if (!in.Msg.empty()) { @@ -716,6 +719,11 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, availability.Mode = APIAvailability::None; availability.Msg = copyString(info.UnavailableMsg); } + + if (info.UnavailableInSwift) { + availability.Mode = APIAvailability::NonSwift; + availability.Msg = copyString(info.UnavailableMsg); + } } /// Map nullability information for a function. diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 8ca364eec198d..6f08fd4c923dd 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -99,6 +99,16 @@ static void ProcessAPINotes(Sema &S, Decl *D, if (Info.Unavailable && !D->hasAttr()) { D->addAttr(UnavailableAttr::CreateImplicit(S.Context, Info.UnavailableMsg)); } + + if (Info.UnavailableInSwift) { + D->addAttr(AvailabilityAttr::CreateImplicit(S.Context, + &S.Context.Idents.get("swift"), + VersionTuple(), + VersionTuple(), + VersionTuple(), + /*Unavailable=*/true, + Info.UnavailableMsg)); + } } /// Process API notes for a variable or property. From 2400d6de15e12c3dc1760d8f3a89a0e9640e5051 Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Sat, 20 Feb 2016 09:05:43 -0800 Subject: [PATCH 024/585] [SemaAPINotes] Fix compiler error after change of r261163. apple-llvm-split-commit: 1ffcce8fb7b5a90aec255fd1908933c80d5925ad apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaAPINotes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 6f08fd4c923dd..2f4356b71b702 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -107,7 +107,8 @@ static void ProcessAPINotes(Sema &S, Decl *D, VersionTuple(), VersionTuple(), /*Unavailable=*/true, - Info.UnavailableMsg)); + Info.UnavailableMsg, + /*Nopartial=*/true)); } } From 91b20c157854dbbf05c77c36614ce8631d44dd33 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 20 Feb 2016 21:35:48 -0800 Subject: [PATCH 025/585] [API Notes] Add support for SwiftName on anything, which maps to the swift_name attribute. This allows API notes to be used to override the Swift names of Objective-C entities. apple-llvm-split-commit: 89282506936a78eb364ad8239954a666e3dd0d5f apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 12 +++++-- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 7 ++++ clang/lib/APINotes/APINotesWriter.cpp | 4 ++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 21 +++++++++++ clang/lib/Sema/SemaAPINotes.cpp | 35 ++++++++++++++----- clang/test/APINotes/Inputs/roundtrip.apinotes | 13 +++++++ 7 files changed, 81 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 3cf149357bb4a..79f34ccc21898 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -58,7 +58,7 @@ class ContextID { /// Describes API notes data for any entity. /// -/// This is used as the base of +/// This is used as the base of all API notes. class CommonEntityInfo { public: /// Message to use when this entity is unavailable. @@ -70,13 +70,17 @@ class CommonEntityInfo { /// Whether this entity is marked unavailable in Swift. unsigned UnavailableInSwift : 1; + /// Swift name of this entity. + std::string SwiftName; + CommonEntityInfo() : Unavailable(0), UnavailableInSwift(0) { } friend bool operator==(const CommonEntityInfo &lhs, const CommonEntityInfo &rhs) { return lhs.UnavailableMsg == rhs.UnavailableMsg && lhs.Unavailable == rhs.Unavailable && - lhs.UnavailableInSwift == rhs.UnavailableInSwift; + lhs.UnavailableInSwift == rhs.UnavailableInSwift && + lhs.SwiftName == rhs.SwiftName; } friend bool operator!=(const CommonEntityInfo &lhs, @@ -103,6 +107,10 @@ class CommonEntityInfo { } } + if (rhs.SwiftName.length() != 0 && + lhs.SwiftName.length() == 0) + lhs.SwiftName = rhs.SwiftName; + return lhs; } diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 0801d2ceefd97..79241042951e2 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -35,7 +35,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 7; +const uint16_t VERSION_MINOR = 8; using IdentifierID = Fixnum<31>; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index c768f3a3df7f7..25831287c022c 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -39,6 +39,13 @@ namespace { = std::string(reinterpret_cast(data), reinterpret_cast(data) + msgLength); data += msgLength; + + unsigned swiftNameLength + = endian::readNext(data); + info.SwiftName + = std::string(reinterpret_cast(data), + reinterpret_cast(data) + swiftNameLength); + data += swiftNameLength; } /// Used to deserialize the on-disk identifier table. diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 0e498a797a3ea..a9d83d95fd9fc 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -265,7 +265,7 @@ namespace { /// Retrieve the serialized size of the given CommonEntityInfo, for use in /// on-disk hash tables. static unsigned getCommonEntityInfoSize(const CommonEntityInfo &info) { - return 3 + info.UnavailableMsg.size(); + return 5 + info.UnavailableMsg.size() + info.SwiftName.size(); } /// Emit a serialized representation of the common entity information. @@ -275,6 +275,8 @@ namespace { writer.write(info.Unavailable << 1 | info.UnavailableInSwift); writer.write(info.UnavailableMsg.size()); out.write(info.UnavailableMsg.c_str(), info.UnavailableMsg.size()); + writer.write(info.SwiftName.size()); + out.write(info.SwiftName.c_str(), info.SwiftName.size()); } /// Used to serialize the on-disk Objective-C context table. diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 8c27a01e5edde..8896c40cda0a5 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -168,6 +168,7 @@ namespace { NullabilitySeq Nullability; llvm::Optional NullabilityOfRet; AvailabilityItem Availability; + StringRef SwiftName; api_notes::FactoryAsInitKind FactoryAsInit = api_notes::FactoryAsInitKind::Infer; bool DesignatedInit = false; @@ -179,6 +180,7 @@ namespace { StringRef Name; llvm::Optional Nullability; AvailabilityItem Availability; + StringRef SwiftName; }; typedef std::vector PropertiesSeq; @@ -186,6 +188,7 @@ namespace { StringRef Name; bool AuditedForNullability = false; AvailabilityItem Availability; + StringRef SwiftName; MethodsSeq Methods; PropertiesSeq Properties; }; @@ -196,6 +199,7 @@ namespace { NullabilitySeq Nullability; llvm::Optional NullabilityOfRet; AvailabilityItem Availability; + StringRef SwiftName; }; typedef std::vector FunctionsSeq; @@ -203,6 +207,7 @@ namespace { StringRef Name; llvm::Optional Nullability; AvailabilityItem Availability; + StringRef SwiftName; }; typedef std::vector GlobalVariablesSeq; @@ -278,6 +283,7 @@ namespace llvm { AbsentNullability); io.mapOptional("Availability", p.Availability.Mode); io.mapOptional("AvailabilityMsg", p.Availability.Msg); + io.mapOptional("SwiftName", p.SwiftName); } }; @@ -291,6 +297,7 @@ namespace llvm { AbsentNullability); io.mapOptional("Availability", m.Availability.Mode); io.mapOptional("AvailabilityMsg", m.Availability.Msg); + io.mapOptional("SwiftName", m.SwiftName); io.mapOptional("FactoryAsInit", m.FactoryAsInit, api_notes::FactoryAsInitKind::Infer); io.mapOptional("DesignatedInit", m.DesignatedInit, false); @@ -305,6 +312,7 @@ namespace llvm { io.mapOptional("AuditedForNullability", c.AuditedForNullability, false); io.mapOptional("Availability", c.Availability.Mode); io.mapOptional("AvailabilityMsg", c.Availability.Msg); + io.mapOptional("SwiftName", c.SwiftName); io.mapOptional("Methods", c.Methods); io.mapOptional("Properties", c.Properties); } @@ -319,6 +327,7 @@ namespace llvm { AbsentNullability); io.mapOptional("Availability", f.Availability.Mode); io.mapOptional("AvailabilityMsg", f.Availability.Msg); + io.mapOptional("SwiftName", f.SwiftName); } }; @@ -330,6 +339,7 @@ namespace llvm { AbsentNullability); io.mapOptional("Availability", v.Availability.Mode); io.mapOptional("AvailabilityMsg", v.Availability.Msg); + io.mapOptional("SwiftName", v.SwiftName); } }; @@ -463,6 +473,7 @@ static bool compile(const Module &module, return; convertAvailability(meth.Availability, mInfo, meth.Selector); + mInfo.SwiftName = meth.SwiftName; // Check if the selector ends with ':' to determine if it takes arguments. bool takesArguments = meth.Selector.endswith(":"); @@ -505,6 +516,7 @@ static bool compile(const Module &module, return; convertAvailability(cl.Availability, cInfo, cl.Name); + cInfo.SwiftName = cl.SwiftName; if (cl.AuditedForNullability) cInfo.setDefaultNullability(*DefaultNullability); @@ -545,6 +557,7 @@ static bool compile(const Module &module, if (!isAvailable(prop.Availability)) continue; convertAvailability(prop.Availability, pInfo, prop.Name); + pInfo.SwiftName = prop.SwiftName; if (prop.Nullability) pInfo.setNullabilityAudited(*prop.Nullability); Writer->addObjCProperty(clID, prop.Name, pInfo); @@ -598,6 +611,7 @@ static bool compile(const Module &module, if (!isAvailable(global.Availability)) continue; convertAvailability(global.Availability, info, global.Name); + info.SwiftName = global.SwiftName; if (global.Nullability) info.setNullabilityAudited(*global.Nullability); Writer->addGlobalVariable(global.Name, info); @@ -617,6 +631,7 @@ static bool compile(const Module &module, if (!isAvailable(function.Availability)) continue; convertAvailability(function.Availability, info, function.Name); + info.SwiftName = function.SwiftName; convertNullability(function.Nullability, function.NullabilityOfRet, info, function.Name); @@ -707,6 +722,8 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, // Handle class information. handleAvailability(record.Availability, info); + record.SwiftName = copyString(info.SwiftName); + if (info.getDefaultNullability()) { record.AuditedForNullability = true; } @@ -771,6 +788,7 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, handleNullability(method.Nullability, method.NullabilityOfRet, info, selector.count(':')); handleAvailability(method.Availability, info); + method.SwiftName = copyString(info.SwiftName); method.FactoryAsInit = info.getFactoryAsInitKind(); method.DesignatedInit = info.DesignatedInit; method.Required = info.Required; @@ -787,6 +805,7 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, Property property; property.Name = name; handleAvailability(property.Availability, info); + property.SwiftName = copyString(info.SwiftName); // FIXME: No way to represent "not audited for nullability". if (auto nullability = info.getNullability()) { @@ -805,6 +824,7 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, Function function; function.Name = name; handleAvailability(function.Availability, info); + function.SwiftName = copyString(info.SwiftName); if (info.NumAdjustedNullable > 0) handleNullability(function.Nullability, function.NullabilityOfRet, info, info.NumAdjustedNullable-1); @@ -817,6 +837,7 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, GlobalVariable global; global.Name = name; handleAvailability(global.Availability, info); + global.SwiftName = copyString(info.SwiftName); // FIXME: No way to represent "not audited for nullability". if (auto nullability = info.getNullability()) { diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 2f4356b71b702..649e8c455975b 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -93,22 +93,39 @@ static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability) { } } +/// Copy a string into ASTContext-allocated memory. +static StringRef CopyString(ASTContext &ctx, StringRef string) { + void *mem = ctx.Allocate(string.size(), alignof(char)); + memcpy(mem, string.data(), string.size()); + return StringRef(static_cast(mem), string.size()); +} + static void ProcessAPINotes(Sema &S, Decl *D, const api_notes::CommonEntityInfo &Info) { // Availability if (Info.Unavailable && !D->hasAttr()) { - D->addAttr(UnavailableAttr::CreateImplicit(S.Context, Info.UnavailableMsg)); + D->addAttr(UnavailableAttr::CreateImplicit(S.Context, + CopyString(S.Context, + Info.UnavailableMsg))); } if (Info.UnavailableInSwift) { - D->addAttr(AvailabilityAttr::CreateImplicit(S.Context, - &S.Context.Idents.get("swift"), - VersionTuple(), - VersionTuple(), - VersionTuple(), - /*Unavailable=*/true, - Info.UnavailableMsg, - /*Nopartial=*/true)); + D->addAttr(AvailabilityAttr::CreateImplicit( + S.Context, + &S.Context.Idents.get("swift"), + VersionTuple(), + VersionTuple(), + VersionTuple(), + /*Unavailable=*/true, + CopyString(S.Context, Info.UnavailableMsg), + /*Nopartial=*/true)); + } + + // swift_name + if (!Info.SwiftName.empty() && !D->hasAttr()) { + D->addAttr(SwiftNameAttr::CreateImplicit(S.Context, + CopyString(S.Context, + Info.SwiftName))); } } diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index b1722406663eb..a1b60e5b19f1f 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -6,12 +6,14 @@ Classes: - Name: NSCell Availability: available AvailabilityMsg: '' + SwiftName: '' Methods: - Selector: init MethodKind: Instance NullabilityOfRet: U Availability: available AvailabilityMsg: '' + SwiftName: '' DesignatedInit: true - Selector: 'initImageCell:' MethodKind: Instance @@ -19,6 +21,7 @@ Classes: NullabilityOfRet: U Availability: available AvailabilityMsg: '' + SwiftName: '' DesignatedInit: true - Selector: 'initTextCell:' MethodKind: Instance @@ -26,6 +29,7 @@ Classes: NullabilityOfRet: U Availability: available AvailabilityMsg: '' + SwiftName: '' DesignatedInit: true - Selector: 'initWithCoder:' MethodKind: Instance @@ -33,12 +37,14 @@ Classes: NullabilityOfRet: U Availability: available AvailabilityMsg: '' + SwiftName: '' DesignatedInit: true Required: true - Name: NSView AuditedForNullability: true Availability: available AvailabilityMsg: '' + SwiftName: '' Methods: - Selector: 'addSubview:' MethodKind: Instance @@ -46,34 +52,41 @@ Classes: NullabilityOfRet: N Availability: available AvailabilityMsg: '' + SwiftName: '' - Selector: 'addSubview:positioned:relativeTo:' MethodKind: Instance Nullability: [ N, N, O ] NullabilityOfRet: N Availability: available AvailabilityMsg: '' + SwiftName: '' - Selector: 'beginDraggingSessionWithItems:event:source:' MethodKind: Instance Nullability: [ U, U, N ] NullabilityOfRet: N Availability: available AvailabilityMsg: '' + SwiftName: 'beginDragginSession(_:event:source:)' Properties: - Name: enclosingScrollView Nullability: O Availability: available AvailabilityMsg: '' + SwiftName: enclosing - Name: makeBackingLayer Nullability: N Availability: available AvailabilityMsg: '' + SwiftName: '' Functions: - Name: NSAvailableWindowDepths NullabilityOfRet: N Availability: available AvailabilityMsg: '' + SwiftName: 'availableWindowDepths()' Globals: - Name: NSCalibratedWhiteColorSpace Nullability: N Availability: available AvailabilityMsg: '' + SwiftName: calibratedWhite From d2348adb2ff85d88b151c83dbcc7d160ff80e80b Mon Sep 17 00:00:00 2001 From: Tim Northover Date: Mon, 22 Feb 2016 14:17:39 -0800 Subject: [PATCH 026/585] ARM test fixup: allow lr to be used. apple-llvm-split-commit: ad7215d2e218b598f668ebaff2a22d8b8744dd89 apple-llvm-split-dir: llvm/ --- llvm/test/CodeGen/ARM/swifterror.ll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/test/CodeGen/ARM/swifterror.ll b/llvm/test/CodeGen/ARM/swifterror.ll index 99a531cd706c8..e48bf82e5731a 100644 --- a/llvm/test/CodeGen/ARM/swifterror.ll +++ b/llvm/test/CodeGen/ARM/swifterror.ll @@ -140,7 +140,7 @@ define float @foo_if(%swift_error** swifterror %error_ptr_ref, i32 %cc) { ; CHECK-O0: mov r0, #16 ; CHECK-O0: malloc ; CHECK-O0: mov [[ID:r[0-9]+]], r0 -; CHECK-O0: mov [[ID2:r[0-9]+]], #1 +; CHECK-O0: mov [[ID2:[a-z0-9]+]], #1 ; CHECK-O0: strb [[ID2]], [r0, #8] ; CHECK-O0: mov r6, [[ID]] ; reload from stack From 4a4c6955aa8449f00a52d6b740e4523ec92827fa Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 22 Feb 2016 19:38:28 -0800 Subject: [PATCH 027/585] Replaces uses of Fixnum with llvm::PointerEmbeddedInt. The two types are nearly identical, and Fixnum is only in the Swift branches of LLVM, not in mainline LLVM. The main change here is that PointerEmbeddedInt's representation is always a pointer, which means sizeof and anything depending on it give you the size of a pointer rather than the smallest primitive integral type that can hold the number. But for serialization purposes being explicit is probably a good thing, although it does mean more coordination across files. apple-llvm-split-commit: 6ce47441485dc39046bcca06b6692e80d8c863b7 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesFormat.h | 9 ++++--- clang/lib/APINotes/APINotesReader.cpp | 22 ++++++++-------- clang/lib/APINotes/APINotesWriter.cpp | 37 +++++++++++++-------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 79241042951e2..b49064bbf11e8 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -1,4 +1,4 @@ -//===--- APINotesFormat.h - The internals of API notes files ------*- C++ -*-===// +//===--- APINotesFormat.h - The internals of API notes files ----*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -17,6 +17,7 @@ #include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/Hashing.h" +#include "llvm/ADT/PointerEmbeddedInt.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Bitcode/RecordLayout.h" @@ -37,13 +38,13 @@ const uint16_t VERSION_MAJOR = 0; /// When the format changes IN ANY WAY, this number should be incremented. const uint16_t VERSION_MINOR = 8; -using IdentifierID = Fixnum<31>; +using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; -using SelectorID = Fixnum<31>; +using SelectorID = PointerEmbeddedInt; using SelectorIDField = BCVBR<16>; -using StoredContextID = Fixnum<31>; +using StoredContextID = PointerEmbeddedInt; /// The various types of blocks that can occur within a API notes file. /// diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 25831287c022c..8739f03b953e6 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -125,7 +125,7 @@ namespace { static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto nameID - = endian::readNext(data); + = endian::readNext(data); auto isProtocol = endian::readNext(data); return { nameID, isProtocol }; } @@ -133,7 +133,7 @@ namespace { static data_type ReadData(internal_key_type key, const uint8_t *data, unsigned length) { data_type result; - result.first = endian::readNext(data); + result.first = endian::readNext(data); readCommonEntityInfo(data, result.second); if (*data++) { result.second.setDefaultNullability(static_cast(*data)); @@ -187,8 +187,8 @@ namespace { } static internal_key_type ReadKey(const uint8_t *data, unsigned length) { - auto classID = endian::readNext(data); - auto nameID = endian::readNext(data); + auto classID = endian::readNext(data); + auto nameID = endian::readNext(data); return { classID, nameID }; } @@ -247,8 +247,8 @@ namespace { } static internal_key_type ReadKey(const uint8_t *data, unsigned length) { - auto classID = endian::readNext(data); - auto selectorID = endian::readNext(data); + auto classID = endian::readNext(data); + auto selectorID = endian::readNext(data); auto isInstance = endian::readNext(data); return internal_key_type{ classID, selectorID, isInstance }; } @@ -299,17 +299,17 @@ namespace { static internal_key_type ReadKey(const uint8_t *data, unsigned length) { internal_key_type key; key.NumPieces = endian::readNext(data); - unsigned numIdents = (length - sizeof(uint16_t)) / sizeof(IdentifierID); + unsigned numIdents = (length - sizeof(uint16_t)) / sizeof(uint32_t); for (unsigned i = 0; i != numIdents; ++i) { key.Identifiers.push_back( - endian::readNext(data)); + endian::readNext(data)); } return key; } static data_type ReadData(internal_key_type key, const uint8_t *data, unsigned length) { - return endian::readNext(data); + return endian::readNext(data); } }; @@ -346,7 +346,7 @@ namespace { } static internal_key_type ReadKey(const uint8_t *data, unsigned length) { - auto nameID = endian::readNext(data); + auto nameID = endian::readNext(data); return nameID; } @@ -391,7 +391,7 @@ namespace { } static internal_key_type ReadKey(const uint8_t *data, unsigned length) { - auto nameID = endian::readNext(data); + auto nameID = endian::readNext(data); return nameID; } diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index a9d83d95fd9fc..430c2a2ad97e1 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -230,7 +230,6 @@ namespace { void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, unsigned len) { - static_assert(sizeof(IdentifierID) <= 4, "DeclID too large"); endian::Writer writer(out); writer.write(data); } @@ -299,8 +298,8 @@ namespace { std::pair EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { - uint32_t keyLength = sizeof(IdentifierID) + 1; - uint32_t dataLength = sizeof(ContextID) + uint32_t keyLength = sizeof(uint32_t) + 1; + uint32_t dataLength = sizeof(uint32_t) + getCommonEntityInfoSize(data.second) + dataBytes; endian::Writer writer(out); @@ -311,14 +310,14 @@ namespace { void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { endian::Writer writer(out); - writer.write(key.first); + writer.write(key.first); writer.write(key.second); } void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, unsigned len) { endian::Writer writer(out); - writer.write(data.first); + writer.write(data.first); emitCommonEntityInfo(out, data.second); @@ -400,7 +399,7 @@ namespace { std::pair EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { - uint32_t keyLength = sizeof(IdentifierID) + sizeof(IdentifierID); + uint32_t keyLength = sizeof(uint32_t) + sizeof(uint32_t); uint32_t dataLength = getVariableInfoSize(data); endian::Writer writer(out); writer.write(keyLength); @@ -410,8 +409,8 @@ namespace { void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { endian::Writer writer(out); - writer.write(key.first); - writer.write(key.second); + writer.write(key.first); + writer.write(key.second); } void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, @@ -482,7 +481,7 @@ namespace { std::pair EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { - uint32_t keyLength = sizeof(IdentifierID) + sizeof(SelectorID) + 1; + uint32_t keyLength = sizeof(uint32_t) + sizeof(uint32_t) + 1; uint32_t dataLength = getFunctionInfoSize(data) + 3; endian::Writer writer(out); writer.write(keyLength); @@ -492,8 +491,8 @@ namespace { void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { endian::Writer writer(out); - writer.write(std::get<0>(key)); - writer.write(std::get<1>(key)); + writer.write(std::get<0>(key)); + writer.write(std::get<1>(key)); writer.write(std::get<2>(key)); } @@ -555,8 +554,8 @@ namespace { key_type_ref key, data_type_ref data) { uint32_t keyLength = sizeof(uint16_t) - + sizeof(IdentifierID) * key.Identifiers.size(); - uint32_t dataLength = sizeof(SelectorID); + + sizeof(uint32_t) * key.Identifiers.size(); + uint32_t dataLength = sizeof(uint32_t); endian::Writer writer(out); writer.write(keyLength); writer.write(dataLength); @@ -567,14 +566,14 @@ namespace { endian::Writer writer(out); writer.write(key.NumPieces); for (auto piece : key.Identifiers) { - writer.write(piece); + writer.write(piece); } } void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, unsigned len) { endian::Writer writer(out); - writer.write(data); + writer.write(data); } }; } // end anonymous namespace @@ -621,7 +620,7 @@ namespace { std::pair EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { - uint32_t keyLength = sizeof(IdentifierID); + uint32_t keyLength = sizeof(uint32_t); uint32_t dataLength = getVariableInfoSize(data); endian::Writer writer(out); writer.write(keyLength); @@ -631,7 +630,7 @@ namespace { void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { endian::Writer writer(out); - writer.write(key); + writer.write(key); } void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, @@ -683,7 +682,7 @@ namespace { std::pair EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { - uint32_t keyLength = sizeof(IdentifierID); + uint32_t keyLength = sizeof(uint32_t); uint32_t dataLength = getFunctionInfoSize(data); endian::Writer writer(out); writer.write(keyLength); @@ -693,7 +692,7 @@ namespace { void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { endian::Writer writer(out); - writer.write(key); + writer.write(key); } void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, From c3322f80746568134fd54fca83ddda52027dbd1c Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 22 Feb 2016 19:49:14 -0800 Subject: [PATCH 028/585] Remove llvm::Fixnum. Use llvm::PointerEmbeddedInt from now on. apple-llvm-split-commit: 9a68e4a28a9da0fe9a098147ea139eb8a0e0bc66 apple-llvm-split-dir: llvm/ --- llvm/include/llvm/ADT/Fixnum.h | 190 ----------------------- llvm/include/llvm/Bitcode/RecordLayout.h | 10 +- 2 files changed, 1 insertion(+), 199 deletions(-) delete mode 100644 llvm/include/llvm/ADT/Fixnum.h diff --git a/llvm/include/llvm/ADT/Fixnum.h b/llvm/include/llvm/ADT/Fixnum.h deleted file mode 100644 index 033cebe6f7574..0000000000000 --- a/llvm/include/llvm/ADT/Fixnum.h +++ /dev/null @@ -1,190 +0,0 @@ -//===- Fixnum.h - An integer type with an explicit bit width ----*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -/// \file Declares Fixnum, an integer type with an explicit bit width, -/// and utilities for working with bit widths of integers. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_ADT_FIXNUM_H -#define LLVM_ADT_FIXNUM_H - -#include "llvm/Support/MathExtras.h" -#include "llvm/Support/PointerLikeTypeTraits.h" -#include -#include - -namespace llvm { - -/// Defines a member #type that is the smallest signed integer that can hold -/// a value with the given bit width. -template -struct int_least { - static_assert(Bits <= 64, "too many bits"); - using type = typename std::conditional<(Bits <= 8), int_least8_t, - typename std::conditional<(Bits <= 16), int_least16_t, - typename std::conditional<(Bits <= 32), int_least32_t, - int_least64_t>::type>::type>::type; -}; - -/// Defines a member #type that is the smallest unsigned integer that can hold -/// a value with the given bit width. -template -struct uint_least { - using type = - typename std::make_unsigned::type>::type; -}; - -/// A wrapper for an integer type that is guaranteed to only use a certain -/// number of bits. -/// -/// This can be used to treat an integer like a pointer with low bits free. -/// -/// Note that if the integer type is signed, \p Bits must include the sign -/// bit, just like a bitfield. -template ::type> -class Fixnum { - static_assert(Bits <= (std::numeric_limits::digits + - std::numeric_limits::is_signed), - "too many bits for integer type"); - - IntType Value; - - void assertValid() const { - assert((std::is_signed::value ? llvm::isInt(Value) - : llvm::isUInt(Value)) && - "value exceeds limited bit width"); - } - -public: - using value_type = IntType; - - Fixnum() : Value(0) {} - - /*implicit*/ Fixnum(IntType val) : Value(val) { - assertValid(); - } - - /// Initialize a Fixnum from another, smaller Fixnum. - /// - /// This is always safe and thus permitted as an implicit coercion. - template - /*implicit*/ Fixnum( - const typename std::enable_if<(OtherBits < Bits), - Fixnum>::type &other) - : Value(static_cast(other)) {} - - /// Initialize a Fixnum from another of a different width. - /// - /// This is permitted, but checked with assertions. It must be explicitly - /// requested -- it is not a valid implicit conversion. - template - explicit Fixnum(const Fixnum &other) { - operator=(other); - } - - /// Assign to a Fixnum from another of a different width. - /// - /// This is permitted, but checked with assertions. - template - Fixnum &operator=(const Fixnum &other) { - Value = static_cast(other); - assert(static_cast(Value) == other && - "cannot represent the same value"); - assert(((Value < 0) == (other < 0)) && "signedness mismatch"); - assertValid(); - return *this; - } - - /*implicit*/ operator IntType() const { - return Value; - } - - Fixnum &operator++() { - assert((Value != std::numeric_limits::max()) && - "increment would cause wraparound"); - ++Value; - assertValid(); - return *this; - } - - Fixnum operator++(int) { - assert((Value != std::numeric_limits::max()) && - "increment would cause wraparound"); - Fixnum result = *this; - ++Value; - assertValid(); - return result; - } - - Fixnum &operator--() { - assert((Value != std::numeric_limits::min()) && - "decrement would cause wraparound"); - --Value; - assertValid(); - return *this; - } - - Fixnum operator--(int) { - assert((Value != std::numeric_limits::min()) && - "decrement would cause wraparound"); - Fixnum result = *this; - --Value; - assertValid(); - return result; - } - - bool operator==(const Fixnum &RHS) const { - return Value == RHS.Value; - } - bool operator!=(const Fixnum &RHS) const { - return !operator==(RHS); - } - - bool operator==(int RHS) const { - return Value == IntType(RHS); - } - bool operator!=(int RHS) const { - return !operator==(RHS); - } -}; - -// Fixnum can be treated like a pointer with low bits free if it is no -// larger than a pointer. -template -class PointerLikeTypeTraits> { - using IntPointerType = - typename std::conditional::value, - intptr_t, uintptr_t>::type; - -public: - static_assert(sizeof(IntType) <= sizeof(IntPointerType), - "Fixnum is too big to fit in a pointer"); - - static inline void * - getAsVoidPointer(const Fixnum &I) { - auto opaqueValue = static_cast(I) << NumLowBitsAvailable; - return reinterpret_cast(opaqueValue); - } - - static inline Fixnum - getFromVoidPointer(const void *P) { - auto opaqueValue = reinterpret_cast(P); - return static_cast(opaqueValue >> NumLowBitsAvailable); - } - - enum { - NumLowBitsAvailable = std::numeric_limits::digits - IntBits - }; -}; - -} // end namespace llvm - -#endif diff --git a/llvm/include/llvm/Bitcode/RecordLayout.h b/llvm/include/llvm/Bitcode/RecordLayout.h index c3cbf13b2239f..ce6c7f8bd869d 100644 --- a/llvm/include/llvm/Bitcode/RecordLayout.h +++ b/llvm/include/llvm/Bitcode/RecordLayout.h @@ -1,4 +1,4 @@ -//===--- BCRecordLayout.h - Convenience wrappers for bitcode ----*- C++ -*-===// +//===--- RecordLayout.h - Convenience wrappers for bitcode ------*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -33,7 +33,6 @@ #define LLVM_BITCODE_RECORDLAYOUT_H #include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/Fixnum.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Bitcode/BitCodes.h" #include "llvm/Bitcode/BitstreamWriter.h" @@ -104,13 +103,6 @@ class BCFixed : public impl::BCField<> { assert(llvm::isUInt(data) && "data value does not fit in the given bit width"); } - - using value_type = Fixnum; - - template - static value_type convert(T rawValue) { - return static_cast(rawValue); - } }; /// Represents a variable-width value in a bitcode record. From 9d786d7571f50979bc660462271f2badf8bbaf23 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Sun, 28 Feb 2016 16:41:12 -0800 Subject: [PATCH 029/585] Remove a newline from README.txt so that when llvm.org gets merged in in the future, we will get a merge commit. This is necessary since although currently llvm.org/compiler-rt and github/swift-compiler-rt are the same, they may not be in the future. In such a case, we want to cut compiler-rt stable branches from github, not from llvm.org. To be able to do that in an automated fashion, the swift merge tool needs to be able to reason about whether or not our internal branch had a commit coming from llvm.org/compiler-rt or github/swift-compiler-rt. The easiest way to do that is by ensuring that merging llvm.org/compiler-rt into github/swift-compiler-rt does not fast forward. apple-llvm-split-commit: 5e77a62b8d53a0198f65035806a1ee93da847ba4 apple-llvm-split-dir: compiler-rt/ --- compiler-rt/README.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler-rt/README.txt b/compiler-rt/README.txt index fc8843246e255..2d64f00e3ebe3 100644 --- a/compiler-rt/README.txt +++ b/compiler-rt/README.txt @@ -8,4 +8,3 @@ Compiler-RT is open source software. You may freely distribute it under the terms of the license agreement found in LICENSE.txt. ================================ - From 3d7dd666a38e5649b38a38c204831ec91ffece7b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 1 Mar 2016 09:05:55 -0800 Subject: [PATCH 030/585] [Swift] Extend swift_name attribute to describe members. Extend the syntax of the swift_name string to allow "Foo.bar", which specifies that the entity to which the attribute appertains should be imported as a member named "foo" of the type with the Swift name "Foo". For functions that will become instance methods, the special argument name "self" indicates which parameter of the C function will become the "self" parameter. For example, given: typedef struct __attribute__(((swift_name("Foo"))) AAFoo { /* ... */ } AAFoo; void AAFooDoSomething(AAFoo foo, double x, double y) __attribute__((swift_name("Foo.doSomething(self:x:y:)"))); The C type AAFoo will be imported as the Swift type Foo, and AAFooDoSomething will be imported as an instance method "doSomething(x:y:)" of Foo, where the first parameter is "self". Part of implementing SE-0033. apple-llvm-split-commit: 864a33392056d76af17f5d955b6082a94b8ea99a apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaDeclAttr.cpp | 38 +++++++++++++++++++++++++++++++- clang/test/SemaObjC/attr-swift.m | 6 +++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 1d744b013862b..cd9b6932e6c58 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4500,6 +4500,21 @@ static bool validateSwiftFunctionName(StringRef Name, StringRef BaseName, Parameters; std::tie(BaseName, Parameters) = Name.split('('); + + // Split at the first '.', if it exists, which separates the context + // name from the base name. + StringRef ContextName; + bool IsMember = false; + std::tie(ContextName, BaseName) = BaseName.split('.'); + if (BaseName.empty()) { + BaseName = ContextName; + ContextName = StringRef(); + } else if (ContextName.empty() || !isValidIdentifier(ContextName)) { + return false; + } else { + IsMember = true; + } + if (!isValidIdentifier(BaseName) || BaseName == "_") return false; @@ -4512,12 +4527,23 @@ static bool validateSwiftFunctionName(StringRef Name, if (Parameters.back() != ':') return false; + Optional SelfLocation; StringRef NextParam; do { std::tie(NextParam, Parameters) = Parameters.split(':'); if (!isValidIdentifier(NextParam)) return false; + + // "self" indicates the "self" argument for a member. + if (IsMember && NextParam == "self") { + // More than one "self"? + if (SelfLocation) return false; + + // The "self" location is the current parameter. + SelfLocation = ParamCount; + } + ++ParamCount; } while (!Parameters.empty()); @@ -4584,7 +4610,17 @@ static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { isa(D) || isa(D) || isa(D) || isa(D) || isa(D) || isa(D) || isa(D)) { - if (!isValidIdentifier(Name)) { + StringRef ContextName, BaseName; + std::tie(ContextName, BaseName) = Name.split('.'); + if (BaseName.empty()) { + BaseName = ContextName; + ContextName = StringRef(); + } else if (!isValidIdentifier(ContextName)) { + S.Diag(ArgLoc, diag::err_attr_swift_name_identifier) << Attr.getName(); + return; + } + + if (!isValidIdentifier(BaseName)) { S.Diag(ArgLoc, diag::err_attr_swift_name_identifier) << Attr.getName(); return; } diff --git a/clang/test/SemaObjC/attr-swift.m b/clang/test/SemaObjC/attr-swift.m index 98c705423f120..80a379f05475e 100644 --- a/clang/test/SemaObjC/attr-swift.m +++ b/clang/test/SemaObjC/attr-swift.m @@ -97,6 +97,12 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { typedef int some_int_type __attribute__((swift_name("SomeInt"))); +struct Point3D createPoint3D(float x, float y, float z) __attribute__((swift_name("Point3D.init(x:y:z:)"))); +struct Point3D rotatePoint3D(Point3D point, float radians) __attribute__((swift_name("Point3D.rotate(self:radius:)"))); +struct Point3D badRotatePoint3D(Point3D point, float radians) __attribute__((swift_name("Point3D.rotate(radius:)"))); // expected-warning {{too few parameters in 'swift_name' attribute (expected 2; got 1)}} + +extern struct Point3D identityPoint __attribute__((swift_name("Point3D.identity"))); + // --- swift_error --- @class NSError; From 51e294ac7343ae9fe79021ce4f84e27bc2b6bd46 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 1 Mar 2016 12:55:29 -0800 Subject: [PATCH 031/585] [Swift] Extend swift_name to support getters and setters. Extend the string format of swift_name to support labeling a function as a getter or setter for a given property name, so that the getter (and optional setter) function(s) will be imported into Swift as a property. Slightly extended from what SE-0033 actually requires, but useful in its own right. apple-llvm-split-commit: 91e87c28d1accf4b6cb958c46eed2d81bad37f30 apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaDeclAttr.cpp | 31 ++++++++++++++++++++++++++++++- clang/test/SemaObjC/attr-swift.m | 24 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index cd9b6932e6c58..2051c51313a80 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4495,6 +4495,19 @@ static bool validateSwiftFunctionName(StringRef Name, unsigned &ParamCount, bool &IsSingleParamInit) { ParamCount = 0; + + // Check whether this will be mapped to a getter or setter of a + // property. + bool isGetter = false; + bool isSetter = false; + if (Name.startswith("getter:")) { + isGetter = true; + Name = Name.substr(7); + } else if (Name.startswith("setter:")) { + isSetter = true; + Name = Name.substr(7); + } + if (Name.back() != ')') return false; @@ -4521,8 +4534,13 @@ static bool validateSwiftFunctionName(StringRef Name, if (Parameters.empty()) return false; Parameters = Parameters.drop_back(); // ')' - if (Parameters.empty()) + + if (Parameters.empty()) { + // Setters must have at least one parameter. + if (isSetter) return false; + return true; + } if (Parameters.back() != ':') return false; @@ -4550,6 +4568,17 @@ static bool validateSwiftFunctionName(StringRef Name, IsSingleParamInit = (ParamCount == 1 && BaseName == "init" && NextParam != "_"); + // Check the number of parameters for a getter/setter. + if (isGetter || isSetter) { + // Setters have one parameter for the new value. + unsigned NumExpectedParams = isSetter ? 1 : 0; + + // Instance methods have one parameter for "self". + if (SelfLocation) ++NumExpectedParams; + + if (ParamCount != NumExpectedParams) return true; + } + return true; } diff --git a/clang/test/SemaObjC/attr-swift.m b/clang/test/SemaObjC/attr-swift.m index 80a379f05475e..28042635f7542 100644 --- a/clang/test/SemaObjC/attr-swift.m +++ b/clang/test/SemaObjC/attr-swift.m @@ -103,6 +103,30 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { extern struct Point3D identityPoint __attribute__((swift_name("Point3D.identity"))); +// Getters and setters. +float Point3DGetMagnitude(Point3D point) __attribute__((swift_name("getter:Point3D.magnitude(self:)"))); + +float Point3DGetRadius(Point3D point) __attribute__((swift_name("getter:Point3D.radius(self:)"))); +void Point3DSetRadius(Point3D point, float radius) __attribute__((swift_name("setter:Point3D.radius(self:_:)"))); + +Point3D getCurrentPoint3D(void) __attribute__((swift_name("getter:currentPoint3D()"))); + +void setCurrentPoint3D(Point3D point) __attribute__((swift_name("setter:currentPoint3D(_:)"))); + +Point3D getLastPoint3D(void) __attribute__((swift_name("getter:lastPoint3D()"))); + +void setLastPoint3D(Point3D point) __attribute__((swift_name("setter:lastPoint3D(_:)"))); + +Point3D getZeroPoint() __attribute__((swift_name("getter:Point3D()"))); +void setZeroPoint(Point3D point) __attribute__((swift_name("setter:Point3D(_:)"))); + +Point3D badGetter1(int x) __attribute__((swift_name("getter:bad1(_:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} +void badSetter1() __attribute__((swift_name("getter:bad1())"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} + +Point3D badGetter2(Point3D point) __attribute__((swift_name("getter:bad2(_:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} + +void badSetter2(Point3D point) __attribute__((swift_name("setter:bad2(self:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} + // --- swift_error --- @class NSError; From 59849692055ece2d327f853961dc7f5aecde5c7a Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Fri, 4 Mar 2016 16:06:20 -0800 Subject: [PATCH 032/585] Fixup test typos in the swift_name tests apple-llvm-split-commit: 1459ff57b040b6a46a12366d621e7b8533762961 apple-llvm-split-dir: clang/ --- clang/test/SemaObjC/attr-swift.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/test/SemaObjC/attr-swift.m b/clang/test/SemaObjC/attr-swift.m index 28042635f7542..effcfefff2f05 100644 --- a/clang/test/SemaObjC/attr-swift.m +++ b/clang/test/SemaObjC/attr-swift.m @@ -98,8 +98,8 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { typedef int some_int_type __attribute__((swift_name("SomeInt"))); struct Point3D createPoint3D(float x, float y, float z) __attribute__((swift_name("Point3D.init(x:y:z:)"))); -struct Point3D rotatePoint3D(Point3D point, float radians) __attribute__((swift_name("Point3D.rotate(self:radius:)"))); -struct Point3D badRotatePoint3D(Point3D point, float radians) __attribute__((swift_name("Point3D.rotate(radius:)"))); // expected-warning {{too few parameters in 'swift_name' attribute (expected 2; got 1)}} +struct Point3D rotatePoint3D(Point3D point, float radians) __attribute__((swift_name("Point3D.rotate(self:radians:)"))); +struct Point3D badRotatePoint3D(Point3D point, float radians) __attribute__((swift_name("Point3D.rotate(radians:)"))); // expected-warning {{too few parameters in 'swift_name' attribute (expected 2; got 1)}} extern struct Point3D identityPoint __attribute__((swift_name("Point3D.identity"))); @@ -117,8 +117,8 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { void setLastPoint3D(Point3D point) __attribute__((swift_name("setter:lastPoint3D(_:)"))); -Point3D getZeroPoint() __attribute__((swift_name("getter:Point3D()"))); -void setZeroPoint(Point3D point) __attribute__((swift_name("setter:Point3D(_:)"))); +Point3D getZeroPoint() __attribute__((swift_name("getter:Point3D.zero()"))); +void setZeroPoint(Point3D point) __attribute__((swift_name("setter:Point3D.zero(_:)"))); Point3D badGetter1(int x) __attribute__((swift_name("getter:bad1(_:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} void badSetter1() __attribute__((swift_name("getter:bad1())"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} From 1da22bceff45387e158a6240456caa5a087392ff Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 10 Mar 2016 23:03:57 -0800 Subject: [PATCH 033/585] [Swift] Add swift_bridge attribute for bridging Objective-C types to Swift. apple-llvm-split-commit: 64b3bf21dccabfc9e966e70b88f2e8394b23613b apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 15 ++++++++++++- clang/include/clang/Basic/Attr.td | 8 +++++++ clang/include/clang/Basic/AttrDocs.td | 12 +++++++++-- clang/lib/APINotes/APINotesReader.cpp | 8 +++++++ clang/lib/APINotes/APINotesWriter.cpp | 7 ++++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 7 +++++++ clang/lib/Sema/SemaAPINotes.cpp | 16 ++++++++++++++ clang/lib/Sema/SemaDeclAttr.cpp | 21 +++++++++++++++++++ clang/lib/Sema/SemaDeclObjC.cpp | 6 ++++-- clang/test/APINotes/Inputs/roundtrip.apinotes | 2 ++ 10 files changed, 96 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 79f34ccc21898..decb137a0f502 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -127,6 +127,11 @@ class ObjCContextInfo : public CommonEntityInfo { /// Whether this class has designated initializers recorded. unsigned HasDesignatedInits : 1; + /// The Swift type to which a given Objective-C class is bridged. + /// + /// Reflects the swift_bridge attribute. + std::string SwiftBridge; + public: ObjCContextInfo() : CommonEntityInfo(), @@ -162,11 +167,15 @@ class ObjCContextInfo : public CommonEntityInfo { DefaultNullability = 0; } + const std::string &getSwiftBridge() const { return SwiftBridge; } + void setSwiftBridge(const std::string &swiftType) { SwiftBridge = swiftType; } + friend bool operator==(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { return static_cast(lhs) == rhs && lhs.HasDefaultNullability == rhs.HasDefaultNullability && lhs.DefaultNullability == rhs.DefaultNullability && - lhs.HasDesignatedInits == rhs.HasDesignatedInits; + lhs.HasDesignatedInits == rhs.HasDesignatedInits && + lhs.SwiftBridge == rhs.SwiftBridge; } friend bool operator!=(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { @@ -186,6 +195,10 @@ class ObjCContextInfo : public CommonEntityInfo { } lhs.HasDesignatedInits |= rhs.HasDesignatedInits; + + if (lhs.SwiftBridge.empty() && !rhs.SwiftBridge.empty()) + lhs.SwiftBridge = rhs.SwiftBridge; + return lhs; } diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 33c9930e460f8..0f11afc863686 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1333,6 +1333,14 @@ def Regparm : TypeAttr { let Documentation = [RegparmDocs]; } +def SwiftBridge : Attr { + let Spellings = [GNU<"swift_bridge">]; + let Subjects = SubjectList<[Tag, TypedefName, ObjCProtocol], ErrorDiag, + "ExpectedType">; + let Args = [StringArgument<"SwiftType">]; + let Documentation = [SwiftBridgeDocs]; +} + def SwiftError : InheritableAttr { let Spellings = [GCC<"swift_error">]; let Args = [EnumArgument<"Convention", "ConventionKind", diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 64e45379f5f6f..1010545a3d834 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1860,6 +1860,12 @@ arguments, with arbitrary offsets. }]; } +def SwiftDocs : DocumentationCategory<"Controlling Swift Import"> { + let Content = [{ +Clang supports additional attributes for controlling how APIs are imported into Swift. + }]; +} + def NSErrorDomainDocs : Documentation { let Category = DocCatFunction; let Content = [{ @@ -1867,9 +1873,11 @@ The ``ns_error_domain`` attribute indicates a global constant representing the e }]; } -def SwiftDocs : DocumentationCategory<"Controlling Swift Import"> { + +def SwiftBridgeDocs : Documentation { + let Category = SwiftDocs; let Content = [{ -Clang supports additional attributes for controlling how APIs are imported into Swift. +The ``swift_bridge`` attribute indicates that the type to which the attribute appertains is bridged to the named Swift type. }]; } diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 8739f03b953e6..c1bb118398b55 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -140,6 +140,14 @@ namespace { } ++data; result.second.setHasDesignatedInits(*data++); + + // swift bridge. + unsigned swiftBridgeLength = + endian::readNext(data); + result.second.setSwiftBridge( + StringRef(reinterpret_cast(data), swiftBridgeLength)); + data += swiftBridgeLength; + return result; } }; diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 430c2a2ad97e1..f48a75daf056d 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -301,7 +301,8 @@ namespace { uint32_t keyLength = sizeof(uint32_t) + 1; uint32_t dataLength = sizeof(uint32_t) + getCommonEntityInfoSize(data.second) - + dataBytes; + + dataBytes + + 2 + data.second.getSwiftBridge().size(); endian::Writer writer(out); writer.write(keyLength); writer.write(dataLength); @@ -332,6 +333,10 @@ namespace { bytes[2] = data.second.hasDesignatedInits(); out.write(reinterpret_cast(bytes), dataBytes); + + writer.write(data.second.getSwiftBridge().size()); + out.write(data.second.getSwiftBridge().data(), + data.second.getSwiftBridge().size()); } }; } // end anonymous namespace diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 8896c40cda0a5..17d8fab3637f7 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -189,6 +189,7 @@ namespace { bool AuditedForNullability = false; AvailabilityItem Availability; StringRef SwiftName; + StringRef SwiftBridge; MethodsSeq Methods; PropertiesSeq Properties; }; @@ -313,6 +314,7 @@ namespace llvm { io.mapOptional("Availability", c.Availability.Mode); io.mapOptional("AvailabilityMsg", c.Availability.Msg); io.mapOptional("SwiftName", c.SwiftName); + io.mapOptional("SwiftBridge", c.SwiftBridge); io.mapOptional("Methods", c.Methods); io.mapOptional("Properties", c.Properties); } @@ -521,6 +523,9 @@ static bool compile(const Module &module, if (cl.AuditedForNullability) cInfo.setDefaultNullability(*DefaultNullability); + if (isClass) + cInfo.setSwiftBridge(cl.SwiftBridge); + ContextID clID = isClass ? Writer->addObjCClass(cl.Name, cInfo) : Writer->addObjCProtocol(cl.Name, cInfo); @@ -727,6 +732,8 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, if (info.getDefaultNullability()) { record.AuditedForNullability = true; } + + record.SwiftBridge = copyString(info.getSwiftBridge()); } /// Map availability information, if present. diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 649e8c455975b..b9dcfb873416a 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -239,6 +239,22 @@ static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, ProcessAPINotes(S, D, static_cast(Info)); } +/// Process API notes for an Objective-C class. +static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, + const api_notes::ObjCContextInfo &Info) { + // swift_bridge + if (!Info.getSwiftBridge().empty() && + !D->getAttr()) { + D->addAttr( + SwiftBridgeAttr::CreateImplicit(S.Context, + CopyString(S.Context, + Info.getSwiftBridge()))); + } + + // Handle information common to Objective-C classes and protocols. + ProcessAPINotes(S, static_cast(D), Info); +} + /// Process API notes that are associated with this declaration, mapping them /// to attributes as appropriate. void Sema::ProcessAPINotes(Decl *D) { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 8410145e8ac0f..110a0c18da943 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4910,6 +4910,24 @@ static void handleSwiftError(Sema &S, Decl *D, const AttributeList &attr) { attr.getAttributeSpellingListIndex())); } +static void handleSwiftBridgeAttr(Sema &S, Decl *D, const AttributeList &Attr) { + // Make sure that there is a string literal as the annotation's single + // argument. + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(Attr, 0, Str)) + return; + + // Don't duplicate annotations that are already set. + if (D->hasAttr()) { + S.Diag(Attr.getLoc(), diag::warn_duplicate_attribute) << Attr.getName(); + return; + } + + D->addAttr(::new (S.Context) + SwiftBridgeAttr(Attr.getRange(), S.Context, Str, + Attr.getAttributeSpellingListIndex())); +} + //===----------------------------------------------------------------------===// // Microsoft specific attribute handlers. //===----------------------------------------------------------------------===// @@ -6184,6 +6202,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_SwiftError: handleSwiftError(S, D, Attr); break; + case AttributeList::AT_SwiftBridge: + handleSwiftBridgeAttr(S, D, Attr); + break; } } diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 02a38b78a1473..b7eec4750b5c7 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -1687,7 +1687,8 @@ Sema::ActOnForwardProtocolDeclaration(SourceLocation AtProtocolLoc, = ObjCProtocolDecl::Create(Context, CurContext, Ident, IdentPair.second, AtProtocolLoc, PrevDecl); - + ProcessAPINotes(PDecl); + PushOnScopeChains(PDecl, TUScope); CheckObjCDeclScope(PDecl); @@ -3009,7 +3010,8 @@ Sema::ActOnForwardClassDeclaration(SourceLocation AtClassLoc, ClassName, TypeParams, PrevIDecl, IdentLocs[i]); IDecl->setAtEndRange(IdentLocs[i]); - + ProcessAPINotes(IDecl); + PushOnScopeChains(IDecl, TUScope); CheckObjCDeclScope(IDecl); DeclsInGroup.push_back(IDecl); diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index a1b60e5b19f1f..18bf2deeb1934 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -7,6 +7,7 @@ Classes: Availability: available AvailabilityMsg: '' SwiftName: '' + SwiftBridge: '' Methods: - Selector: init MethodKind: Instance @@ -45,6 +46,7 @@ Classes: Availability: available AvailabilityMsg: '' SwiftName: '' + SwiftBridge: View Methods: - Selector: 'addSubview:' MethodKind: Instance From d2239ed7116a4106bcb3466a0dc7c049e6d60794 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 17 Mar 2016 13:39:41 -0700 Subject: [PATCH 034/585] [Swift] swift_bridge can apply to ObjCInterfaceDecls, of course. Clang's architectural failure of ObjCInterfaceDecl not being a TypeDecl continues to surprise us. apple-llvm-split-commit: 1e173f644bb3ee84c70d77b398372743a2e9f3e2 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Attr.td | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 57e46f0418b95..1a0756d5ed7fd 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1338,8 +1338,8 @@ def Regparm : TypeAttr { def SwiftBridge : Attr { let Spellings = [GNU<"swift_bridge">]; - let Subjects = SubjectList<[Tag, TypedefName, ObjCProtocol], ErrorDiag, - "ExpectedType">; + let Subjects = SubjectList<[Tag, TypedefName, ObjCInterface, ObjCProtocol], + ErrorDiag, "ExpectedType">; let Args = [StringArgument<"SwiftType">]; let Documentation = [SwiftBridgeDocs]; } From 44deacba2206782454f6110f4b2ce43c5937a5ce Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 17 Mar 2016 14:20:33 -0700 Subject: [PATCH 035/585] [API Notes] Fix compilation error reported in rdar://problem/25219276. apple-llvm-split-commit: 24e2d6e0a65e408125cb2f45101258b6e3d7780c apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaAPINotes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index b9dcfb873416a..203424468b2d1 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -118,7 +118,8 @@ static void ProcessAPINotes(Sema &S, Decl *D, VersionTuple(), /*Unavailable=*/true, CopyString(S.Context, Info.UnavailableMsg), - /*Nopartial=*/true)); + /*Strict=*/false, + StringRef())); } // swift_name From c99d9494d31ee6d76b0086431d5f09cc19675fa4 Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Sat, 19 Mar 2016 08:13:25 -0700 Subject: [PATCH 036/585] [APINotes] Fix build error with creating 'AvailabilityAttr', after upstream changes. apple-llvm-split-commit: d015e679baac7607f63ec9a9f0834b9c088595e7 apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaAPINotes.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 203424468b2d1..4e96ace98f31e 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -118,8 +118,7 @@ static void ProcessAPINotes(Sema &S, Decl *D, VersionTuple(), /*Unavailable=*/true, CopyString(S.Context, Info.UnavailableMsg), - /*Strict=*/false, - StringRef())); + /*Strict=*/false)); } // swift_name From be2cb65cb054c4fb5641b0f7dcc16e3fb0573ee3 Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Mon, 21 Mar 2016 11:44:28 -0700 Subject: [PATCH 037/585] [APINotes] Fix build error with creating 'AvailabilityAttr', after r263958. apple-llvm-split-commit: 7fd255d183c292400f9c4fdb9bcac4e81a7f6c7f apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaAPINotes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 4e96ace98f31e..6e06e4cc80a0a 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -118,7 +118,8 @@ static void ProcessAPINotes(Sema &S, Decl *D, VersionTuple(), /*Unavailable=*/true, CopyString(S.Context, Info.UnavailableMsg), - /*Strict=*/false)); + /*Strict=*/false, + /*Replacement=*/StringRef())); } // swift_name From 12023f3bbf7d96ee059b23bc53a05b64a5b8d1e6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Mar 2016 09:31:19 -0700 Subject: [PATCH 038/585] [API Notes] Add support for tags (struct/union/enum/C++ class) and typedefs. Addresses rdar://problem/25365464. apple-llvm-split-commit: fea40e61dc6210e5b84e4dae09491e085287adc2 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 21 ++ clang/include/clang/APINotes/APINotesWriter.h | 12 + clang/include/clang/APINotes/Types.h | 66 +++- clang/lib/APINotes/APINotesFormat.h | 36 ++- clang/lib/APINotes/APINotesReader.cpp | 303 +++++++++++++++++- clang/lib/APINotes/APINotesWriter.cpp | 175 +++++++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 216 ++++++++++--- clang/lib/Sema/SemaAPINotes.cpp | 65 +++- .../Inputs/APINotes/HeaderLib.apinotes | 8 + .../test/APINotes/Inputs/Headers/HeaderLib.h | 3 + clang/test/APINotes/Inputs/roundtrip.apinotes | 12 + clang/test/APINotes/availability.m | 6 + 12 files changed, 834 insertions(+), 89 deletions(-) diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 4759f85c1ab48..16cb5ebe109b4 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -102,6 +102,21 @@ class APINotesReader { /// \returns information about the global function, if known. Optional lookupGlobalFunction(StringRef name); + /// Look for information regarding the given tag + /// (struct/union/enum/C++ class). + /// + /// \param name The name of the tag. + /// + /// \returns information about the tag, if known. + Optional lookupTag(StringRef name); + + /// Look for information regarding the given typedef. + /// + /// \param name The name of the typedef. + /// + /// \returns information about the typedef, if known. + Optional lookupTypedef(StringRef name); + /// Visitor used when walking the contents of the API notes file. class Visitor { public: @@ -131,6 +146,12 @@ class APINotesReader { /// Visit a global function. virtual void visitGlobalFunction(StringRef name, const GlobalFunctionInfo &info); + + /// Visit a tag. + virtual void visitTag(StringRef name, const TagInfo &info); + + /// Visit a typedef. + virtual void visitTypedef(StringRef name, const TypedefInfo &info); }; /// Visit the contents of the API notes file, passing each entity to the diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h index dca0773aa28f8..38935a6a271c9 100644 --- a/clang/include/clang/APINotes/APINotesWriter.h +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -89,6 +89,18 @@ class APINotesWriter { /// \param name The name of this global function. /// \param info Information about this global function. void addGlobalFunction(StringRef name, const GlobalFunctionInfo &info); + + /// Add information about a tag (struct/union/enum/C++ class). + /// + /// \param name The name of this tag. + /// \param info Information about this tag. + void addTag(StringRef name, const TagInfo &info); + + /// Add information about a typedef. + /// + /// \param name The name of this typedef. + /// \param info Information about this typedef. + void addTypedef(StringRef name, const TypedefInfo &info); }; } // end namespace api_notes diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index decb137a0f502..5053190f408eb 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -113,11 +113,43 @@ class CommonEntityInfo { return lhs; } +}; + +/// Describes API notes for types. +class CommonTypeInfo : public CommonEntityInfo { + /// The Swift type to which a given type is bridged. + /// + /// Reflects the swift_bridge attribute. + std::string SwiftBridge; + +public: + CommonTypeInfo() : CommonEntityInfo() { } + + const std::string &getSwiftBridge() const { return SwiftBridge; } + void setSwiftBridge(const std::string &swiftType) { SwiftBridge = swiftType; } + friend CommonTypeInfo &operator|=(CommonTypeInfo &lhs, + const CommonTypeInfo &rhs) { + static_cast(lhs) |= rhs; + if (lhs.SwiftBridge.empty() && !rhs.SwiftBridge.empty()) + lhs.SwiftBridge = rhs.SwiftBridge; + return lhs; + } + + friend bool operator==(const CommonTypeInfo &lhs, + const CommonTypeInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.SwiftBridge == rhs.SwiftBridge; + } + + friend bool operator!=(const CommonTypeInfo &lhs, + const CommonTypeInfo &rhs) { + return !(lhs == rhs); + } }; /// Describes API notes data for an Objective-C class or protocol. -class ObjCContextInfo : public CommonEntityInfo { +class ObjCContextInfo : public CommonTypeInfo { /// Whether this class has a default nullability. unsigned HasDefaultNullability : 1; @@ -127,14 +159,9 @@ class ObjCContextInfo : public CommonEntityInfo { /// Whether this class has designated initializers recorded. unsigned HasDesignatedInits : 1; - /// The Swift type to which a given Objective-C class is bridged. - /// - /// Reflects the swift_bridge attribute. - std::string SwiftBridge; - public: ObjCContextInfo() - : CommonEntityInfo(), + : CommonTypeInfo(), HasDefaultNullability(0), DefaultNullability(0), HasDesignatedInits(0) @@ -167,15 +194,11 @@ class ObjCContextInfo : public CommonEntityInfo { DefaultNullability = 0; } - const std::string &getSwiftBridge() const { return SwiftBridge; } - void setSwiftBridge(const std::string &swiftType) { SwiftBridge = swiftType; } - friend bool operator==(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { - return static_cast(lhs) == rhs && + return static_cast(lhs) == rhs && lhs.HasDefaultNullability == rhs.HasDefaultNullability && lhs.DefaultNullability == rhs.DefaultNullability && - lhs.HasDesignatedInits == rhs.HasDesignatedInits && - lhs.SwiftBridge == rhs.SwiftBridge; + lhs.HasDesignatedInits == rhs.HasDesignatedInits; } friend bool operator!=(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { @@ -185,7 +208,7 @@ class ObjCContextInfo : public CommonEntityInfo { friend ObjCContextInfo &operator|=(ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { // Merge inherited info. - static_cast(lhs) |= rhs; + static_cast(lhs) |= rhs; // Merge nullability. if (!lhs.getDefaultNullability()) { @@ -196,9 +219,6 @@ class ObjCContextInfo : public CommonEntityInfo { lhs.HasDesignatedInits |= rhs.HasDesignatedInits; - if (lhs.SwiftBridge.empty() && !rhs.SwiftBridge.empty()) - lhs.SwiftBridge = rhs.SwiftBridge; - return lhs; } @@ -436,6 +456,18 @@ class GlobalFunctionInfo : public FunctionInfo { GlobalFunctionInfo() : FunctionInfo() { } }; +/// Describes API notes data for a tag. +class TagInfo : public CommonTypeInfo { +public: + TagInfo() : CommonTypeInfo() { } +}; + +/// Describes API notes data for a typedef. +class TypedefInfo : public CommonTypeInfo { +public: + TypedefInfo() : CommonTypeInfo() { } +}; + } // end namespace api_notes } // end namespace clang diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index b49064bbf11e8..761470ddfa19e 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 8; +const uint16_t VERSION_MINOR = 9; using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; @@ -85,7 +85,15 @@ enum BlockID { /// The (global) functions data block, which maps global function names to /// information about the global function. - GLOBAL_FUNCTION_BLOCK_ID + GLOBAL_FUNCTION_BLOCK_ID, + + /// The tag data block, which maps tag names to information about + /// the tags. + TAG_BLOCK_ID, + + /// The typedef data block, which maps typedef names to information about + /// the typedefs. + TYPEDEF_BLOCK_ID, }; namespace control_block { @@ -194,6 +202,30 @@ namespace global_function_block { >; } +namespace tag_block { + enum { + TAG_DATA = 1 + }; + + using TagDataLayout = BCRecordLayout< + TAG_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from name to tag information + >; +}; + +namespace typedef_block { + enum { + TYPEDEF_DATA = 1 + }; + + using TypedefDataLayout = BCRecordLayout< + TYPEDEF_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from name to typedef information + >; +}; + /// A stored Objective-C selector. struct StoredObjCSelector { unsigned NumPieces; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index c1bb118398b55..f400ebf6457b4 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -48,6 +48,17 @@ namespace { data += swiftNameLength; } + /// Read serialized CommonTypeInfo. + void readCommonTypeInfo(const uint8_t *&data, CommonTypeInfo &info) { + readCommonEntityInfo(data, info); + + unsigned swiftBridgeLength = + endian::readNext(data); + info.setSwiftBridge( + StringRef(reinterpret_cast(data), swiftBridgeLength)); + data += swiftBridgeLength; + } + /// Used to deserialize the on-disk identifier table. class IdentifierTableInfo { public: @@ -134,19 +145,12 @@ namespace { unsigned length) { data_type result; result.first = endian::readNext(data); - readCommonEntityInfo(data, result.second); + readCommonTypeInfo(data, result.second); if (*data++) { result.second.setDefaultNullability(static_cast(*data)); } ++data; result.second.setHasDesignatedInits(*data++); - - // swift bridge. - unsigned swiftBridgeLength = - endian::readNext(data); - result.second.setSwiftBridge( - StringRef(reinterpret_cast(data), swiftBridgeLength)); - data += swiftBridgeLength; return result; } @@ -410,6 +414,96 @@ namespace { return info; } }; + + /// Used to deserialize the on-disk tag table. + class TagTableInfo { + public: + using internal_key_type = unsigned; // name ID + using external_key_type = internal_key_type; + using data_type = TagInfo; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return static_cast(llvm::hash_value(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + auto nameID = endian::readNext(data); + return nameID; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + TagInfo info; + readCommonTypeInfo(data, info); + return info; + } + }; + + /// Used to deserialize the on-disk typedef table. + class TypedefTableInfo { + public: + using internal_key_type = unsigned; // name ID + using external_key_type = internal_key_type; + using data_type = TypedefInfo; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return static_cast(llvm::hash_value(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + auto nameID = endian::readNext(data); + return nameID; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + TypedefInfo info; + readCommonTypeInfo(data, info); + return info; + } + }; } // end anonymous namespace class APINotesReader::Implementation { @@ -465,6 +559,18 @@ class APINotesReader::Implementation { /// The global function table. std::unique_ptr GlobalFunctionTable; + using SerializedTagTable = + llvm::OnDiskIterableChainedHashTable; + + /// The tag table. + std::unique_ptr TagTable; + + using SerializedTypedefTable = + llvm::OnDiskIterableChainedHashTable; + + /// The typedef table. + std::unique_ptr TypedefTable; + /// Retrieve the identifier ID for the given string, or an empty /// optional if the string is unknown. Optional getIdentifier(StringRef str); @@ -489,6 +595,10 @@ class APINotesReader::Implementation { SmallVectorImpl &scratch); bool readGlobalFunctionBlock(llvm::BitstreamCursor &cursor, SmallVectorImpl &scratch); + bool readTagBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); + bool readTypedefBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); }; Optional APINotesReader::Implementation::getIdentifier( @@ -959,6 +1069,112 @@ bool APINotesReader::Implementation::readGlobalFunctionBlock( return false; } +bool APINotesReader::Implementation::readTagBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(TAG_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case tag_block::TAG_DATA: { + // Already saw tag table. + if (TagTable) + return true; + + uint32_t tableOffset; + tag_block::TagDataLayout::readRecord(scratch, tableOffset); + auto base = reinterpret_cast(blobData.data()); + + TagTable.reset( + SerializedTagTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + +bool APINotesReader::Implementation::readTypedefBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(TYPEDEF_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case typedef_block::TYPEDEF_DATA: { + // Already saw typedef table. + if (TypedefTable) + return true; + + uint32_t tableOffset; + typedef_block::TypedefDataLayout::readRecord(scratch, tableOffset); + auto base = reinterpret_cast(blobData.data()); + + TypedefTable.reset( + SerializedTypedefTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + APINotesReader::APINotesReader(std::unique_ptr inputBuffer, bool &failed) : Impl(*new Implementation) @@ -1057,6 +1273,20 @@ APINotesReader::APINotesReader(std::unique_ptr inputBuffer, } break; + case TAG_BLOCK_ID: + if (!hasValidControlBlock || Impl.readTagBlock(cursor, scratch)) { + failed = true; + return; + } + break; + + case TYPEDEF_BLOCK_ID: + if (!hasValidControlBlock || Impl.readTypedefBlock(cursor, scratch)) { + failed = true; + return; + } + break; + default: // Unknown top-level block, possibly for use by a future version of the // module format. @@ -1197,6 +1427,37 @@ Optional APINotesReader::lookupGlobalFunction( return *known; } + +Optional APINotesReader::lookupTag(StringRef name) { + if (!Impl.TagTable) + return None; + + Optional nameID = Impl.getIdentifier(name); + if (!nameID) + return None; + + auto known = Impl.TagTable->find(*nameID); + if (known == Impl.TagTable->end()) + return None; + + return *known; +} + +Optional APINotesReader::lookupTypedef(StringRef name) { + if (!Impl.TypedefTable) + return None; + + Optional nameID = Impl.getIdentifier(name); + if (!nameID) + return None; + + auto known = Impl.TypedefTable->find(*nameID); + if (known == Impl.TypedefTable->end()) + return None; + + return *known; +} + APINotesReader::Visitor::~Visitor() { } void APINotesReader::Visitor::visitObjCClass(ContextID contextID, @@ -1224,6 +1485,14 @@ void APINotesReader::Visitor::visitGlobalFunction( StringRef name, const GlobalFunctionInfo &info) { } +void APINotesReader::Visitor::visitTag( + StringRef name, + const TagInfo &info) { } + +void APINotesReader::Visitor::visitTypedef( + StringRef name, + const TypedefInfo &info) { } + void APINotesReader::visit(Visitor &visitor) { // FIXME: All of these iterations would be significantly more efficient if we // could get the keys and data together, but OnDiskIterableHashTable doesn't @@ -1310,5 +1579,23 @@ void APINotesReader::visit(Visitor &visitor) { visitor.visitGlobalVariable(name, info); } } + + // Visit tags. + if (Impl.TagTable) { + for (auto key : Impl.TagTable->keys()) { + auto name = identifiers[key]; + auto info = *Impl.TagTable->find(key); + visitor.visitTag(name, info); + } + } + + // Visit typedefs. + if (Impl.TypedefTable) { + for (auto key : Impl.TypedefTable->keys()) { + auto name = identifiers[key]; + auto info = *Impl.TypedefTable->find(key); + visitor.visitTypedef(name, info); + } + } } diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index f48a75daf056d..2d55c927c703f 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -77,6 +77,16 @@ class APINotesWriter::Implementation { /// Indexed by the identifier ID. llvm::DenseMap GlobalFunctions; + /// Information about tags. + /// + /// Indexed by the identifier ID. + llvm::DenseMap Tags; + + /// Information about typedefs. + /// + /// Indexed by the identifier ID. + llvm::DenseMap Typedefs; + /// Retrieve the ID for the given identifier. IdentifierID getIdentifier(StringRef identifier) { if (identifier.empty()) @@ -123,6 +133,8 @@ class APINotesWriter::Implementation { void writeObjCSelectorBlock(llvm::BitstreamWriter &writer); void writeGlobalVariableBlock(llvm::BitstreamWriter &writer); void writeGlobalFunctionBlock(llvm::BitstreamWriter &writer); + void writeTagBlock(llvm::BitstreamWriter &writer); + void writeTypedefBlock(llvm::BitstreamWriter &writer); }; /// Record the name of a block. @@ -278,6 +290,20 @@ namespace { out.write(info.SwiftName.c_str(), info.SwiftName.size()); } + // Retrieve the serialized size of the given CommonTypeInfo, for use + // in on-disk hash tables. + static unsigned getCommonTypeInfoSize(const CommonTypeInfo &info) { + return 2 + info.getSwiftBridge().size() + getCommonEntityInfoSize(info); + } + + /// Emit a serialized representation of the common type information. + static void emitCommonTypeInfo(raw_ostream &out, const CommonTypeInfo &info) { + emitCommonEntityInfo(out, info); + endian::Writer writer(out); + writer.write(info.getSwiftBridge().size()); + out.write(info.getSwiftBridge().c_str(), info.getSwiftBridge().size()); + } + /// Used to serialize the on-disk Objective-C context table. class ObjCContextTableInfo { public: @@ -300,9 +326,8 @@ namespace { data_type_ref data) { uint32_t keyLength = sizeof(uint32_t) + 1; uint32_t dataLength = sizeof(uint32_t) - + getCommonEntityInfoSize(data.second) - + dataBytes - + 2 + data.second.getSwiftBridge().size(); + + getCommonTypeInfoSize(data.second) + + dataBytes; endian::Writer writer(out); writer.write(keyLength); writer.write(dataLength); @@ -320,7 +345,7 @@ namespace { endian::Writer writer(out); writer.write(data.first); - emitCommonEntityInfo(out, data.second); + emitCommonTypeInfo(out, data.second); // FIXME: Inefficient representation. uint8_t bytes[dataBytes] = { 0, 0, 0 }; @@ -333,10 +358,6 @@ namespace { bytes[2] = data.second.hasDesignatedInits(); out.write(reinterpret_cast(bytes), dataBytes); - - writer.write(data.second.getSwiftBridge().size()); - out.write(data.second.getSwiftBridge().data(), - data.second.getSwiftBridge().size()); } }; } // end anonymous namespace @@ -732,6 +753,130 @@ void APINotesWriter::Implementation::writeGlobalFunctionBlock( layout.emit(ScratchRecord, tableOffset, hashTableBlob); } +namespace { + /// Used to serialize the on-disk tag table. + class TagTableInfo { + public: + using key_type = unsigned; // name ID + using key_type_ref = key_type; + using data_type = TagInfo; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return static_cast(llvm::hash_value(key)); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(IdentifierID); + uint32_t dataLength = getCommonTypeInfoSize(data); + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key); + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + emitCommonTypeInfo(out, data); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeTagBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, TAG_BLOCK_ID, 3); + + if (Tags.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : Tags) + generator.insert(entry.first, entry.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + tag_block::TagDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + +namespace { + /// Used to serialize the on-disk typedef table. + class TypedefTableInfo { + public: + using key_type = unsigned; // name ID + using key_type_ref = key_type; + using data_type = TypedefInfo; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return static_cast(llvm::hash_value(key)); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(IdentifierID); + uint32_t dataLength = getCommonTypeInfoSize(data); + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key); + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + emitCommonTypeInfo(out, data); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeTypedefBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, TYPEDEF_BLOCK_ID, 3); + + if (Typedefs.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : Typedefs) + generator.insert(entry.first, entry.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + typedef_block::TypedefDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &os) { // Write the API notes file into a buffer. SmallVector buffer; @@ -752,6 +897,8 @@ void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &os) { writeObjCSelectorBlock(writer); writeGlobalVariableBlock(writer); writeGlobalFunctionBlock(writer); + writeTagBlock(writer); + writeTypedefBlock(writer); } // Write the buffer to the stream. @@ -855,3 +1002,15 @@ void APINotesWriter::addGlobalFunction(llvm::StringRef name, assert(!Impl.GlobalFunctions.count(nameID)); Impl.GlobalFunctions[nameID] = info; } + +void APINotesWriter::addTag(llvm::StringRef name, const TagInfo &info) { + IdentifierID tagID = Impl.getIdentifier(name); + assert(!Impl.Tags.count(tagID)); + Impl.Tags[tagID] = info; +} + +void APINotesWriter::addTypedef(llvm::StringRef name, const TypedefInfo &info) { + IdentifierID typedefID = Impl.getIdentifier(name); + assert(!Impl.Typedefs.count(typedefID)); + Impl.Typedefs[typedefID] = info; +} diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 17d8fab3637f7..39f6c9d641084 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -57,6 +57,10 @@ ... Globals: # List of globals ... + Tags: # List of tags (struct/union/enum/C++ class) + ... + Typedefs: # List of typedef-names and C++11 type aliases + ... Each class and protocol is defined as following: @@ -212,6 +216,22 @@ namespace { }; typedef std::vector GlobalVariablesSeq; + struct Tag { + StringRef Name; + AvailabilityItem Availability; + StringRef SwiftName; + StringRef SwiftBridge; + }; + typedef std::vector TagsSeq; + + struct Typedef { + StringRef Name; + AvailabilityItem Availability; + StringRef SwiftName; + StringRef SwiftBridge; + }; + typedef std::vector TypedefsSeq; + struct Module { StringRef Name; AvailabilityItem Availability; @@ -219,6 +239,8 @@ namespace { ClassesSeq Protocols; FunctionsSeq Functions; GlobalVariablesSeq Globals; + TagsSeq Tags; + TypedefsSeq Typedefs; LLVM_ATTRIBUTE_DEPRECATED( void dump() LLVM_ATTRIBUTE_USED, @@ -232,6 +254,8 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(Property) LLVM_YAML_IS_SEQUENCE_VECTOR(Class) LLVM_YAML_IS_SEQUENCE_VECTOR(Function) LLVM_YAML_IS_SEQUENCE_VECTOR(GlobalVariable) +LLVM_YAML_IS_SEQUENCE_VECTOR(Tag) +LLVM_YAML_IS_SEQUENCE_VECTOR(Typedef) namespace llvm { namespace yaml { @@ -345,6 +369,28 @@ namespace llvm { } }; + template <> + struct MappingTraits { + static void mapping(IO &io, Tag& t) { + io.mapRequired("Name", t.Name); + io.mapOptional("Availability", t.Availability.Mode); + io.mapOptional("AvailabilityMsg", t.Availability.Msg); + io.mapOptional("SwiftName", t.SwiftName); + io.mapOptional("SwiftBridge", t.SwiftBridge); + } + }; + + template <> + struct MappingTraits { + static void mapping(IO &io, Typedef& t) { + io.mapRequired("Name", t.Name); + io.mapOptional("Availability", t.Availability.Mode); + io.mapOptional("AvailabilityMsg", t.Availability.Msg); + io.mapOptional("SwiftName", t.SwiftName); + io.mapOptional("SwiftBridge", t.SwiftBridge); + } + }; + template <> struct MappingTraits { static void mapping(IO &io, Module& m) { @@ -355,6 +401,8 @@ namespace llvm { io.mapOptional("Protocols", m.Protocols); io.mapOptional("Functions", m.Functions); io.mapOptional("Globals", m.Globals); + io.mapOptional("Tags", m.Tags); + io.mapOptional("Typedefs", m.Typedefs); } }; } @@ -377,11 +425,7 @@ static bool parseAPINotes(StringRef yamlInput, Module &module, return static_cast(yin.error()); } -static bool compile(const Module &module, - llvm::raw_ostream &os, - api_notes::OSType targetOS, - llvm::SourceMgr::DiagHandlerTy diagHandler, - void *diagHandlerCtxt){ +namespace { using namespace api_notes; class YAMLConverter { @@ -431,7 +475,7 @@ static bool compile(const Module &module, outInfo.UnavailableMsg = in.Msg; } else { if (!in.Msg.empty()) { - emitError("availability message for available class '" + + emitError("availability message for available API '" + apiName + "' will not be used"); } } @@ -466,17 +510,37 @@ static bool compile(const Module &module, } } + /// Convert the common parts of an entity from YAML. + template + bool convertCommon(const T& common, CommonEntityInfo &info, + StringRef apiName) { + if (!isAvailable(common.Availability)) + return true; + + convertAvailability(common.Availability, info, apiName); + info.SwiftName = common.SwiftName; + return false; + } + + /// Convert the common parts of a type entity from YAML. + template + bool convertCommonType(const T& common, CommonTypeInfo &info, + StringRef apiName) { + if (convertCommon(common, info, apiName)) + return true; + + info.setSwiftBridge(common.SwiftBridge); + return false; + } + // Translate from Method into ObjCMethodInfo and write it out. void convertMethod(const Method &meth, ContextID classID, StringRef className) { ObjCMethodInfo mInfo; - if (!isAvailable(meth.Availability)) + if (convertCommon(meth, mInfo, meth.Selector)) return; - convertAvailability(meth.Availability, mInfo, meth.Selector); - mInfo.SwiftName = meth.SwiftName; - // Check if the selector ends with ':' to determine if it takes arguments. bool takesArguments = meth.Selector.endswith(":"); @@ -505,27 +569,20 @@ static bool compile(const Module &module, // Write it. Writer->addObjCMethod(classID, selectorRef, - meth.Kind == MethodKind::Instance, - mInfo); + meth.Kind == MethodKind::Instance, + mInfo); } void convertContext(const Class &cl, bool isClass) { // Write the class. ObjCContextInfo cInfo; - // First, translate and check availability info. - if (!isAvailable(cl.Availability)) + if (convertCommonType(cl, cInfo, cl.Name)) return; - convertAvailability(cl.Availability, cInfo, cl.Name); - cInfo.SwiftName = cl.SwiftName; - if (cl.AuditedForNullability) cInfo.setDefaultNullability(*DefaultNullability); - if (isClass) - cInfo.setSwiftBridge(cl.SwiftBridge); - ContextID clID = isClass ? Writer->addObjCClass(cl.Name, cInfo) : Writer->addObjCProtocol(cl.Name, cInfo); @@ -644,12 +701,52 @@ static bool compile(const Module &module, Writer->addGlobalFunction(function.Name, info); } + // Write all tags. + llvm::StringSet<> knownTags; + for (const auto &t : TheModule.Tags) { + // Check for duplicate tag definitions. + if (!knownTags.insert(t.Name).second) { + emitError("multiple definitions of tag '" + t.Name + "'"); + continue; + } + + TagInfo tagInfo; + if (convertCommonType(t, tagInfo, t.Name)) + continue; + + Writer->addTag(t.Name, tagInfo); + } + + // Write all typedefs. + llvm::StringSet<> knownTypedefs; + for (const auto &t : TheModule.Typedefs) { + // Check for duplicate typedef definitions. + if (!knownTags.insert(t.Name).second) { + emitError("multiple definitions of typedef '" + t.Name + "'"); + continue; + } + + TypedefInfo typedefInfo; + if (convertCommonType(t, typedefInfo, t.Name)) + continue; + + Writer->addTypedef(t.Name, typedefInfo); + } + if (!ErrorOccured) Writer->writeToStream(OS); return ErrorOccured; } }; +} + +static bool compile(const Module &module, + llvm::raw_ostream &os, + api_notes::OSType targetOS, + llvm::SourceMgr::DiagHandlerTy diagHandler, + void *diagHandlerCtxt){ + using namespace api_notes; YAMLConverter c(module, targetOS, os, diagHandler, diagHandlerCtxt); return c.convertModule(); @@ -689,15 +786,7 @@ bool api_notes::compileAPINotes(StringRef yamlInput, return compile(module, os, targetOS, diagHandler, diagHandlerCtxt); } -bool api_notes::decompileAPINotes(std::unique_ptr input, - llvm::raw_ostream &os) { - // Try to read the file. - auto reader = APINotesReader::get(std::move(input)); - if (!reader) { - llvm::errs() << "not a well-formed API notes binary file\n"; - return true; - } - +namespace { // Deserialize the API notes file into a module. class DecompileVisitor : public APINotesReader::Visitor { /// Allocator used to clone those strings that need it. @@ -720,20 +809,28 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, return StringRef(reinterpret_cast(ptr), string.size()); } + template + void handleCommon(T &record, const CommonEntityInfo &info) { + handleAvailability(record.Availability, info); + record.SwiftName = copyString(info.SwiftName); + } + + template + void handleCommonType(T &record, const CommonTypeInfo &info) { + handleCommon(record, info); + record.SwiftBridge = copyString(info.getSwiftBridge()); + } + /// Map Objective-C context info. void handleObjCContext(Class &record, StringRef name, const ObjCContextInfo &info) { record.Name = name; - // Handle class information. - handleAvailability(record.Availability, info); - record.SwiftName = copyString(info.SwiftName); + handleCommonType(record, info); if (info.getDefaultNullability()) { record.AuditedForNullability = true; } - - record.SwiftBridge = copyString(info.getSwiftBridge()); } /// Map availability information, if present. @@ -792,10 +889,9 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, method.Selector = copyString(selector); method.Kind = isInstanceMethod ? MethodKind::Instance : MethodKind::Class; + handleCommon(method, info); handleNullability(method.Nullability, method.NullabilityOfRet, info, selector.count(':')); - handleAvailability(method.Availability, info); - method.SwiftName = copyString(info.SwiftName); method.FactoryAsInit = info.getFactoryAsInitKind(); method.DesignatedInit = info.DesignatedInit; method.Required = info.Required; @@ -811,8 +907,7 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, const ObjCPropertyInfo &info) { Property property; property.Name = name; - handleAvailability(property.Availability, info); - property.SwiftName = copyString(info.SwiftName); + handleCommon(property, info); // FIXME: No way to represent "not audited for nullability". if (auto nullability = info.getNullability()) { @@ -830,8 +925,7 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, const GlobalFunctionInfo &info) { Function function; function.Name = name; - handleAvailability(function.Availability, info); - function.SwiftName = copyString(info.SwiftName); + handleCommon(function, info); if (info.NumAdjustedNullable > 0) handleNullability(function.Nullability, function.NullabilityOfRet, info, info.NumAdjustedNullable-1); @@ -843,8 +937,7 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, const GlobalVariableInfo &info) { GlobalVariable global; global.Name = name; - handleAvailability(global.Availability, info); - global.SwiftName = copyString(info.SwiftName); + handleCommon(global, info); // FIXME: No way to represent "not audited for nullability". if (auto nullability = info.getNullability()) { @@ -854,10 +947,35 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, TheModule.Globals.push_back(global); } + virtual void visitTag(StringRef name, const TagInfo &info) { + Tag tag; + tag.Name = name; + handleCommonType(tag, info); + TheModule.Tags.push_back(tag); + } + + virtual void visitTypedef(StringRef name, const TypedefInfo &info) { + Typedef td; + td.Name = name; + handleCommonType(td, info); + TheModule.Typedefs.push_back(td); + } + /// Retrieve the module. Module &getModule() { return TheModule; } - } decompileVisitor; + }; +} + +bool api_notes::decompileAPINotes(std::unique_ptr input, + llvm::raw_ostream &os) { + // Try to read the file. + auto reader = APINotesReader::get(std::move(input)); + if (!reader) { + llvm::errs() << "not a well-formed API notes binary file\n"; + return true; + } + DecompileVisitor decompileVisitor; reader->visit(decompileVisitor); // Sort the data in the module, because the API notes reader doesn't preserve @@ -911,6 +1029,18 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, return lhs.Name < rhs.Name; }); + // Sort tags. + std::sort(module.Tags.begin(), module.Tags.end(), + [](const Tag &lhs, const Tag &rhs) -> bool { + return lhs.Name < rhs.Name; + }); + + // Sort typedefs. + std::sort(module.Typedefs.begin(), module.Typedefs.end(), + [](const Typedef &lhs, const Typedef &rhs) -> bool { + return lhs.Name < rhs.Name; + }); + // Output the YAML representation. Output yout(os); yout << module; diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 6e06e4cc80a0a..fd70e94cd32a8 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -130,6 +130,20 @@ static void ProcessAPINotes(Sema &S, Decl *D, } } +static void ProcessAPINotes(Sema &S, Decl *D, + const api_notes::CommonTypeInfo &Info) { + // swift_bridge + if (!Info.getSwiftBridge().empty() && + !D->getAttr()) { + D->addAttr( + SwiftBridgeAttr::CreateImplicit(S.Context, + CopyString(S.Context, + Info.getSwiftBridge()))); + } + + ProcessAPINotes(S, D, static_cast(Info)); +} + /// Process API notes for a variable or property. static void ProcessAPINotes(Sema &S, Decl *D, const api_notes::VariableInfo &Info) { @@ -232,26 +246,31 @@ static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, static_cast(Info)); } +/// Process API notes for a tag. +static void ProcessAPINotes(Sema &S, TagDecl *D, + const api_notes::TagInfo &Info) { + // Handle common type information. + ProcessAPINotes(S, D, static_cast(Info)); +} + +/// Process API notes for a typedef. +static void ProcessAPINotes(Sema &S, TypedefNameDecl *D, + const api_notes::TypedefInfo &Info) { + // Handle common type information. + ProcessAPINotes(S, D, static_cast(Info)); +} + /// Process API notes for an Objective-C class or protocol. static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, const api_notes::ObjCContextInfo &Info) { - // Handle common entity information. - ProcessAPINotes(S, D, static_cast(Info)); + // Handle common type information. + ProcessAPINotes(S, D, static_cast(Info)); } /// Process API notes for an Objective-C class. static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, const api_notes::ObjCContextInfo &Info) { - // swift_bridge - if (!Info.getSwiftBridge().empty() && - !D->getAttr()) { - D->addAttr( - SwiftBridgeAttr::CreateImplicit(S.Context, - CopyString(S.Context, - Info.getSwiftBridge()))); - } - // Handle information common to Objective-C classes and protocols. ProcessAPINotes(S, static_cast(D), Info); } @@ -314,6 +333,30 @@ void Sema::ProcessAPINotes(Decl *D) { return; } + // Tags + if (auto Tag = dyn_cast(D)) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Info = Reader->lookupTag(Tag->getName())) { + ::ProcessAPINotes(*this, Tag, *Info); + } + } + + return; + } + + // Typedefs + if (auto Typedef = dyn_cast(D)) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Info = Reader->lookupTypedef(Typedef->getName())) { + ::ProcessAPINotes(*this, Typedef, *Info); + } + } + + return; + } + return; } diff --git a/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes b/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes index a4ddafe2892ec..8d8ff11f69b57 100644 --- a/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes +++ b/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes @@ -15,3 +15,11 @@ Globals: Nullability: N - Name: unavailable_global_int Availability: none + +Tags: + - Name: unavailable_struct + Availability: none + +Typedefs: + - Name: unavailable_typedef + Availability: none diff --git a/clang/test/APINotes/Inputs/Headers/HeaderLib.h b/clang/test/APINotes/Inputs/Headers/HeaderLib.h index 1cf199cd49a02..81a7d63d4684d 100644 --- a/clang/test/APINotes/Inputs/Headers/HeaderLib.h +++ b/clang/test/APINotes/Inputs/Headers/HeaderLib.h @@ -10,4 +10,7 @@ int unavailable_global_int; void do_something_with_pointers(int *ptr1, int *ptr2); +typedef int unavailable_typedef; +struct unavailable_struct { int x, y, z; }; + #endif diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 18bf2deeb1934..378c7258d51b7 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -92,3 +92,15 @@ Globals: Availability: available AvailabilityMsg: '' SwiftName: calibratedWhite +Tags: + - Name: NSSomeStruct + Availability: available + AvailabilityMsg: '' + SwiftName: SomeStruct + SwiftBridge: '' +Typedefs: + - Name: NSTypedef + Availability: available + AvailabilityMsg: '' + SwiftName: Typedef + SwiftBridge: '' diff --git a/clang/test/APINotes/availability.m b/clang/test/APINotes/availability.m index 5b996ec8c9497..1cfc65862f46a 100644 --- a/clang/test/APINotes/availability.m +++ b/clang/test/APINotes/availability.m @@ -12,6 +12,12 @@ int main() { i = unavailable_global_int; // expected-error{{'unavailable_global_int' is unavailable}} // expected-note@HeaderLib.h:9{{'unavailable_global_int' has been explicitly marked unavailable here}} + unavailable_typedef t; // expected-error{{'unavailable_typedef' is unavailable}} + // expected-note@HeaderLib.h:13{{'unavailable_typedef' has been explicitly marked unavailable here}} + + struct unavailable_struct s; // expected-error{{'unavailable_struct' is unavailable}} + // expected-note@HeaderLib.h:14{{'unavailable_struct' has been explicitly marked unavailable here}} + B *b = 0; // expected-error{{'B' is unavailable: just don't}} // expected-note@SomeKit/SomeKit.h:15{{'B' has been explicitly marked unavailable here}} From 28251dbad19792681c683aa3bad59e50e7762181 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Mar 2016 10:45:01 -0700 Subject: [PATCH 039/585] [API Notes] Add support for enumerators. Addresses the rest of rdar://problem/25365464. apple-llvm-split-commit: 8256e301e9018f584560d8f20652974c04b52907 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 11 ++ clang/include/clang/APINotes/APINotesWriter.h | 6 + clang/include/clang/APINotes/Types.h | 6 + clang/lib/APINotes/APINotesFormat.h | 18 ++- clang/lib/APINotes/APINotesReader.cpp | 143 ++++++++++++++++++ clang/lib/APINotes/APINotesWriter.cpp | 76 ++++++++++ clang/lib/APINotes/APINotesYAMLCompiler.cpp | 53 +++++++ clang/lib/Sema/SemaAPINotes.cpp | 23 +++ clang/test/APINotes/Inputs/roundtrip.apinotes | 5 + 9 files changed, 340 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 16cb5ebe109b4..839e1dc5ee942 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -102,6 +102,13 @@ class APINotesReader { /// \returns information about the global function, if known. Optional lookupGlobalFunction(StringRef name); + /// Look for information regarding the given enumerator. + /// + /// \param name The name of the enumerator. + /// + /// \returns information about the enumerator, if known. + Optional lookupEnumConstant(StringRef name); + /// Look for information regarding the given tag /// (struct/union/enum/C++ class). /// @@ -147,6 +154,10 @@ class APINotesReader { virtual void visitGlobalFunction(StringRef name, const GlobalFunctionInfo &info); + /// Visit an enumerator. + virtual void visitEnumConstant(StringRef name, + const EnumConstantInfo &info); + /// Visit a tag. virtual void visitTag(StringRef name, const TagInfo &info); diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h index 38935a6a271c9..66dc8ffebbaeb 100644 --- a/clang/include/clang/APINotes/APINotesWriter.h +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -90,6 +90,12 @@ class APINotesWriter { /// \param info Information about this global function. void addGlobalFunction(StringRef name, const GlobalFunctionInfo &info); + /// Add information about an enumerator. + /// + /// \param name The name of this enumerator. + /// \param info Information about this enumerator. + void addEnumConstant(StringRef name, const EnumConstantInfo &info); + /// Add information about a tag (struct/union/enum/C++ class). /// /// \param name The name of this tag. diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 5053190f408eb..2fc4ee8fe7328 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -456,6 +456,12 @@ class GlobalFunctionInfo : public FunctionInfo { GlobalFunctionInfo() : FunctionInfo() { } }; +/// Describes API notes data for an enumerator. +class EnumConstantInfo : public CommonEntityInfo { +public: + EnumConstantInfo() : CommonEntityInfo() { } +}; + /// Describes API notes data for a tag. class TagInfo : public CommonTypeInfo { public: diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 761470ddfa19e..2e52a4d7fddf5 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 9; +const uint16_t VERSION_MINOR = 10; // enum constants using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; @@ -94,6 +94,10 @@ enum BlockID { /// The typedef data block, which maps typedef names to information about /// the typedefs. TYPEDEF_BLOCK_ID, + + /// The enum constant data block, which maps enumerator names to + /// information about the enumerators. + ENUM_CONSTANT_BLOCK_ID, }; namespace control_block { @@ -226,6 +230,18 @@ namespace typedef_block { >; }; +namespace enum_constant_block { + enum { + ENUM_CONSTANT_DATA = 1 + }; + + using EnumConstantDataLayout = BCRecordLayout< + ENUM_CONSTANT_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from name to enumerator information + >; +} + /// A stored Objective-C selector. struct StoredObjCSelector { unsigned NumPieces; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index f400ebf6457b4..40f0f8938857d 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -415,6 +415,51 @@ namespace { } }; + /// Used to deserialize the on-disk enumerator table. + class EnumConstantTableInfo { + public: + using internal_key_type = unsigned; // name ID + using external_key_type = internal_key_type; + using data_type = EnumConstantInfo; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return static_cast(llvm::hash_value(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + auto nameID = endian::readNext(data); + return nameID; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + EnumConstantInfo info; + readCommonEntityInfo(data, info); + return info; + } + }; + /// Used to deserialize the on-disk tag table. class TagTableInfo { public: @@ -559,6 +604,12 @@ class APINotesReader::Implementation { /// The global function table. std::unique_ptr GlobalFunctionTable; + using SerializedEnumConstantTable = + llvm::OnDiskIterableChainedHashTable; + + /// The enumerator table. + std::unique_ptr EnumConstantTable; + using SerializedTagTable = llvm::OnDiskIterableChainedHashTable; @@ -595,6 +646,8 @@ class APINotesReader::Implementation { SmallVectorImpl &scratch); bool readGlobalFunctionBlock(llvm::BitstreamCursor &cursor, SmallVectorImpl &scratch); + bool readEnumConstantBlock(llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch); bool readTagBlock(llvm::BitstreamCursor &cursor, SmallVectorImpl &scratch); bool readTypedefBlock(llvm::BitstreamCursor &cursor, @@ -1069,6 +1122,60 @@ bool APINotesReader::Implementation::readGlobalFunctionBlock( return false; } +bool APINotesReader::Implementation::readEnumConstantBlock( + llvm::BitstreamCursor &cursor, + SmallVectorImpl &scratch) { + if (cursor.EnterSubBlock(ENUM_CONSTANT_BLOCK_ID)) + return true; + + auto next = cursor.advance(); + while (next.Kind != llvm::BitstreamEntry::EndBlock) { + if (next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (cursor.SkipBlock()) + return true; + + next = cursor.advance(); + continue; + } + + scratch.clear(); + StringRef blobData; + unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); + switch (kind) { + case enum_constant_block::ENUM_CONSTANT_DATA: { + // Already saw enumerator table. + if (EnumConstantTable) + return true; + + uint32_t tableOffset; + enum_constant_block::EnumConstantDataLayout::readRecord(scratch, + tableOffset); + auto base = reinterpret_cast(blobData.data()); + + EnumConstantTable.reset( + SerializedEnumConstantTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + next = cursor.advance(); + } + + return false; +} + bool APINotesReader::Implementation::readTagBlock( llvm::BitstreamCursor &cursor, SmallVectorImpl &scratch) { @@ -1273,6 +1380,14 @@ APINotesReader::APINotesReader(std::unique_ptr inputBuffer, } break; + case ENUM_CONSTANT_BLOCK_ID: + if (!hasValidControlBlock || + Impl.readEnumConstantBlock(cursor, scratch)) { + failed = true; + return; + } + break; + case TAG_BLOCK_ID: if (!hasValidControlBlock || Impl.readTagBlock(cursor, scratch)) { failed = true; @@ -1428,6 +1543,21 @@ Optional APINotesReader::lookupGlobalFunction( return *known; } +Optional APINotesReader::lookupEnumConstant(StringRef name) { + if (!Impl.EnumConstantTable) + return None; + + Optional nameID = Impl.getIdentifier(name); + if (!nameID) + return None; + + auto known = Impl.EnumConstantTable->find(*nameID); + if (known == Impl.EnumConstantTable->end()) + return None; + + return *known; +} + Optional APINotesReader::lookupTag(StringRef name) { if (!Impl.TagTable) return None; @@ -1485,6 +1615,10 @@ void APINotesReader::Visitor::visitGlobalFunction( StringRef name, const GlobalFunctionInfo &info) { } +void APINotesReader::Visitor::visitEnumConstant( + StringRef name, + const EnumConstantInfo &info) { } + void APINotesReader::Visitor::visitTag( StringRef name, const TagInfo &info) { } @@ -1580,6 +1714,15 @@ void APINotesReader::visit(Visitor &visitor) { } } + // Visit global variables. + if (Impl.EnumConstantTable) { + for (auto key : Impl.EnumConstantTable->keys()) { + auto name = identifiers[key]; + auto info = *Impl.EnumConstantTable->find(key); + visitor.visitEnumConstant(name, info); + } + } + // Visit tags. if (Impl.TagTable) { for (auto key : Impl.TagTable->keys()) { diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 2d55c927c703f..dd90ee2e59012 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -77,6 +77,11 @@ class APINotesWriter::Implementation { /// Indexed by the identifier ID. llvm::DenseMap GlobalFunctions; + /// Information about enumerators. + /// + /// Indexed by the identifier ID. + llvm::DenseMap EnumConstants; + /// Information about tags. /// /// Indexed by the identifier ID. @@ -133,6 +138,7 @@ class APINotesWriter::Implementation { void writeObjCSelectorBlock(llvm::BitstreamWriter &writer); void writeGlobalVariableBlock(llvm::BitstreamWriter &writer); void writeGlobalFunctionBlock(llvm::BitstreamWriter &writer); + void writeEnumConstantBlock(llvm::BitstreamWriter &writer); void writeTagBlock(llvm::BitstreamWriter &writer); void writeTypedefBlock(llvm::BitstreamWriter &writer); }; @@ -753,6 +759,68 @@ void APINotesWriter::Implementation::writeGlobalFunctionBlock( layout.emit(ScratchRecord, tableOffset, hashTableBlob); } +namespace { + /// Used to serialize the on-disk global enum constant. + class EnumConstantTableInfo { + public: + using key_type = unsigned; // name ID + using key_type_ref = key_type; + using data_type = EnumConstantInfo; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref key) { + return static_cast(llvm::hash_value(key)); + } + + std::pair EmitKeyDataLength(raw_ostream &out, + key_type_ref key, + data_type_ref data) { + uint32_t keyLength = sizeof(uint32_t); + uint32_t dataLength = getCommonEntityInfoSize(data); + endian::Writer writer(out); + writer.write(keyLength); + writer.write(dataLength); + return { keyLength, dataLength }; + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key); + } + + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + emitCommonEntityInfo(out, data); + } + }; +} // end anonymous namespace + +void APINotesWriter::Implementation::writeEnumConstantBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, ENUM_CONSTANT_BLOCK_ID, 3); + + if (EnumConstants.empty()) + return; + + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : EnumConstants) + generator.insert(entry.first, entry.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + enum_constant_block::EnumConstantDataLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); +} + namespace { /// Used to serialize the on-disk tag table. class TagTableInfo { @@ -897,6 +965,7 @@ void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &os) { writeObjCSelectorBlock(writer); writeGlobalVariableBlock(writer); writeGlobalFunctionBlock(writer); + writeEnumConstantBlock(writer); writeTagBlock(writer); writeTypedefBlock(writer); } @@ -1003,6 +1072,13 @@ void APINotesWriter::addGlobalFunction(llvm::StringRef name, Impl.GlobalFunctions[nameID] = info; } +void APINotesWriter::addEnumConstant(llvm::StringRef name, + const EnumConstantInfo &info) { + IdentifierID enumConstantID = Impl.getIdentifier(name); + assert(!Impl.EnumConstants.count(enumConstantID)); + Impl.EnumConstants[enumConstantID] = info; +} + void APINotesWriter::addTag(llvm::StringRef name, const TagInfo &info) { IdentifierID tagID = Impl.getIdentifier(name); assert(!Impl.Tags.count(tagID)); diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 39f6c9d641084..9ad6dd6a08533 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -216,6 +216,13 @@ namespace { }; typedef std::vector GlobalVariablesSeq; + struct EnumConstant { + StringRef Name; + AvailabilityItem Availability; + StringRef SwiftName; + }; + typedef std::vector EnumConstantsSeq; + struct Tag { StringRef Name; AvailabilityItem Availability; @@ -239,6 +246,7 @@ namespace { ClassesSeq Protocols; FunctionsSeq Functions; GlobalVariablesSeq Globals; + EnumConstantsSeq EnumConstants; TagsSeq Tags; TypedefsSeq Typedefs; @@ -254,6 +262,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(Property) LLVM_YAML_IS_SEQUENCE_VECTOR(Class) LLVM_YAML_IS_SEQUENCE_VECTOR(Function) LLVM_YAML_IS_SEQUENCE_VECTOR(GlobalVariable) +LLVM_YAML_IS_SEQUENCE_VECTOR(EnumConstant) LLVM_YAML_IS_SEQUENCE_VECTOR(Tag) LLVM_YAML_IS_SEQUENCE_VECTOR(Typedef) @@ -369,6 +378,16 @@ namespace llvm { } }; + template <> + struct MappingTraits { + static void mapping(IO &io, EnumConstant& v) { + io.mapRequired("Name", v.Name); + io.mapOptional("Availability", v.Availability.Mode); + io.mapOptional("AvailabilityMsg", v.Availability.Msg); + io.mapOptional("SwiftName", v.SwiftName); + } + }; + template <> struct MappingTraits { static void mapping(IO &io, Tag& t) { @@ -401,6 +420,7 @@ namespace llvm { io.mapOptional("Protocols", m.Protocols); io.mapOptional("Functions", m.Functions); io.mapOptional("Globals", m.Globals); + io.mapOptional("Enumerators", m.EnumConstants); io.mapOptional("Tags", m.Tags); io.mapOptional("Typedefs", m.Typedefs); } @@ -701,6 +721,24 @@ namespace { Writer->addGlobalFunction(function.Name, info); } + // Write all enumerators. + llvm::StringSet<> knownEnumConstants; + for (const auto &enumConstant : TheModule.EnumConstants) { + // Check for duplicate enumerators + if (!knownEnumConstants.insert(enumConstant.Name).second) { + emitError("multiple definitions of enumerator '" + + enumConstant.Name + "'"); + continue; + } + + EnumConstantInfo info; + if (!isAvailable(enumConstant.Availability)) + continue; + convertAvailability(enumConstant.Availability, info, enumConstant.Name); + info.SwiftName = enumConstant.SwiftName; + Writer->addEnumConstant(enumConstant.Name, info); + } + // Write all tags. llvm::StringSet<> knownTags; for (const auto &t : TheModule.Tags) { @@ -947,6 +985,15 @@ namespace { TheModule.Globals.push_back(global); } + virtual void visitEnumConstant(StringRef name, + const EnumConstantInfo &info) { + EnumConstant enumConstant; + enumConstant.Name = name; + handleCommon(enumConstant, info); + + TheModule.EnumConstants.push_back(enumConstant); + } + virtual void visitTag(StringRef name, const TagInfo &info) { Tag tag; tag.Name = name; @@ -1029,6 +1076,12 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, return lhs.Name < rhs.Name; }); + // Sort enum constants. + std::sort(module.EnumConstants.begin(), module.EnumConstants.end(), + [](const EnumConstant &lhs, const EnumConstant &rhs) -> bool { + return lhs.Name < rhs.Name; + }); + // Sort tags. std::sort(module.Tags.begin(), module.Tags.end(), [](const Tag &lhs, const Tag &rhs) -> bool { diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index fd70e94cd32a8..bef88e2f87227 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -224,6 +224,15 @@ static void ProcessAPINotes(Sema &S, FunctionDecl *D, static_cast(Info)); } +/// Process API notes for an enumerator. +static void ProcessAPINotes(Sema &S, EnumConstantDecl *D, + const api_notes::EnumConstantInfo &Info) { + + // Handle common information. + ProcessAPINotes(S, D, + static_cast(Info)); +} + /// Process API notes for an Objective-C method. static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, const api_notes::ObjCMethodInfo &Info) { @@ -360,6 +369,20 @@ void Sema::ProcessAPINotes(Decl *D) { return; } + // Enumerators. + if (D->getDeclContext()->getRedeclContext()->isFileContext()) { + if (auto EnumConstant = dyn_cast(D)) { + if (api_notes::APINotesReader *Reader + = APINotes.findAPINotes(D->getLocation())) { + if (auto Info = Reader->lookupEnumConstant(EnumConstant->getName())) { + ::ProcessAPINotes(*this, EnumConstant, *Info); + } + } + + return; + } + } + if (auto ObjCContainer = dyn_cast(D->getDeclContext())) { // Location function that looks up an Objective-C context. auto GetContext = [&](api_notes::APINotesReader *Reader) diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 378c7258d51b7..c34907ff67dff 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -92,6 +92,11 @@ Globals: Availability: available AvailabilityMsg: '' SwiftName: calibratedWhite +Enumerators: + - Name: NSColorRed + Availability: available + AvailabilityMsg: '' + SwiftName: Red Tags: - Name: NSSomeStruct Availability: available From ee44727d08e96f4beac295b1449fe2380ffaec49 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Mar 2016 13:24:21 -0700 Subject: [PATCH 040/585] [API Notes] Document enumerators. NFC apple-llvm-split-commit: 6655e5c223c943fcb407af0c71d9a85d605c599c apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 9ad6dd6a08533..a92529eacfbd0 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -57,6 +57,8 @@ ... Globals: # List of globals ... + Enumerators: # List of enumerators + ... Tags: # List of tags (struct/union/enum/C++ class) ... Typedefs: # List of typedef-names and C++11 type aliases From c9b36058aa7c7daeeccc4b78e0056082dae33edb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Mar 2016 14:00:13 -0700 Subject: [PATCH 041/585] [API notes] Allow tags and typedefs to have the same name. apple-llvm-split-commit: 7b1da27e88dba8f7bc3d51b1ff4a2874e65ca89d apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index a92529eacfbd0..4e84b9fbf1a79 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -761,7 +761,7 @@ namespace { llvm::StringSet<> knownTypedefs; for (const auto &t : TheModule.Typedefs) { // Check for duplicate typedef definitions. - if (!knownTags.insert(t.Name).second) { + if (!knownTypedefs.insert(t.Name).second) { emitError("multiple definitions of typedef '" + t.Name + "'"); continue; } From a62f3b9ba8c419e678d7dad7dd47a8c43cbc8c24 Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Tue, 29 Mar 2016 11:21:53 -0700 Subject: [PATCH 042/585] Remove test/Bitcode/swiftself.ll. The content is now in test/Bitcode/attributes.ll. apple-llvm-split-commit: 68893305c9bc4cc4f8250bb259b94d006de75606 apple-llvm-split-dir: llvm/ --- llvm/test/Bitcode/swiftself.ll | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 llvm/test/Bitcode/swiftself.ll diff --git a/llvm/test/Bitcode/swiftself.ll b/llvm/test/Bitcode/swiftself.ll deleted file mode 100644 index e9293ec98b3dd..0000000000000 --- a/llvm/test/Bitcode/swiftself.ll +++ /dev/null @@ -1,8 +0,0 @@ -; RUN: llvm-as < %s | llvm-dis | FileCheck %s -; RUN: verify-uselistorder < %s - -define void @test(i8* swiftself) -; CHECK: define void @test(i8* swiftself) -{ - ret void; -} From cac31c16d04d6b2dceac626eedad60e0ea6416b8 Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Tue, 29 Mar 2016 11:28:19 -0700 Subject: [PATCH 043/585] Fix merge error. apple-llvm-split-commit: 7b81cc42518899f5497b13479da068d8e9a5a4d8 apple-llvm-split-dir: llvm/ --- llvm/lib/CodeGen/SelectionDAG/FastISel.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp index d718214f97aaa..6a691de4aecc9 100644 --- a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp @@ -90,10 +90,7 @@ void FastISel::ArgListEntry::setAttributes(ImmutableCallSite *CS, IsInAlloca = CS->paramHasAttr(AttrIdx, Attribute::InAlloca); IsReturned = CS->paramHasAttr(AttrIdx, Attribute::Returned); IsSwiftSelf = CS->paramHasAttr(AttrIdx, Attribute::SwiftSelf); -<<<<<<< HEAD IsSwiftError = CS->paramHasAttr(AttrIdx, Attribute::SwiftError); -======= ->>>>>>> mirror/master Alignment = CS->getParamAlignment(AttrIdx); } @@ -964,11 +961,8 @@ bool FastISel::lowerCallTo(CallLoweringInfo &CLI) { Flags.setSRet(); if (Arg.IsSwiftSelf) Flags.setSwiftSelf(); -<<<<<<< HEAD if (Arg.IsSwiftError) Flags.setSwiftError(); -======= ->>>>>>> mirror/master if (Arg.IsByVal) Flags.setByVal(); if (Arg.IsInAlloca) { From 8b235645d1def8a9d3962a235a86bd72f687af88 Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Tue, 29 Mar 2016 11:37:19 -0700 Subject: [PATCH 044/585] Fix merge error again. apple-llvm-split-commit: 0e8d609bab4cc1772be1fc308c724320df193635 apple-llvm-split-dir: llvm/ --- llvm/lib/Target/ARM/ARMCallingConv.td | 30 ++++++--------------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/llvm/lib/Target/ARM/ARMCallingConv.td b/llvm/lib/Target/ARM/ARMCallingConv.td index 6c95a1b98e9ea..21ec04d0911d7 100644 --- a/llvm/lib/Target/ARM/ARMCallingConv.td +++ b/llvm/lib/Target/ARM/ARMCallingConv.td @@ -23,18 +23,12 @@ def CC_ARM_APCS : CallingConv<[ CCIfType<[i1, i8, i16], CCPromoteToType>, -<<<<<<< HEAD - // An SwiftSelf is passed in R9. + // A SwiftSelf is passed in R9. CCIfSwiftSelf>>, - // An SwiftError is passed in R6. + // A SwiftError is passed in R6. CCIfSwiftError>>, -======= - // A SwiftSelf is passed in R9. - CCIfSwiftSelf>>, - ->>>>>>> mirror/master // Handle all vector types as either f64 or v2f64. CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, @@ -166,18 +160,12 @@ def CC_ARM_AAPCS : CallingConv<[ CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, -<<<<<<< HEAD - // An SwiftSelf is passed in R9. + // A SwiftSelf is passed in R9. CCIfSwiftSelf>>, - // An SwiftError is passed in R6. + // A SwiftError is passed in R6. CCIfSwiftError>>, -======= - // A SwiftSelf is passed in R9. - CCIfSwiftSelf>>, - ->>>>>>> mirror/master CCIfType<[f64, v2f64], CCCustom<"CC_ARM_AAPCS_Custom_f64">>, CCIfType<[f32], CCBitConvertToType>, CCDelegateTo @@ -209,18 +197,12 @@ def CC_ARM_AAPCS_VFP : CallingConv<[ CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, -<<<<<<< HEAD - // An SwiftSelf is passed in R9. + // A SwiftSelf is passed in R9. CCIfSwiftSelf>>, - // An SwiftError is passed in R6. + // A SwiftError is passed in R6. CCIfSwiftError>>, -======= - // A SwiftSelf is passed in R9. - CCIfSwiftSelf>>, - ->>>>>>> mirror/master // HFAs are passed in a contiguous block of registers, or on the stack CCIfConsecutiveRegs>, From 4abe07ef9f2a3e26f417930f113236c74e14f71c Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 30 Mar 2016 15:50:56 -0700 Subject: [PATCH 045/585] [Import as member] Module map attribute swift_infer_import_as_member Introduces a new module map attribute, allowing a module to opt-into import-as-member inference. This allows the provider of the APIs to use inference, rather than require building with a flag. apple-llvm-split-commit: 74fdc2be88845ac7e1fd24e12eb11bc44d400db2 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Module.h | 3 +++ clang/include/clang/Lex/ModuleMap.h | 3 +++ clang/lib/Basic/Module.cpp | 2 ++ clang/lib/Lex/ModuleMap.cpp | 9 ++++++++- clang/test/Modules/Inputs/swift_name/module.modulemap | 2 ++ clang/test/Modules/infer_swift_name.m | 6 ++++++ 6 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 clang/test/Modules/Inputs/swift_name/module.modulemap create mode 100644 clang/test/Modules/infer_swift_name.m diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h index c95968d9a755f..f8bdf0618fe9d 100644 --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -183,6 +183,9 @@ class Module { /// \brief Whether this is an inferred submodule (module * { ... }). unsigned IsInferred : 1; + /// \brief Whether this is a module who has its swift_names inferred. + unsigned IsSwiftInferImportAsMember : 1; + /// \brief Whether we should infer submodules for this module based on /// the headers. /// diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h index d9d66c1065074..4d7d4436ac6c6 100644 --- a/clang/include/clang/Lex/ModuleMap.h +++ b/clang/include/clang/Lex/ModuleMap.h @@ -176,6 +176,9 @@ class ModuleMap { /// \brief Whether this is an exhaustive set of configuration macros. unsigned IsExhaustive : 1; + + /// \brief Whether this is a module who has its swift_names inferred. + unsigned IsSwiftInferImportAsMember : 1; }; /// \brief A directory for which framework modules can be inferred. diff --git a/clang/lib/Basic/Module.cpp b/clang/lib/Basic/Module.cpp index 48d36b67db3fe..fbae283fa6964 100644 --- a/clang/lib/Basic/Module.cpp +++ b/clang/lib/Basic/Module.cpp @@ -337,6 +337,8 @@ void Module::print(raw_ostream &OS, unsigned Indent) const { OS << " [system]"; if (IsExternC) OS << " [extern_c]"; + if (IsSwiftInferImportAsMember) + OS << " [swift_infer_import_as_member]"; } OS << " {\n"; diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp index 8fe88c8197323..8a2d2584de8e7 100644 --- a/clang/lib/Lex/ModuleMap.cpp +++ b/clang/lib/Lex/ModuleMap.cpp @@ -1301,7 +1301,9 @@ namespace { /// \brief The 'extern_c' attribute. AT_extern_c, /// \brief The 'exhaustive' attribute. - AT_exhaustive + AT_exhaustive, + // \brief The 'swift_infer_import_as_member' attribute. + AT_swift_infer_import_as_member, }; } @@ -2379,6 +2381,7 @@ bool ModuleMapParser::parseOptionalAttributes(Attributes &Attrs) { .Case("exhaustive", AT_exhaustive) .Case("extern_c", AT_extern_c) .Case("system", AT_system) + .Case("swift_infer_import_as_member", AT_swift_infer_import_as_member) .Default(AT_unknown); switch (Attribute) { case AT_unknown: @@ -2394,6 +2397,10 @@ bool ModuleMapParser::parseOptionalAttributes(Attributes &Attrs) { Attrs.IsExternC = true; break; + case AT_swift_infer_import_as_member: + Attrs.IsSwiftInferImportAsMember = true; + break; + case AT_exhaustive: Attrs.IsExhaustive = true; break; diff --git a/clang/test/Modules/Inputs/swift_name/module.modulemap b/clang/test/Modules/Inputs/swift_name/module.modulemap new file mode 100644 index 0000000000000..b7ec6b15988b0 --- /dev/null +++ b/clang/test/Modules/Inputs/swift_name/module.modulemap @@ -0,0 +1,2 @@ +module SwiftNameInferred [swift_infer_import_as_member] { +} \ No newline at end of file diff --git a/clang/test/Modules/infer_swift_name.m b/clang/test/Modules/infer_swift_name.m new file mode 100644 index 0000000000000..d4b4a5d4979bd --- /dev/null +++ b/clang/test/Modules/infer_swift_name.m @@ -0,0 +1,6 @@ +// RUN: rm -rf %t +// RUN: %clang_cc1 -fmodules-cache-path=%t -fmodules -fimplicit-module-maps -I %S/Inputs/swift_name %s -verify +// REQUIRES: shell + +@import SwiftNameInferred; // ok +@import SwiftName; // expected-error{{module 'SwiftName' not found}} From e1754572da6f9a072840c30b09fd18dc8640bd7e Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 30 Mar 2016 17:36:32 -0700 Subject: [PATCH 046/585] [Import as member] APINotes for SwiftInferImportAsMember. Adds APINotes support for adding the swift_infer_import_as_member module map attribute. apple-llvm-split-commit: 7b3e765344c9c89e9b083b92101438d37f41162d apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 3 +++ clang/include/clang/APINotes/APINotesWriter.h | 3 +++ clang/include/clang/APINotes/Types.h | 5 +++++ clang/lib/APINotes/APINotesFormat.h | 8 +++++++- clang/lib/APINotes/APINotesReader.cpp | 10 ++++++++++ clang/lib/APINotes/APINotesWriter.cpp | 12 ++++++++++++ clang/lib/APINotes/APINotesYAMLCompiler.cpp | 11 +++++++++++ clang/test/APINotes/Inputs/Headers/APINotes.apinotes | 1 + clang/test/APINotes/Inputs/roundtrip.apinotes | 1 + 9 files changed, 53 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 839e1dc5ee942..29dcc1f56f920 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -50,6 +50,9 @@ class APINotesReader { /// notes. StringRef getModuleName() const; + /// Retrieve the module options + ModuleOptions getModuleOptions() const; + /// Look for information regarding the given Objective-C class. /// /// \param name The name of the class we're looking for. diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h index 66dc8ffebbaeb..aa41756b2e778 100644 --- a/clang/include/clang/APINotes/APINotesWriter.h +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -107,6 +107,9 @@ class APINotesWriter { /// \param name The name of this typedef. /// \param info Information about this typedef. void addTypedef(StringRef name, const TypedefInfo &info); + + /// Add module options + void addModuleOptions(ModuleOptions opts); }; } // end namespace api_notes diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 2fc4ee8fe7328..7b4d0647ef24d 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -474,6 +474,11 @@ class TypedefInfo : public CommonTypeInfo { TypedefInfo() : CommonTypeInfo() { } }; +/// Descripts a series of options for a module +struct ModuleOptions { + bool SwiftInferImportAsMember; +}; + } // end namespace api_notes } // end namespace clang diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 2e52a4d7fddf5..bc57e451ec549 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -105,7 +105,8 @@ namespace control_block { // VERSION_MAJOR. enum { METADATA = 1, - MODULE_NAME = 2 + MODULE_NAME = 2, + MODULE_OPTIONS = 3 }; using MetadataLayout = BCRecordLayout< @@ -118,6 +119,11 @@ namespace control_block { MODULE_NAME, BCBlob // Module name >; + + using ModuleOptionsLayout = BCRecordLayout< + MODULE_OPTIONS, + BCFixed<1> // SwiftInferImportAsMember + >; } namespace identifier_block { diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 40f0f8938857d..67574c9d4b2c9 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -562,6 +562,9 @@ class APINotesReader::Implementation { /// The name of the module that we read from the control block. std::string ModuleName; + /// Various options and attributes for the module + ModuleOptions moduleOptions; + using SerializedIdentifierTable = llvm::OnDiskIterableChainedHashTable; @@ -735,6 +738,9 @@ bool APINotesReader::Implementation::readControlBlock( ModuleName = blobData.str(); break; + case control_block::MODULE_OPTIONS: + moduleOptions = {(scratch.front() & 1) != 0}; + default: // Unknown metadata record, possibly for use by a future version of the // module format. @@ -1440,6 +1446,10 @@ StringRef APINotesReader::getModuleName() const { return Impl.ModuleName; } +ModuleOptions APINotesReader::getModuleOptions() const { + return Impl.moduleOptions; +} + auto APINotesReader::lookupObjCClass(StringRef name) -> Optional> { if (!Impl.ObjCContextTable) diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index dd90ee2e59012..d2bd6f19ec88c 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -43,6 +43,8 @@ class APINotesWriter::Implementation { /// The name of the module std::string ModuleName; + bool SwiftInferImportAsMember = false; + /// Information about Objective-C contexts (classes or protocols). /// /// Indexed by the identifier ID and a bit indication whether we're looking @@ -214,6 +216,11 @@ void APINotesWriter::Implementation::writeControlBlock( control_block::ModuleNameLayout moduleName(writer); moduleName.emit(ScratchRecord, ModuleName); + + if (SwiftInferImportAsMember) { + control_block::ModuleOptionsLayout moduleOptions(writer); + moduleOptions.emit(ScratchRecord, SwiftInferImportAsMember); + } } namespace { @@ -1090,3 +1097,8 @@ void APINotesWriter::addTypedef(llvm::StringRef name, const TypedefInfo &info) { assert(!Impl.Typedefs.count(typedefID)); Impl.Typedefs[typedefID] = info; } + +void APINotesWriter::addModuleOptions(ModuleOptions opts) { + Impl.SwiftInferImportAsMember = opts.SwiftInferImportAsMember; +} + diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 4e84b9fbf1a79..6fe87bc7fa256 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -252,6 +252,8 @@ namespace { TagsSeq Tags; TypedefsSeq Typedefs; + llvm::Optional SwiftInferImportAsMember; + LLVM_ATTRIBUTE_DEPRECATED( void dump() LLVM_ATTRIBUTE_USED, "only for use within the debugger"); @@ -418,6 +420,7 @@ namespace llvm { io.mapRequired("Name", m.Name); io.mapOptional("Availability", m.Availability.Mode); io.mapOptional("AvailabilityMsg", m.Availability.Msg); + io.mapOptional("SwiftInferImportAsMember", m.SwiftInferImportAsMember); io.mapOptional("Classes", m.Classes); io.mapOptional("Protocols", m.Protocols); io.mapOptional("Functions", m.Functions); @@ -773,6 +776,9 @@ namespace { Writer->addTypedef(t.Name, typedefInfo); } + if (TheModule.SwiftInferImportAsMember) + Writer->addModuleOptions({true}); + if (!ErrorOccured) Writer->writeToStream(OS); @@ -1034,6 +1040,11 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, // Set module name. module.Name = reader->getModuleName(); + // Set module options + auto opts = reader->getModuleOptions(); + if (opts.SwiftInferImportAsMember) + module.SwiftInferImportAsMember = true; + // Sort classes. std::sort(module.Classes.begin(), module.Classes.end(), [](const Class &lhs, const Class &rhs) -> bool { diff --git a/clang/test/APINotes/Inputs/Headers/APINotes.apinotes b/clang/test/APINotes/Inputs/Headers/APINotes.apinotes index a4ddafe2892ec..08210fc705651 100644 --- a/clang/test/APINotes/Inputs/Headers/APINotes.apinotes +++ b/clang/test/APINotes/Inputs/Headers/APINotes.apinotes @@ -1,4 +1,5 @@ Name: HeaderLib +SwiftInferImportAsMember: true Functions: - Name: custom_realloc NullabilityOfRet: N diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index c34907ff67dff..8509c79389b71 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -2,6 +2,7 @@ Name: AppKit Availability: available AvailabilityMsg: '' +SwiftInferImportAsMember: true Classes: - Name: NSCell Availability: available From 7a5269f2976b4c8361a3f16c26c2e4cbb3b27625 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Fri, 1 Apr 2016 11:22:42 -0700 Subject: [PATCH 047/585] [APINotes] Apply ModuleOptions CompilerInstance will now check for ModuleOptions in the current module's APINotes, and apply them if present. apple-llvm-split-commit: 9defcfa9fd3486b91fad78974dafcb12cdd4c786 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesManager.h | 4 ++++ clang/lib/Frontend/CompilerInstance.cpp | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h index 69752a9c77e90..7f540b9c17dee 100644 --- a/clang/include/clang/APINotes/APINotesManager.h +++ b/clang/include/clang/APINotes/APINotesManager.h @@ -111,6 +111,10 @@ class APINotesManager { /// Find the API notes reader that corresponds to the given source location. APINotesReader *findAPINotes(SourceLocation Loc); + + APINotesReader *getCurrentModuleReader() { + return CurrentModuleReader.get(); + } }; } // end namespace api_notes diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 46f9d43d169cf..e4f8c597064b0 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "clang/Frontend/CompilerInstance.h" +#include "clang/APINotes/APINotesReader.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" @@ -537,10 +538,21 @@ void CompilerInstance::createSema(TranslationUnitKind TUKind, TUKind, CompletionConsumer)); // If we're building a module, notify the API notes manager. - if (!getLangOpts().CurrentModule.empty()) { + StringRef currentModuleName = getLangOpts().CurrentModule; + if (!currentModuleName.empty()) { (void)TheSema->APINotes.loadCurrentModuleAPINotes( - getLangOpts().CurrentModule, + currentModuleName, getAPINotesOpts().ModuleSearchPaths); + // Check for any attributes we should add to the module + if (auto curReader = TheSema->APINotes.getCurrentModuleReader()) { + auto currentModule = getPreprocessor().getCurrentModule(); + assert(currentModule && "how can we have a reader for it?"); + + // swift_infer_import_as_member + if (curReader->getModuleOptions().SwiftInferImportAsMember) { + currentModule->IsSwiftInferImportAsMember = true; + } + } } } From 85cc59e541628ae283495b1004a7e3f43c89841a Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Fri, 1 Apr 2016 13:34:49 -0700 Subject: [PATCH 048/585] [APINotes] Incorporate Doug's feedback apple-llvm-split-commit: d3f65076e94c422efe213c9a3c8f015be8bd0bf2 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index bc57e451ec549..79143c48533db 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 10; // enum constants +const uint16_t VERSION_MINOR = 11; // SwiftInferImportAsMember using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 67574c9d4b2c9..b05eb9d603db6 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -563,7 +563,7 @@ class APINotesReader::Implementation { std::string ModuleName; /// Various options and attributes for the module - ModuleOptions moduleOptions; + ModuleOptions ModuleOpts; using SerializedIdentifierTable = llvm::OnDiskIterableChainedHashTable; @@ -739,7 +739,8 @@ bool APINotesReader::Implementation::readControlBlock( break; case control_block::MODULE_OPTIONS: - moduleOptions = {(scratch.front() & 1) != 0}; + ModuleOpts.SwiftInferImportAsMember = (scratch.front() & 1) != 0; + break; default: // Unknown metadata record, possibly for use by a future version of the @@ -1447,7 +1448,7 @@ StringRef APINotesReader::getModuleName() const { } ModuleOptions APINotesReader::getModuleOptions() const { - return Impl.moduleOptions; + return Impl.ModuleOpts; } auto APINotesReader::lookupObjCClass(StringRef name) From ffe0b11e1fc902112f02ab46c03a9ec2fb910bce Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Fri, 1 Apr 2016 23:23:52 +0000 Subject: [PATCH 049/585] [Objective-C] Introduce objc_runtime_visible attribute. The objc_runtime_visible attribute deals with an odd corner case where a particular Objective-C class is known to the Objective-C runtime (and, therefore, accessible by name) but its symbol has been hidden for some reason. For such classes, teach CodeGen to use objc_lookUpClass to retrieve the Class object, rather than referencing the class symbol directly. Classes annotated with objc_runtime_visible have two major limitations that fall out from places where Objective-C metadata needs to refer to the class (or metaclass) symbol directly: * One cannot implement a subclass of an objc_runtime_visible class. * One cannot implement a category on an objc_runtime_visible class. Implements rdar://problem/25494092. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@265201 91177308-0d34-0410-b5e6-96231b3b80d8 apple-llvm-split-commit: dfc8d2d85ae7bc36824c3f3427207710c9051d6e apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Attr.td | 6 +++ clang/include/clang/Basic/AttrDocs.td | 7 +++ .../clang/Basic/DiagnosticSemaKinds.td | 6 +++ clang/lib/CodeGen/CGObjCMac.cpp | 49 +++++++++++++++++++ clang/lib/Sema/SemaDeclAttr.cpp | 3 ++ clang/lib/Sema/SemaDeclObjC.cpp | 17 +++++++ .../CodeGenObjC/attr-objc-runtime-visible.m | 19 +++++++ .../test/SemaObjC/attr-objc-runtime-visible.m | 19 +++++++ 8 files changed, 126 insertions(+) create mode 100644 clang/test/CodeGenObjC/attr-objc-runtime-visible.m create mode 100644 clang/test/SemaObjC/attr-objc-runtime-visible.m diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 85a54b6569505..cab04d247ff58 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1269,6 +1269,12 @@ def ObjCRuntimeName : Attr { let Documentation = [ObjCRuntimeNameDocs]; } +def ObjCRuntimeVisible : Attr { + let Spellings = [GNU<"objc_runtime_visible">]; + let Subjects = SubjectList<[ObjCInterface], ErrorDiag>; + let Documentation = [ObjCRuntimeVisibleDocs]; +} + def ObjCBoxable : Attr { let Spellings = [GNU<"objc_boxable">]; let Subjects = SubjectList<[Record], ErrorDiag, "ExpectedStructOrUnion">; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 521b38f650997..317fdb4385032 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -612,6 +612,13 @@ can only be placed before an @protocol or @interface declaration: }]; } +def ObjCRuntimeVisibleDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +This attribute specifies that the Objective-C class to which it applies is visible to the Objective-C runtime but not to the linker. Classes annotated with this attribute cannot be subclassed and cannot have categories defined for them. + }]; +} + def ObjCBoxableDocs : Documentation { let Category = DocCatFunction; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 85e9f5b3f41ec..edf9d47eddc3c 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -717,6 +717,12 @@ def err_restricted_superclass_mismatch : Error< def warn_objc_root_class_missing : Warning< "class %0 defined without specifying a base class">, InGroup; +def err_objc_runtime_visible_category : Error< + "cannot implement a category for class %0 that is only visible via the " + "Objective-C runtime">; +def err_objc_runtime_visible_subclass : Error< + "cannot implement subclass %0 of a superclass %1 that is only visible via the " + "Objective-C runtime">; def note_objc_needs_superclass : Note< "add a super class to fix this problem">; def warn_dup_category_def : Warning< diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp index b2301bdaf2a78..df98ae0669a4b 100644 --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -349,6 +349,20 @@ class ObjCCommonTypesHelper { return CGM.CreateRuntimeFunction(FTy, "objc_enumerationMutation"); } + llvm::Constant *getLookUpClassFn() { + CodeGen::CodeGenTypes &Types = CGM.getTypes(); + ASTContext &Ctx = CGM.getContext(); + // Class objc_lookUpClass (const char *) + SmallVector Params; + Params.push_back( + Ctx.getCanonicalType(Ctx.getPointerType(Ctx.CharTy.withConst()))); + llvm::FunctionType *FTy = + Types.GetFunctionType(Types.arrangeBuiltinFunctionDeclaration( + Ctx.getCanonicalType(Ctx.getObjCClassType()), + Params)); + return CGM.CreateRuntimeFunction(FTy, "objc_lookUpClass"); + } + /// GcReadWeakFn -- LLVM objc_read_weak (id *src) function. llvm::Constant *getGcReadWeakFn() { // id objc_read_weak (id *) @@ -981,6 +995,12 @@ class CGObjCCommonMac : public CodeGen::CGObjCRuntime { /// defined. The return value has type ProtocolPtrTy. llvm::Constant *GetProtocolRef(const ObjCProtocolDecl *PD); + /// Return a reference to the given Class using runtime calls rather than + /// by a symbol reference. + llvm::Value *EmitClassRefViaRuntime(CodeGenFunction &CGF, + const ObjCInterfaceDecl *ID, + ObjCCommonTypesHelper &ObjCTypes); + public: /// CreateMetadataVar - Create a global variable with internal /// linkage for use by the Objective-C runtime. @@ -2674,6 +2694,25 @@ llvm::Constant *CGObjCCommonMac::GetProtocolRef(const ObjCProtocolDecl *PD) { return GetOrEmitProtocolRef(PD); } +llvm::Value *CGObjCCommonMac::EmitClassRefViaRuntime( + CodeGenFunction &CGF, + const ObjCInterfaceDecl *ID, + ObjCCommonTypesHelper &ObjCTypes) { + llvm::Constant *lookUpClassFn = ObjCTypes.getLookUpClassFn(); + + llvm::Value *className = + CGF.CGM.GetAddrOfConstantCString(ID->getObjCRuntimeNameAsString()) + .getPointer(); + ASTContext &ctx = CGF.CGM.getContext(); + className = + CGF.Builder.CreateBitCast(className, + CGF.ConvertType( + ctx.getPointerType(ctx.CharTy.withConst()))); + llvm::CallInst *call = CGF.Builder.CreateCall(lookUpClassFn, className); + call->setDoesNotThrow(); + return call; +} + /* // Objective-C 1.0 extensions struct _objc_protocol { @@ -4634,6 +4673,11 @@ llvm::Value *CGObjCMac::EmitClassRefFromId(CodeGenFunction &CGF, llvm::Value *CGObjCMac::EmitClassRef(CodeGenFunction &CGF, const ObjCInterfaceDecl *ID) { + // If the class has the objc_runtime_visible attribute, we need to + // use the Objective-C runtime to get the class. + if (ID->hasAttr()) + return EmitClassRefViaRuntime(CGF, ID, ObjCTypes); + return EmitClassRefFromId(CGF, ID->getIdentifier()); } @@ -6894,6 +6938,11 @@ llvm::Value *CGObjCNonFragileABIMac::EmitClassRefFromId(CodeGenFunction &CGF, llvm::Value *CGObjCNonFragileABIMac::EmitClassRef(CodeGenFunction &CGF, const ObjCInterfaceDecl *ID) { + // If the class has the objc_runtime_visible attribute, we need to + // use the Objective-C runtime to get the class. + if (ID->hasAttr()) + return EmitClassRefViaRuntime(CGF, ID, ObjCTypes); + return EmitClassRefFromId(CGF, ID->getIdentifier(), ID->isWeakImported(), ID); } diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index f9e469621db67..4dae401c3d937 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5926,6 +5926,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_ObjCRuntimeName: handleObjCRuntimeName(S, D, Attr); break; + case AttributeList::AT_ObjCRuntimeVisible: + handleSimpleAttribute(S, D, Attr); + break; case AttributeList::AT_ObjCBoxable: handleObjCBoxable(S, D, Attr); break; diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index b7eec4750b5c7..60410bc62b89a 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -1836,6 +1836,13 @@ Decl *Sema::ActOnStartCategoryImplementation( if (IDecl) DiagnoseUseOfDecl(IDecl, ClassLoc); + // If the interface has the objc_runtime_visible attribute, we + // cannot implement a category for it. + if (IDecl && IDecl->hasAttr()) { + Diag(ClassLoc, diag::err_objc_runtime_visible_category) + << IDecl->getDeclName(); + } + /// Check that CatName, category name, is not used in another implementation. if (CatIDecl) { if (CatIDecl->getImplementation()) { @@ -1973,6 +1980,16 @@ Decl *Sema::ActOnStartClassImplementation( dyn_cast(IDecl), IMPDecl->getLocation(), 1); } + + // If the superclass has the objc_runtime_visible attribute, we + // cannot implement a subclass of it. + if (IDecl->getSuperClass() && + IDecl->getSuperClass()->hasAttr()) { + Diag(ClassLoc, diag::err_objc_runtime_visible_subclass) + << IDecl->getDeclName() + << IDecl->getSuperClass()->getDeclName(); + } + return ActOnObjCContainerStartDefinition(IMPDecl); } diff --git a/clang/test/CodeGenObjC/attr-objc-runtime-visible.m b/clang/test/CodeGenObjC/attr-objc-runtime-visible.m new file mode 100644 index 0000000000000..6e224e7189037 --- /dev/null +++ b/clang/test/CodeGenObjC/attr-objc-runtime-visible.m @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fobjc-runtime=macosx-10.9.0 -emit-llvm %s -o - | FileCheck %s + +// RUN: %clang_cc1 -triple i386-apple-darwin -fobjc-runtime=macosx-fragile-10.9.0 -emit-llvm %s -o - | FileCheck %s + +@interface Root ++(Class)class; +@end + +__attribute__((objc_runtime_visible)) +__attribute__((objc_runtime_name("MyRuntimeVisibleClass"))) +@interface A : Root +@end + +// CHECK: [[CLASSNAME:@.*]] = private unnamed_addr constant [22 x i8] c"MyRuntimeVisibleClass +// CHECK: define i8* @getClass() #0 { +Class getClass(void) { + // CHECK: call i8* @objc_lookUpClass(i8* getelementptr inbounds ([22 x i8], [22 x i8]* [[CLASSNAME]], i32 0, i32 0)) #2 + return [A class]; +} diff --git a/clang/test/SemaObjC/attr-objc-runtime-visible.m b/clang/test/SemaObjC/attr-objc-runtime-visible.m new file mode 100644 index 0000000000000..b5ec809ff2f58 --- /dev/null +++ b/clang/test/SemaObjC/attr-objc-runtime-visible.m @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -verify -fsyntax-only %s + +__attribute__((objc_runtime_visible)) +@interface A +@end + +@interface A(X) +@end + +@implementation A(X) // expected-error{{cannot implement a category for class 'A' that is only visible via the Objective-C runtime}} +@end + +@interface B : A +@end + +@implementation B // expected-error{{cannot implement subclass 'B' of a superclass 'A' that is only visible via the Objective-C runtime}} +@end + + From 4b14de0a03dbfdd83811d71ed1c5fb4e4875c301 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Sat, 2 Apr 2016 13:59:41 -0700 Subject: [PATCH 050/585] [Import as member] Default initialize new attribute to false apple-llvm-split-commit: 9c3676d2786793ded0d8576770cdc5943dfe4597 apple-llvm-split-dir: clang/ --- clang/lib/Basic/Module.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Basic/Module.cpp b/clang/lib/Basic/Module.cpp index fbae283fa6964..3cad40c7c0429 100644 --- a/clang/lib/Basic/Module.cpp +++ b/clang/lib/Basic/Module.cpp @@ -31,7 +31,8 @@ Module::Module(StringRef Name, SourceLocation DefinitionLoc, Module *Parent, IsMissingRequirement(false), HasIncompatibleModuleFile(false), IsAvailable(true), IsFromModuleFile(false), IsFramework(IsFramework), IsExplicit(IsExplicit), IsSystem(false), IsExternC(false), - IsInferred(false), InferSubmodules(false), InferExplicitSubmodules(false), + IsInferred(false), IsSwiftInferImportAsMember(false), + InferSubmodules(false), InferExplicitSubmodules(false), InferExportWildcard(false), ConfigMacrosExhaustive(false), NameVisibility(Hidden) { if (Parent) { From af8875d5137d023d3f405119099e3fc1434a52b1 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 4 Apr 2016 11:48:49 -0700 Subject: [PATCH 051/585] [Import as member] ASTReader/Writer for swift_infer_import_as_member apple-llvm-split-commit: 097804bb99d4afb1f0bb3f543e028a404ab8f6a2 apple-llvm-split-dir: clang/ --- clang/lib/Serialization/ASTReader.cpp | 2 ++ clang/lib/Serialization/ASTWriter.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index c0517e7db81ff..3dee7236a9888 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -4488,6 +4488,7 @@ ASTReader::ReadSubmoduleBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { bool IsExplicit = Record[Idx++]; bool IsSystem = Record[Idx++]; bool IsExternC = Record[Idx++]; + bool IsSwiftInferImportAsMember = Record[Idx++]; bool InferSubmodules = Record[Idx++]; bool InferExplicitSubmodules = Record[Idx++]; bool InferExportWildcard = Record[Idx++]; @@ -4532,6 +4533,7 @@ ASTReader::ReadSubmoduleBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { CurrentModule->IsFromModuleFile = true; CurrentModule->IsSystem = IsSystem || CurrentModule->IsSystem; CurrentModule->IsExternC = IsExternC; + CurrentModule->IsSwiftInferImportAsMember = IsSwiftInferImportAsMember; CurrentModule->InferSubmodules = InferSubmodules; CurrentModule->InferExplicitSubmodules = InferExplicitSubmodules; CurrentModule->InferExportWildcard = InferExportWildcard; diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index c77a7099642db..e520d13e1e2ca 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -2428,6 +2428,7 @@ void ASTWriter::WriteSubmodules(Module *WritingModule) { Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsExplicit Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsSystem Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsExternC + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsSwiftInferIAM... Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // InferSubmodules... Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // InferExplicit... Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // InferExportWild... @@ -2522,9 +2523,9 @@ void ASTWriter::WriteSubmodules(Module *WritingModule) { { RecordData::value_type Record[] = { SUBMODULE_DEFINITION, ID, ParentID, Mod->IsFramework, Mod->IsExplicit, - Mod->IsSystem, Mod->IsExternC, Mod->InferSubmodules, - Mod->InferExplicitSubmodules, Mod->InferExportWildcard, - Mod->ConfigMacrosExhaustive}; + Mod->IsSystem, Mod->IsExternC, Mod->IsSwiftInferImportAsMember, + Mod->InferSubmodules, Mod->InferExplicitSubmodules, + Mod->InferExportWildcard, Mod->ConfigMacrosExhaustive}; Stream.EmitRecordWithBlob(DefinitionAbbrev, Record, Mod->Name); } From bdc785943492921c6e834b978952194692fa1801 Mon Sep 17 00:00:00 2001 From: John McCall Date: Mon, 4 Apr 2016 15:57:22 -0700 Subject: [PATCH 052/585] Fix for the fact that the IR asm writer actually implements swiftcc correctly on this branch. apple-llvm-split-commit: aee0cff195d01ce19cf013efab2a6d31113e87cc apple-llvm-split-dir: clang/ --- clang/test/CodeGen/arm-swiftcall.c | 2 +- clang/test/CodeGenCXX/arm-swiftcall.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/CodeGen/arm-swiftcall.c b/clang/test/CodeGen/arm-swiftcall.c index f8c97c418a5ab..d54a31337085f 100644 --- a/clang/test/CodeGen/arm-swiftcall.c +++ b/clang/test/CodeGen/arm-swiftcall.c @@ -47,7 +47,7 @@ void test_context_error_1() { // CHECK: [[TEMP:%.*]] = alloca swifterror float*, align 4 // CHECK: [[T0:%.*]] = load float*, float** [[ERROR]], align 4 // CHECK: store float* [[T0]], float** [[TEMP]], align 4 -// CHECK: call [[SWIFTCC:cc16]] void @context_error_1(i32* swiftself [[X]], float** swifterror [[TEMP]]) +// CHECK: call [[SWIFTCC:swiftcc]] void @context_error_1(i32* swiftself [[X]], float** swifterror [[TEMP]]) // CHECK: [[T0:%.*]] = load float*, float** [[TEMP]], align 4 // CHECK: store float* [[T0]], float** [[ERROR]], align 4 diff --git a/clang/test/CodeGenCXX/arm-swiftcall.cpp b/clang/test/CodeGenCXX/arm-swiftcall.cpp index d67a9a0282d6f..535350c808d3a 100644 --- a/clang/test/CodeGenCXX/arm-swiftcall.cpp +++ b/clang/test/CodeGenCXX/arm-swiftcall.cpp @@ -77,7 +77,7 @@ TEST(struct_1); // CHECK: ret void // CHECK-LABEL: define void @test_struct_1() // CHECK: [[TMP:%.*]] = alloca [[REC]], align 4 -// CHECK: [[CALL:%.*]] = call [[SWIFTCC:cc16]] [[UAGG]] @return_struct_1() +// CHECK: [[CALL:%.*]] = call [[SWIFTCC:swiftcc]] [[UAGG]] @return_struct_1() // CHECK: [[CAST_TMP:%.*]] = bitcast [[REC]]* [[TMP]] to [[AGG]]* // CHECK: [[T0:%.*]] = getelementptr inbounds [[AGG]], [[AGG]]* [[CAST_TMP]], i32 0, i32 0 // CHECK: [[T1:%.*]] = extractvalue [[UAGG]] [[CALL]], 0 From e780b78883bb413db6f866424d47b5982294cd8a Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 5 Apr 2016 09:45:49 -0700 Subject: [PATCH 053/585] Ignore redundant, implicit swift_name attributes. apple-llvm-split-commit: fba0d40ddee1793f9dc7bc15608bbd3b2cccf25f apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaDeclAttr.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 4dae401c3d937..c46b138925e7a 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -3689,8 +3689,12 @@ SwiftNameAttr *Sema::mergeSwiftNameAttr(Decl *D, SourceRange Range, // FIXME: Warn about an incompatible override. return nullptr; } - Diag(Inline->getLocation(), diag::warn_attribute_ignored) << Inline; - Diag(Range.getBegin(), diag::note_conflicting_attribute); + + if (Inline->getName() != Name && !Inline->isImplicit()) { + Diag(Inline->getLocation(), diag::warn_attribute_ignored) << Inline; + Diag(Range.getBegin(), diag::note_conflicting_attribute); + } + D->dropAttr(); } From edef80528ccb9ad635756bbf05f32a2f04090b77 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 5 Apr 2016 09:47:08 -0700 Subject: [PATCH 054/585] [API notes] Make sure to remove our module caches before running tests. apple-llvm-split-commit: b5ef83c0a635f1049baf466eb4321784934fc434 apple-llvm-split-dir: clang/ --- clang/test/APINotes/nullability.c | 1 + clang/test/APINotes/nullability.m | 1 + clang/test/APINotes/objc_designated_inits.m | 1 + 3 files changed, 3 insertions(+) diff --git a/clang/test/APINotes/nullability.c b/clang/test/APINotes/nullability.c index 54b0df6cea94c..86054eade82e4 100644 --- a/clang/test/APINotes/nullability.c +++ b/clang/test/APINotes/nullability.c @@ -1,3 +1,4 @@ +// RUN: rm -rf %t && mkdir -p %t // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #include "HeaderLib.h" diff --git a/clang/test/APINotes/nullability.m b/clang/test/APINotes/nullability.m index ba51cd64d825f..ce4beabb597ff 100644 --- a/clang/test/APINotes/nullability.m +++ b/clang/test/APINotes/nullability.m @@ -1,3 +1,4 @@ +// RUN: rm -rf %t && mkdir -p %t // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #import diff --git a/clang/test/APINotes/objc_designated_inits.m b/clang/test/APINotes/objc_designated_inits.m index 5eb5fa103d5d5..bbb50ba1a74bc 100644 --- a/clang/test/APINotes/objc_designated_inits.m +++ b/clang/test/APINotes/objc_designated_inits.m @@ -1,3 +1,4 @@ +// RUN: rm -rf %t && mkdir -p %t // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #include "HeaderLib.h" From 7ea89a69d5a6586e03c2698c307998292ad5d69c Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Tue, 5 Apr 2016 16:17:46 -0700 Subject: [PATCH 055/585] Resolve merge error for r265482. apple-llvm-split-commit: 4a40a31dcde209ea82bbba284ccfb49edf06b515 apple-llvm-split-dir: llvm/ --- llvm/lib/Target/ARM/ARMFastISel.cpp | 1 - llvm/lib/Target/ARM/ARMISelLowering.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/llvm/lib/Target/ARM/ARMFastISel.cpp b/llvm/lib/Target/ARM/ARMFastISel.cpp index b3154bd8c0438..8bbad22ee3a91 100644 --- a/llvm/lib/Target/ARM/ARMFastISel.cpp +++ b/llvm/lib/Target/ARM/ARMFastISel.cpp @@ -1861,7 +1861,6 @@ CCAssignFn *ARMFastISel::CCAssignFnForCall(CallingConv::ID CC, default: llvm_unreachable("Unsupported calling convention"); case CallingConv::Fast: - case CallingConv::Swift: if (Subtarget->hasVFP2() && !isVarArg) { if (!Subtarget->isAAPCS_ABI()) return (Return ? RetFastCC_ARM_APCS : FastCC_ARM_APCS); diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp index b2d9fb95ed7c7..00446b61ed7c3 100644 --- a/llvm/lib/Target/ARM/ARMISelLowering.cpp +++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp @@ -1400,7 +1400,6 @@ ARMTargetLowering::getEffectiveCallingConv(CallingConv::ID CC, else return CallingConv::ARM_AAPCS; case CallingConv::Fast: - case CallingConv::Swift: case CallingConv::CXX_FAST_TLS: if (!Subtarget->isAAPCS_ABI()) { if (Subtarget->hasVFP2() && !Subtarget->isThumb1Only() && !isVarArg) From 4be3aa4419ed070406cac96b807dc9751e384da3 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 7 Apr 2016 10:38:40 -0700 Subject: [PATCH 056/585] [API notes] Process API notes for enumerators. apple-llvm-split-commit: f959c76dc38d96aea9bfd61df29655798957267b apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaDecl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index e421453cd19fe..b02cbbc5b36a7 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -14363,6 +14363,8 @@ Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst, // Process attributes. if (Attr) ProcessDeclAttributeList(S, New, Attr); + ProcessAPINotes(New); + // Register this decl in the current scope stack. New->setAccess(TheEnumDecl->getAccess()); PushOnScopeChains(New, S); From 8d420d7a99da1f24ee745493bcd9677aaf909d8a Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Mon, 11 Apr 2016 21:08:06 +0000 Subject: [PATCH 057/585] Swift Calling Convention: swifterror target support. Differential Revision: http://reviews.llvm.org/D18716 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@265997 91177308-0d34-0410-b5e6-96231b3b80d8 apple-llvm-split-commit: 86c76e126c4c8d714257e409ed1908113365a9dc apple-llvm-split-dir: llvm/ --- .../AArch64/AArch64CallingConvention.td | 6 +- llvm/lib/Target/AArch64/AArch64FastISel.cpp | 40 ++++--- .../Target/AArch64/AArch64FrameLowering.cpp | 3 +- llvm/lib/Target/AArch64/AArch64ISelLowering.h | 8 +- .../Target/AArch64/AArch64RegisterInfo.cpp | 30 ++--- llvm/lib/Target/AArch64/AArch64RegisterInfo.h | 5 - llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp | 4 +- llvm/lib/Target/ARM/ARMCallingConv.td | 6 +- llvm/lib/Target/ARM/ARMFastISel.cpp | 40 ++++--- llvm/lib/Target/ARM/ARMISelLowering.h | 8 +- llvm/lib/Target/X86/X86CallingConv.td | 4 +- llvm/lib/Target/X86/X86FastISel.cpp | 40 ++++--- llvm/lib/Target/X86/X86ISelLowering.cpp | 19 +++ llvm/lib/Target/X86/X86ISelLowering.h | 8 +- llvm/lib/Target/X86/X86RegisterInfo.cpp | 10 +- llvm/test/CodeGen/AArch64/swifterror.ll | 56 +++++---- llvm/test/CodeGen/ARM/swifterror.ll | 55 +++++---- llvm/test/CodeGen/X86/swifterror.ll | 111 +++++++++++++++--- 18 files changed, 293 insertions(+), 160 deletions(-) diff --git a/llvm/lib/Target/AArch64/AArch64CallingConvention.td b/llvm/lib/Target/AArch64/AArch64CallingConvention.td index 7877a7b6da4ac..f6bd012d8c8bf 100644 --- a/llvm/lib/Target/AArch64/AArch64CallingConvention.td +++ b/llvm/lib/Target/AArch64/AArch64CallingConvention.td @@ -278,10 +278,8 @@ def CSR_AArch64_AAPCS : CalleeSavedRegs<(add LR, FP, X19, X20, X21, X22, // case) def CSR_AArch64_AAPCS_ThisReturn : CalleeSavedRegs<(add CSR_AArch64_AAPCS, X0)>; -def CSR_AArch64_AAPCS_SwiftError : CalleeSavedRegs<(add LR, FP, X20, X21, X22, - X23, X24, X25, X26, X27, X28, - D8, D9, D10, D11, - D12, D13, D14, D15)>; +def CSR_AArch64_AAPCS_SwiftError + : CalleeSavedRegs<(sub CSR_AArch64_AAPCS, X19)>; // The function used by Darwin to obtain the address of a thread-local variable // guarantees more than a normal AAPCS function. x16 and x17 are used on the diff --git a/llvm/lib/Target/AArch64/AArch64FastISel.cpp b/llvm/lib/Target/AArch64/AArch64FastISel.cpp index c04878ba2e182..2015d12e79438 100644 --- a/llvm/lib/Target/AArch64/AArch64FastISel.cpp +++ b/llvm/lib/Target/AArch64/AArch64FastISel.cpp @@ -1901,14 +1901,18 @@ bool AArch64FastISel::selectLoad(const Instruction *I) { return false; const Value *SV = I->getOperand(0); - if (const Argument *Arg = dyn_cast(SV)) { - if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) - return false; - } + if (TLI.supportSwiftError()) { + // Swifterror values can come from either a function parameter with + // swifterror attribute or an alloca with swifterror attribute. + if (const Argument *Arg = dyn_cast(SV)) { + if (Arg->hasSwiftErrorAttr()) + return false; + } - if (const AllocaInst *Alloca = dyn_cast(SV)) { - if (Alloca->isSwiftError() && TLI.supportSwiftError()) - return false; + if (const AllocaInst *Alloca = dyn_cast(SV)) { + if (Alloca->isSwiftError()) + return false; + } } // See if we can handle this address. @@ -2076,14 +2080,18 @@ bool AArch64FastISel::selectStore(const Instruction *I) { return false; const Value *PtrV = I->getOperand(1); - if (const Argument *Arg = dyn_cast(PtrV)) { - if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) - return false; - } + if (TLI.supportSwiftError()) { + // Swifterror values can come from either a function parameter with + // swifterror attribute or an alloca with swifterror attribute. + if (const Argument *Arg = dyn_cast(PtrV)) { + if (Arg->hasSwiftErrorAttr()) + return false; + } - if (const AllocaInst *Alloca = dyn_cast(PtrV)) { - if (Alloca->isSwiftError() && TLI.supportSwiftError()) - return false; + if (const AllocaInst *Alloca = dyn_cast(PtrV)) { + if (Alloca->isSwiftError()) + return false; + } } // Get the value to be stored into a register. Use the zero register directly @@ -3667,8 +3675,8 @@ bool AArch64FastISel::selectRet(const Instruction *I) { if (F.isVarArg()) return false; - if (F.getAttributes().hasAttrSomewhere(Attribute::SwiftError) && - TLI.supportSwiftError()) + if (TLI.supportSwiftError() && + F.getAttributes().hasAttrSomewhere(Attribute::SwiftError)) return false; if (TLI.supportSplitCSR(FuncInfo.MF)) diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp index ceeba0a6283e0..047cd577e008b 100644 --- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp +++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp @@ -710,7 +710,8 @@ static bool produceCompactUnwindFrame(MachineFunction &MF) { const AArch64Subtarget &Subtarget = MF.getSubtarget(); AttributeSet Attrs = MF.getFunction()->getAttributes(); return Subtarget.isTargetMachO() && - !Attrs.hasAttrSomewhere(Attribute::SwiftError); + !(Subtarget.getTargetLowering()->supportSwiftError() && + Attrs.hasAttrSomewhere(Attribute::SwiftError)); } diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.h b/llvm/lib/Target/AArch64/AArch64ISelLowering.h index 2bd48e1b2271d..977f7237bda2f 100644 --- a/llvm/lib/Target/AArch64/AArch64ISelLowering.h +++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.h @@ -400,6 +400,10 @@ class AArch64TargetLowering : public TargetLowering { MachineBasicBlock *Entry, const SmallVectorImpl &Exits) const override; + bool supportSwiftError() const override { + return true; + } + private: bool isExtFreeImpl(const Instruction *Ext) const override; @@ -411,10 +415,6 @@ class AArch64TargetLowering : public TargetLowering { void addDRTypeForNEON(MVT VT); void addQRTypeForNEON(MVT VT); - bool supportSwiftError() const override { - return true; - } - SDValue LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv, bool isVarArg, const SmallVectorImpl &Ins, SDLoc DL, diff --git a/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp b/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp index 8d4c55d482bf4..e41f276c81907 100644 --- a/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp +++ b/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp @@ -51,8 +51,10 @@ AArch64RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const { return MF->getInfo()->isSplitCSR() ? CSR_AArch64_CXX_TLS_Darwin_PE_SaveList : CSR_AArch64_CXX_TLS_Darwin_SaveList; - if (MF->getFunction()->getAttributes().hasAttrSomewhere( - Attribute::SwiftError)) + if (MF->getSubtarget().getTargetLowering() + ->supportSwiftError() && + MF->getFunction()->getAttributes().hasAttrSomewhere( + Attribute::SwiftError)) return CSR_AArch64_AAPCS_SwiftError_SaveList; if (MF->getFunction()->getCallingConv() == CallingConv::PreserveMost) return CSR_AArch64_RT_MostRegs_SaveList; @@ -60,26 +62,6 @@ AArch64RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const { return CSR_AArch64_AAPCS_SaveList; } -const MCPhysReg * -AArch64RegisterInfo::getCalleeSavedRegsForLayout( - const MachineFunction *MF) const { - assert(MF && "Invalid MachineFunction pointer."); - if (MF->getFunction()->getCallingConv() == CallingConv::GHC) - // GHC set of callee saved regs is empty as all those regs are - // used for passing STG regs around - return CSR_AArch64_NoRegs_SaveList; - if (MF->getFunction()->getCallingConv() == CallingConv::AnyReg) - return CSR_AArch64_AllRegs_SaveList; - if (MF->getFunction()->getCallingConv() == CallingConv::CXX_FAST_TLS) - return MF->getInfo()->isSplitCSR() ? - CSR_AArch64_CXX_TLS_Darwin_PE_SaveList : - CSR_AArch64_CXX_TLS_Darwin_SaveList; - if (MF->getFunction()->getCallingConv() == CallingConv::PreserveMost) - return CSR_AArch64_RT_MostRegs_SaveList; - else - return CSR_AArch64_AAPCS_SaveList; -} - const MCPhysReg *AArch64RegisterInfo::getCalleeSavedRegsViaCopy( const MachineFunction *MF) const { assert(MF && "Invalid MachineFunction pointer."); @@ -99,7 +81,9 @@ AArch64RegisterInfo::getCallPreservedMask(const MachineFunction &MF, return CSR_AArch64_AllRegs_RegMask; if (CC == CallingConv::CXX_FAST_TLS) return CSR_AArch64_CXX_TLS_Darwin_RegMask; - if (MF.getFunction()->getAttributes().hasAttrSomewhere(Attribute::SwiftError)) + if (MF.getSubtarget().getTargetLowering() + ->supportSwiftError() && + MF.getFunction()->getAttributes().hasAttrSomewhere(Attribute::SwiftError)) return CSR_AArch64_AAPCS_SwiftError_RegMask; if (CC == CallingConv::PreserveMost) return CSR_AArch64_RT_MostRegs_RegMask; diff --git a/llvm/lib/Target/AArch64/AArch64RegisterInfo.h b/llvm/lib/Target/AArch64/AArch64RegisterInfo.h index 41562b5194996..f33f788fd437e 100644 --- a/llvm/lib/Target/AArch64/AArch64RegisterInfo.h +++ b/llvm/lib/Target/AArch64/AArch64RegisterInfo.h @@ -62,11 +62,6 @@ struct AArch64RegisterInfo : public AArch64GenRegisterInfo { const uint32_t *getThisReturnPreservedMask(const MachineFunction &MF, CallingConv::ID) const; - /// Return callee-saved registers for stack layout purpose. When we use - /// SwiftError CSR, we still need to use the standard CSR for layout purpose, - /// since compact unwinding expects the layout according to standard CSR. - const MCPhysReg *getCalleeSavedRegsForLayout(const MachineFunction *MF) const; - BitVector getReservedRegs(const MachineFunction &MF) const override; const TargetRegisterClass * getPointerRegClass(const MachineFunction &MF, diff --git a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp index b3f8187048313..6db2aca99ba2f 100644 --- a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp +++ b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp @@ -87,7 +87,7 @@ ARMBaseRegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const { } } - if (STI.isTargetDarwin() && + if (STI.isTargetDarwin() && STI.getTargetLowering()->supportSwiftError() && F->getAttributes().hasAttrSomewhere(Attribute::SwiftError)) return CSR_iOS_SwiftError_SaveList; @@ -115,7 +115,7 @@ ARMBaseRegisterInfo::getCallPreservedMask(const MachineFunction &MF, // This is academic becase all GHC calls are (supposed to be) tail calls return CSR_NoRegs_RegMask; - if (STI.isTargetDarwin() && + if (STI.isTargetDarwin() && STI.getTargetLowering()->supportSwiftError() && MF.getFunction()->getAttributes().hasAttrSomewhere(Attribute::SwiftError)) return CSR_iOS_SwiftError_RegMask; diff --git a/llvm/lib/Target/ARM/ARMCallingConv.td b/llvm/lib/Target/ARM/ARMCallingConv.td index 21ec04d0911d7..922b800151101 100644 --- a/llvm/lib/Target/ARM/ARMCallingConv.td +++ b/llvm/lib/Target/ARM/ARMCallingConv.td @@ -48,7 +48,7 @@ def RetCC_ARM_APCS : CallingConv<[ CCIfType<[i1, i8, i16], CCPromoteToType>, CCIfType<[f32], CCBitConvertToType>, - // An SwiftError is returned in R6. + // A SwiftError is returned in R6. CCIfSwiftError>>, // Handle all vector types as either f64 or v2f64. @@ -176,7 +176,7 @@ def RetCC_ARM_AAPCS : CallingConv<[ CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, - // An SwiftError is returned in R6. + // A SwiftError is returned in R6. CCIfSwiftError>>, CCIfType<[f64, v2f64], CCCustom<"RetCC_ARM_AAPCS_Custom_f64">>, @@ -218,7 +218,7 @@ def RetCC_ARM_AAPCS_VFP : CallingConv<[ CCIfType<[v1i64, v2i32, v4i16, v8i8, v2f32], CCBitConvertToType>, CCIfType<[v2i64, v4i32, v8i16, v16i8, v4f32], CCBitConvertToType>, - // An SwiftError is returned in R6. + // A SwiftError is returned in R6. CCIfSwiftError>>, CCIfType<[v2f64], CCAssignToReg<[Q0, Q1, Q2, Q3]>>, diff --git a/llvm/lib/Target/ARM/ARMFastISel.cpp b/llvm/lib/Target/ARM/ARMFastISel.cpp index 8bbad22ee3a91..28d72d8c6ca61 100644 --- a/llvm/lib/Target/ARM/ARMFastISel.cpp +++ b/llvm/lib/Target/ARM/ARMFastISel.cpp @@ -1063,14 +1063,18 @@ bool ARMFastISel::SelectLoad(const Instruction *I) { return false; const Value *SV = I->getOperand(0); - if (const Argument *Arg = dyn_cast(SV)) { - if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) - return false; - } + if (TLI.supportSwiftError()) { + // Swifterror values can come from either a function parameter with + // swifterror attribute or an alloca with swifterror attribute. + if (const Argument *Arg = dyn_cast(SV)) { + if (Arg->hasSwiftErrorAttr()) + return false; + } - if (const AllocaInst *Alloca = dyn_cast(SV)) { - if (Alloca->isSwiftError() && TLI.supportSwiftError()) - return false; + if (const AllocaInst *Alloca = dyn_cast(SV)) { + if (Alloca->isSwiftError()) + return false; + } } // Verify we have a legal type before going any further. @@ -1189,14 +1193,18 @@ bool ARMFastISel::SelectStore(const Instruction *I) { return false; const Value *PtrV = I->getOperand(1); - if (const Argument *Arg = dyn_cast(PtrV)) { - if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) - return false; - } + if (TLI.supportSwiftError()) { + // Swifterror values can come from either a function parameter with + // swifterror attribute or an alloca with swifterror attribute. + if (const Argument *Arg = dyn_cast(PtrV)) { + if (Arg->hasSwiftErrorAttr()) + return false; + } - if (const AllocaInst *Alloca = dyn_cast(PtrV)) { - if (Alloca->isSwiftError() && TLI.supportSwiftError()) - return false; + if (const AllocaInst *Alloca = dyn_cast(PtrV)) { + if (Alloca->isSwiftError()) + return false; + } } // Verify we have a legal type before going any further. @@ -2107,8 +2115,8 @@ bool ARMFastISel::SelectRet(const Instruction *I) { if (!FuncInfo.CanLowerReturn) return false; - if (F.getAttributes().hasAttrSomewhere(Attribute::SwiftError) && - TLI.supportSwiftError()) + if (TLI.supportSwiftError() && + F.getAttributes().hasAttrSomewhere(Attribute::SwiftError)) return false; if (TLI.supportSplitCSR(FuncInfo.MF)) diff --git a/llvm/lib/Target/ARM/ARMISelLowering.h b/llvm/lib/Target/ARM/ARMISelLowering.h index be094d74637a2..f4f86b4cbb1a5 100644 --- a/llvm/lib/Target/ARM/ARMISelLowering.h +++ b/llvm/lib/Target/ARM/ARMISelLowering.h @@ -470,6 +470,10 @@ namespace llvm { bool isCheapToSpeculateCttz() const override; bool isCheapToSpeculateCtlz() const override; + bool supportSwiftError() const override { + return true; + } + protected: std::pair findRepresentativeClass(const TargetRegisterInfo *TRI, @@ -587,10 +591,6 @@ namespace llvm { SmallVectorImpl &InVals, bool isThisReturn, SDValue ThisVal) const; - bool supportSwiftError() const override { - return true; - } - bool supportSplitCSR(MachineFunction *MF) const override { return MF->getFunction()->getCallingConv() == CallingConv::CXX_FAST_TLS && MF->getFunction()->hasFnAttribute(Attribute::NoUnwind); diff --git a/llvm/lib/Target/X86/X86CallingConv.td b/llvm/lib/Target/X86/X86CallingConv.td index a7a3bc25abb53..4d99af68a041d 100644 --- a/llvm/lib/Target/X86/X86CallingConv.td +++ b/llvm/lib/Target/X86/X86CallingConv.td @@ -300,7 +300,7 @@ def CC_X86_64_C : CallingConv<[ // A SwiftSelf is passed in R10. CCIfSwiftSelf>>, - // An SwiftError is passed in R12. + // A SwiftError is passed in R12. CCIfSwiftError>>, // The first 6 integer arguments are passed in integer registers. @@ -851,7 +851,7 @@ def CSR_NoRegs : CalleeSavedRegs<(add)>; def CSR_32 : CalleeSavedRegs<(add ESI, EDI, EBX, EBP)>; def CSR_64 : CalleeSavedRegs<(add RBX, R12, R13, R14, R15, RBP)>; -def CSR_64_SwiftError : CalleeSavedRegs<(add RBX, R13, R14, R15, RBP)>; +def CSR_64_SwiftError : CalleeSavedRegs<(sub CSR_64, R12)>; def CSR_32EHRet : CalleeSavedRegs<(add EAX, EDX, CSR_32)>; def CSR_64EHRet : CalleeSavedRegs<(add RAX, RDX, CSR_64)>; diff --git a/llvm/lib/Target/X86/X86FastISel.cpp b/llvm/lib/Target/X86/X86FastISel.cpp index 1e3812ce15cda..8aab37666b00b 100644 --- a/llvm/lib/Target/X86/X86FastISel.cpp +++ b/llvm/lib/Target/X86/X86FastISel.cpp @@ -973,14 +973,18 @@ bool X86FastISel::X86SelectStore(const Instruction *I) { return false; const Value *PtrV = I->getOperand(1); - if (const Argument *Arg = dyn_cast(PtrV)) { - if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) - return false; - } + if (TLI.supportSwiftError()) { + // Swifterror values can come from either a function parameter with + // swifterror attribute or an alloca with swifterror attribute. + if (const Argument *Arg = dyn_cast(PtrV)) { + if (Arg->hasSwiftErrorAttr()) + return false; + } - if (const AllocaInst *Alloca = dyn_cast(PtrV)) { - if (Alloca->isSwiftError() && TLI.supportSwiftError()) - return false; + if (const AllocaInst *Alloca = dyn_cast(PtrV)) { + if (Alloca->isSwiftError()) + return false; + } } const Value *Val = S->getValueOperand(); @@ -1013,8 +1017,8 @@ bool X86FastISel::X86SelectRet(const Instruction *I) { if (!FuncInfo.CanLowerReturn) return false; - if (F.getAttributes().hasAttrSomewhere(Attribute::SwiftError) && - TLI.supportSwiftError()) + if (TLI.supportSwiftError() && + F.getAttributes().hasAttrSomewhere(Attribute::SwiftError)) return false; if (TLI.supportSplitCSR(FuncInfo.MF)) @@ -1149,14 +1153,18 @@ bool X86FastISel::X86SelectLoad(const Instruction *I) { return false; const Value *SV = I->getOperand(0); - if (const Argument *Arg = dyn_cast(SV)) { - if (Arg->hasSwiftErrorAttr() && TLI.supportSwiftError()) - return false; - } + if (TLI.supportSwiftError()) { + // Swifterror values can come from either a function parameter with + // swifterror attribute or an alloca with swifterror attribute. + if (const Argument *Arg = dyn_cast(SV)) { + if (Arg->hasSwiftErrorAttr()) + return false; + } - if (const AllocaInst *Alloca = dyn_cast(SV)) { - if (Alloca->isSwiftError() && TLI.supportSwiftError()) - return false; + if (const AllocaInst *Alloca = dyn_cast(SV)) { + if (Alloca->isSwiftError()) + return false; + } } MVT VT; diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp index b050920bbd891..b1b8b91a1c60c 100644 --- a/llvm/lib/Target/X86/X86ISelLowering.cpp +++ b/llvm/lib/Target/X86/X86ISelLowering.cpp @@ -2227,6 +2227,25 @@ X86TargetLowering::LowerReturn(SDValue Chain, // false, then an sret argument may be implicitly inserted in the SelDAG. In // either case FuncInfo->setSRetReturnReg() will have been called. if (unsigned SRetReg = FuncInfo->getSRetReturnReg()) { + // When we have both sret and another return value, we should use the + // original Chain stored in RetOps[0], instead of the current Chain updated + // in the above loop. If we only have sret, RetOps[0] equals to Chain. + + // For the case of sret and another return value, we have + // Chain_0 at the function entry + // Chain_1 = getCopyToReg(Chain_0) in the above loop + // If we use Chain_1 in getCopyFromReg, we will have + // Val = getCopyFromReg(Chain_1) + // Chain_2 = getCopyToReg(Chain_1, Val) from below + + // getCopyToReg(Chain_0) will be glued together with + // getCopyToReg(Chain_1, Val) into Unit A, getCopyFromReg(Chain_1) will be + // in Unit B, and we will have cyclic dependency between Unit A and Unit B: + // Data dependency from Unit B to Unit A due to usage of Val in + // getCopyToReg(Chain_1, Val) + // Chain dependency from Unit A to Unit B + + // So here, we use RetOps[0] (i.e Chain_0) for getCopyFromReg. SDValue Val = DAG.getCopyFromReg(RetOps[0], dl, SRetReg, getPointerTy(MF.getDataLayout())); diff --git a/llvm/lib/Target/X86/X86ISelLowering.h b/llvm/lib/Target/X86/X86ISelLowering.h index a8bad8e955be4..c73c47c124f2b 100644 --- a/llvm/lib/Target/X86/X86ISelLowering.h +++ b/llvm/lib/Target/X86/X86ISelLowering.h @@ -984,6 +984,10 @@ namespace llvm { bool isIntDivCheap(EVT VT, AttributeSet Attr) const override; + bool supportSwiftError() const override { + return true; + } + protected: std::pair findRepresentativeClass(const TargetRegisterInfo *TRI, @@ -1097,10 +1101,6 @@ namespace llvm { SDValue LowerGC_TRANSITION_START(SDValue Op, SelectionDAG &DAG) const; SDValue LowerGC_TRANSITION_END(SDValue Op, SelectionDAG &DAG) const; - bool supportSwiftError() const override { - return true; - } - SDValue LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv, bool isVarArg, diff --git a/llvm/lib/Target/X86/X86RegisterInfo.cpp b/llvm/lib/Target/X86/X86RegisterInfo.cpp index 2989e761c044d..1ea9533eaf95e 100644 --- a/llvm/lib/Target/X86/X86RegisterInfo.cpp +++ b/llvm/lib/Target/X86/X86RegisterInfo.cpp @@ -299,8 +299,9 @@ X86RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const { return CSR_Win64_SaveList; if (CallsEHReturn) return CSR_64EHRet_SaveList; - if (MF->getFunction()->getAttributes().hasAttrSomewhere( - Attribute::SwiftError)) + if (Subtarget.getTargetLowering()->supportSwiftError() && + MF->getFunction()->getAttributes().hasAttrSomewhere( + Attribute::SwiftError)) return CSR_64_SwiftError_SaveList; return CSR_64_SaveList; } @@ -388,8 +389,9 @@ X86RegisterInfo::getCallPreservedMask(const MachineFunction &MF, if (Is64Bit) { if (IsWin64) return CSR_Win64_RegMask; - if (MF.getFunction()->getAttributes().hasAttrSomewhere( - Attribute::SwiftError)) + if (Subtarget.getTargetLowering()->supportSwiftError() && + MF.getFunction()->getAttributes().hasAttrSomewhere( + Attribute::SwiftError)) return CSR_64_SwiftError_RegMask; return CSR_64_RegMask; } diff --git a/llvm/test/CodeGen/AArch64/swifterror.ll b/llvm/test/CodeGen/AArch64/swifterror.ll index ec9d256fb82ee..4c32513729529 100644 --- a/llvm/test/CodeGen/AArch64/swifterror.ll +++ b/llvm/test/CodeGen/AArch64/swifterror.ll @@ -5,6 +5,8 @@ declare i8* @malloc(i64) declare void @free(i8*) %swift_error = type {i64, i8} +; This tests the basic usage of a swifterror parameter. "foo" is the function +; that takes a swifterror parameter and "caller" is the caller of "foo". define float @foo(%swift_error** swifterror %error_ptr_ref) { ; CHECK-APPLE-LABEL: foo: ; CHECK-APPLE: orr w0, wzr, #0x10 @@ -26,11 +28,12 @@ entry: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp ret float 1.0 } +; "caller" calls "foo" that takes a swifterror parameter. define float @caller(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller: ; CHECK-APPLE: mov [[ID:x[0-9]+]], x0 @@ -54,7 +57,7 @@ entry: %call = call float @foo(%swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 @@ -62,10 +65,11 @@ cont: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } +; "caller2" is the caller of "foo", it calls "foo" inside a loop. define float @caller2(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller2: ; CHECK-APPLE: mov [[ID:x[0-9]+]], x0 @@ -94,7 +98,7 @@ bb_loop: %call = call float @foo(%swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %cmp = fcmp ogt float %call, 1.000000e+00 @@ -105,10 +109,12 @@ bb_end: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } +; "foo_if" is a function that takes a swifterror parameter, it sets swifterror +; under a certain condition. define float @foo_if(%swift_error** swifterror %error_ptr_ref, i32 %cc) { ; CHECK-APPLE-LABEL: foo_if: ; CHECK-APPLE: cbz w0 @@ -142,14 +148,16 @@ gen_error: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp ret float 1.0 normal: ret float 0.0 } +; "foo_loop" is a function that takes a swifterror parameter, it sets swifterror +; under a certain condition inside a loop. define float @foo_loop(%swift_error** swifterror %error_ptr_ref, i32 %cc, float %cc2) { ; CHECK-APPLE-LABEL: foo_loop: ; CHECK-APPLE: mov x0, x19 @@ -188,8 +196,8 @@ gen_error: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp br label %bb_cont bb_cont: @@ -201,6 +209,8 @@ bb_end: %struct.S = type { i32, i32, i32, i32, i32, i32 } +; "foo_sret" is a function that takes a swifterror parameter, it also has a sret +; parameter. define void @foo_sret(%struct.S* sret %agg.result, i32 %val1, %swift_error** swifterror %error_ptr_ref) { ; CHECK-APPLE-LABEL: foo_sret: ; CHECK-APPLE: mov [[SRET:x[0-9]+]], x8 @@ -230,13 +240,14 @@ entry: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp %v2 = getelementptr inbounds %struct.S, %struct.S* %agg.result, i32 0, i32 1 store i32 %val1, i32* %v2 ret void } +; "caller3" calls "foo_sret" that takes a swifterror parameter. define float @caller3(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller3: ; CHECK-APPLE: mov [[ID:x[0-9]+]], x0 @@ -269,7 +280,7 @@ entry: call void @foo_sret(%struct.S* sret %s, i32 1, %swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 @@ -277,22 +288,24 @@ cont: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } +; "foo_vararg" is a function that takes a swifterror parameter, it also has +; variable number of arguments. declare void @llvm.va_start(i8*) nounwind define float @foo_vararg(%swift_error** swifterror %error_ptr_ref, ...) { ; CHECK-APPLE-LABEL: foo_vararg: ; CHECK-APPLE: orr w0, wzr, #0x10 ; CHECK-APPLE: malloc ; CHECK-APPLE: orr [[ID:w[0-9]+]], wzr, #0x1 -; CHECK-APPLE: add [[ARGS:x[0-9]+]], [[TMP:x[0-9]+]], #16 +; CHECK-FIXMEAPPLE: add [[ARGS:x[0-9]+]], [[TMP:x[0-9]+]], #16 ; CHECK-APPLE: strb [[ID]], [x0, #8] ; First vararg -; CHECK-APPLE-DAG: orr {{x[0-9]+}}, [[ARGS]], #0x8 -; CHECK-APPLE-DAG: ldr {{w[0-9]+}}, [{{.*}}[[TMP]], #16] +; CHECK-FIXMEAPPLE-DAG: orr {{x[0-9]+}}, [[ARGS]], #0x8 +; CHECK-FIXMEAPPLE-DAG: ldr {{w[0-9]+}}, [{{.*}}[[TMP]], #16] ; CHECK-APPLE: add {{x[0-9]+}}, {{x[0-9]+}}, #8 ; Second vararg ; CHECK-APPLE: ldr {{w[0-9]+}}, [{{x[0-9]+}}] @@ -306,8 +319,8 @@ entry: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp %args = alloca i8*, align 8 %a10 = alloca i32, align 4 @@ -325,6 +338,7 @@ entry: ret float 1.0 } +; "caller4" calls "foo_vararg" that takes a swifterror parameter. define float @caller4(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller4: @@ -357,7 +371,7 @@ entry: %call = call float (%swift_error**, ...) @foo_vararg(%swift_error** swifterror %error_ptr_ref, i32 %v10, i32 %v11, i32 %v12) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: @@ -366,6 +380,6 @@ cont: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } diff --git a/llvm/test/CodeGen/ARM/swifterror.ll b/llvm/test/CodeGen/ARM/swifterror.ll index e48bf82e5731a..17bd7059f6d40 100644 --- a/llvm/test/CodeGen/ARM/swifterror.ll +++ b/llvm/test/CodeGen/ARM/swifterror.ll @@ -3,8 +3,11 @@ declare i8* @malloc(i64) declare void @free(i8*) -%swift_error = type {i64, i8} +%swift_error = type { i64, i8 } +%struct.S = type { i32, i32, i32, i32, i32, i32 } +; This tests the basic usage of a swifterror parameter. "foo" is the function +; that takes a swifterror parameter and "caller" is the caller of "foo". define float @foo(%swift_error** swifterror %error_ptr_ref) { ; CHECK-APPLE-LABEL: foo: ; CHECK-APPLE: mov r0, #16 @@ -24,11 +27,12 @@ entry: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp ret float 1.0 } +; "caller" calls "foo" that takes a swifterror parameter. define float @caller(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller: ; CHECK-APPLE-DAG: mov [[ID:r[0-9]+]], r0 @@ -60,7 +64,7 @@ entry: %call = call float @foo(%swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 @@ -68,10 +72,11 @@ cont: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } +; "caller2" is the caller of "foo", it calls "foo" inside a loop. define float @caller2(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller2: ; CHECK-APPLE-DAG: mov [[ID:r[0-9]+]], r0 @@ -107,7 +112,7 @@ bb_loop: %call = call float @foo(%swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %cmp = fcmp ogt float %call, 1.000000e+00 @@ -118,10 +123,12 @@ bb_end: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } +; "foo_if" is a function that takes a swifterror parameter, it sets swifterror +; under a certain condition. define float @foo_if(%swift_error** swifterror %error_ptr_ref, i32 %cc) { ; CHECK-APPLE-LABEL: foo_if: ; CHECK-APPLE: cmp r0, #0 @@ -153,14 +160,16 @@ gen_error: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp ret float 1.0 normal: ret float 0.0 } +; "foo_loop" is a function that takes a swifterror parameter, it sets swifterror +; under a certain condition inside a loop. define float @foo_loop(%swift_error** swifterror %error_ptr_ref, i32 %cc, float %cc2) { ; CHECK-APPLE-LABEL: foo_loop: ; CHECK-APPLE: mov [[CODE:r[0-9]+]], r0 @@ -201,8 +210,8 @@ gen_error: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp br label %bb_cont bb_cont: @@ -212,8 +221,8 @@ bb_end: ret float 0.0 } -%struct.S = type { i32, i32, i32, i32, i32, i32 } - +; "foo_sret" is a function that takes a swifterror parameter, it also has a sret +; parameter. define void @foo_sret(%struct.S* sret %agg.result, i32 %val1, %swift_error** swifterror %error_ptr_ref) { ; CHECK-APPLE-LABEL: foo_sret: ; CHECK-APPLE: mov [[SRET:r[0-9]+]], r0 @@ -241,13 +250,14 @@ entry: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp %v2 = getelementptr inbounds %struct.S, %struct.S* %agg.result, i32 0, i32 1 store i32 %val1, i32* %v2 ret void } +; "caller3" calls "foo_sret" that takes a swifterror parameter. define float @caller3(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller3: ; CHECK-APPLE: mov [[ID:r[0-9]+]], r0 @@ -281,7 +291,7 @@ entry: call void @foo_sret(%struct.S* sret %s, i32 1, %swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 @@ -289,10 +299,12 @@ cont: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } +; "foo_vararg" is a function that takes a swifterror parameter, it also has +; variable number of arguments. declare void @llvm.va_start(i8*) nounwind define float @foo_vararg(%swift_error** swifterror %error_ptr_ref, ...) { ; CHECK-APPLE-LABEL: foo_vararg: @@ -307,8 +319,8 @@ entry: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp %args = alloca i8*, align 8 %a10 = alloca i32, align 4 @@ -326,6 +338,7 @@ entry: ret float 1.0 } +; "caller4" calls "foo_vararg" that takes a swifterror parameter. define float @caller4(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller4: ; CHECK-APPLE: mov [[ID:r[0-9]+]], r0 @@ -354,7 +367,7 @@ entry: %call = call float (%swift_error**, ...) @foo_vararg(%swift_error** swifterror %error_ptr_ref, i32 %v10, i32 %v11, i32 %v12) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: @@ -363,6 +376,6 @@ cont: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } diff --git a/llvm/test/CodeGen/X86/swifterror.ll b/llvm/test/CodeGen/X86/swifterror.ll index 484a5f29a3b3e..d8db36b09c25b 100644 --- a/llvm/test/CodeGen/X86/swifterror.ll +++ b/llvm/test/CodeGen/X86/swifterror.ll @@ -5,6 +5,8 @@ declare i8* @malloc(i64) declare void @free(i8*) %swift_error = type {i64, i8} +; This tests the basic usage of a swifterror parameter. "foo" is the function +; that takes a swifterror parameter and "caller" is the caller of "foo". define float @foo(%swift_error** swifterror %error_ptr_ref) { ; CHECK-APPLE-LABEL: foo: ; CHECK-APPLE: movl $16, %edi @@ -21,11 +23,12 @@ entry: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp ret float 1.0 } +; "caller" calls "foo" that takes a swifterror parameter. define float @caller(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller: ; CHECK-APPLE: xorl %r12d, %r12d @@ -48,7 +51,7 @@ entry: %call = call float @foo(%swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 @@ -56,10 +59,11 @@ cont: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } +; "caller2" is the caller of "foo", it calls "foo" inside a loop. define float @caller2(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller2: ; CHECK-APPLE: xorl %r12d, %r12d @@ -88,7 +92,7 @@ bb_loop: %call = call float @foo(%swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %cmp = fcmp ogt float %call, 1.000000e+00 @@ -99,10 +103,12 @@ bb_end: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) ret float 1.0 } +; "foo_if" is a function that takes a swifterror parameter, it sets swifterror +; under a certain condition. define float @foo_if(%swift_error** swifterror %error_ptr_ref, i32 %cc) { ; CHECK-APPLE-LABEL: foo_if: ; CHECK-APPLE: testl %edi, %edi @@ -136,14 +142,16 @@ gen_error: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp ret float 1.0 normal: ret float 0.0 } +; "foo_loop" is a function that takes a swifterror parameter, it sets swifterror +; under a certain condition inside a loop. define float @foo_loop(%swift_error** swifterror %error_ptr_ref, i32 %cc, float %cc2) { ; CHECK-APPLE-LABEL: foo_loop: ; CHECK-APPLE: movq %r12, %rax @@ -181,8 +189,8 @@ gen_error: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp br label %bb_cont bb_cont: @@ -194,6 +202,8 @@ bb_end: %struct.S = type { i32, i32, i32, i32, i32, i32 } +; "foo_sret" is a function that takes a swifterror parameter, it also has a sret +; parameter. define void @foo_sret(%struct.S* sret %agg.result, i32 %val1, %swift_error** swifterror %error_ptr_ref) { ; CHECK-APPLE-LABEL: foo_sret: ; CHECK-APPLE: movq %rdi, %{{.*}} @@ -221,13 +231,14 @@ entry: %call = call i8* @malloc(i64 16) %call.0 = bitcast i8* %call to %swift_error* store %swift_error* %call.0, %swift_error** %error_ptr_ref - %0 = getelementptr inbounds i8, i8* %call, i64 8 - store i8 1, i8* %0 + %tmp = getelementptr inbounds i8, i8* %call, i64 8 + store i8 1, i8* %tmp %v2 = getelementptr inbounds %struct.S, %struct.S* %agg.result, i32 0, i32 1 store i32 %val1, i32* %v2 ret void } +; "caller3" calls "foo_sret" that takes a swifterror parameter. define float @caller3(i8* %error_ref) { ; CHECK-APPLE-LABEL: caller3: ; CHECK-APPLE: movl $1, %esi @@ -263,7 +274,7 @@ entry: call void @foo_sret(%struct.S* sret %s, i32 1, %swift_error** swifterror %error_ptr_ref) %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null - %0 = bitcast %swift_error* %error_from_foo to i8* + %tmp = bitcast %swift_error* %error_from_foo to i8* br i1 %had_error_from_foo, label %handler, label %cont cont: %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 @@ -271,6 +282,78 @@ cont: store i8 %t, i8* %error_ref br label %handler handler: - call void @free(i8* %0) + call void @free(i8* %tmp) + ret float 1.0 +} + +; This is a caller with multiple swifterror values, it calls "foo" twice, each +; time with a different swifterror value, from "alloca swifterror". +define float @caller_with_multiple_swifterror_values(i8* %error_ref, i8* %error_ref2) { +; CHECK-APPLE-LABEL: caller_with_multiple_swifterror_values: + +; The first swifterror value: +; CHECK-APPLE: xorl %r12d, %r12d +; CHECK-APPLE: callq {{.*}}foo +; CHECK-APPLE: testq %r12, %r12 +; CHECK-APPLE: jne +; Access part of the error object and save it to error_ref +; CHECK-APPLE: movb 8(%r12) +; CHECK-APPLE: movq %r12, %rdi +; CHECK_APPLE: callq {{.*}}free + +; The second swifterror value: +; CHECK-APPLE: xorl %r12d, %r12d +; CHECK-APPLE: callq {{.*}}foo +; CHECK-APPLE: testq %r12, %r12 +; CHECK-APPLE: jne +; Access part of the error object and save it to error_ref +; CHECK-APPLE: movb 8(%r12) +; CHECK-APPLE: movq %r12, %rdi +; CHECK_APPLE: callq {{.*}}free + +; CHECK-O0-LABEL: caller_with_multiple_swifterror_values: + +; The first swifterror value: +; CHECK-O0: xorl +; CHECK-O0: movl %{{.*}}, %r12d +; CHECK-O0: callq {{.*}}foo +; CHECK-O0: jne + +; The second swifterror value: +; CHECK-O0: xorl +; CHECK-O0: movl %{{.*}}, %r12d +; CHECK-O0: callq {{.*}}foo +; CHECK-O0: jne +entry: + %error_ptr_ref = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref + %call = call float @foo(%swift_error** swifterror %error_ptr_ref) + %error_from_foo = load %swift_error*, %swift_error** %error_ptr_ref + %had_error_from_foo = icmp ne %swift_error* %error_from_foo, null + %tmp = bitcast %swift_error* %error_from_foo to i8* + br i1 %had_error_from_foo, label %handler, label %cont +cont: + %v1 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo, i64 0, i32 1 + %t = load i8, i8* %v1 + store i8 %t, i8* %error_ref + br label %handler +handler: + call void @free(i8* %tmp) + + %error_ptr_ref2 = alloca swifterror %swift_error* + store %swift_error* null, %swift_error** %error_ptr_ref2 + %call2 = call float @foo(%swift_error** swifterror %error_ptr_ref2) + %error_from_foo2 = load %swift_error*, %swift_error** %error_ptr_ref2 + %had_error_from_foo2 = icmp ne %swift_error* %error_from_foo2, null + %bitcast2 = bitcast %swift_error* %error_from_foo2 to i8* + br i1 %had_error_from_foo2, label %handler2, label %cont2 +cont2: + %v2 = getelementptr inbounds %swift_error, %swift_error* %error_from_foo2, i64 0, i32 1 + %t2 = load i8, i8* %v2 + store i8 %t2, i8* %error_ref2 + br label %handler2 +handler2: + call void @free(i8* %bitcast2) + ret float 1.0 } From 638b0ac101d3e4cefa5f0cc52ffdfe0acc29fea1 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Tue, 12 Apr 2016 14:26:53 -0700 Subject: [PATCH 058/585] Remove some leftover changes related to the Swift calling convention. Support for the Swift calling convention has now been added to LLVM trunk. There were a few changes in the upstream-with-swift branch on github that were related and are no longer needed. This change removes them. apple-llvm-split-commit: 6da1b2710fd3befb75bce79aefd4fc3783dcf802 apple-llvm-split-dir: llvm/ --- llvm/include/llvm/Target/TargetFrameLowering.h | 5 +---- llvm/lib/CodeGen/PrologEpilogInserter.cpp | 3 +-- llvm/lib/IR/Attributes.cpp | 8 ++++---- llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp | 3 +-- llvm/lib/Target/Hexagon/HexagonFrameLowering.h | 3 +-- llvm/lib/Target/X86/X86FrameLowering.cpp | 3 +-- llvm/lib/Target/X86/X86FrameLowering.h | 4 +--- 7 files changed, 10 insertions(+), 19 deletions(-) diff --git a/llvm/include/llvm/Target/TargetFrameLowering.h b/llvm/include/llvm/Target/TargetFrameLowering.h index b48b6979c25c7..9f42adaf814e3 100644 --- a/llvm/include/llvm/Target/TargetFrameLowering.h +++ b/llvm/include/llvm/Target/TargetFrameLowering.h @@ -120,10 +120,7 @@ class TargetFrameLowering { virtual bool assignCalleeSavedSpillSlots(MachineFunction &MF, const TargetRegisterInfo *TRI, - std::vector &CSI, - unsigned &MinCSFrameIndex, - unsigned &MaxCSFrameIndex) const { - + std::vector &CSI) const { return false; } diff --git a/llvm/lib/CodeGen/PrologEpilogInserter.cpp b/llvm/lib/CodeGen/PrologEpilogInserter.cpp index dbaa7f4303d1e..7c3fe33cf7505 100644 --- a/llvm/lib/CodeGen/PrologEpilogInserter.cpp +++ b/llvm/lib/CodeGen/PrologEpilogInserter.cpp @@ -316,8 +316,7 @@ void PEI::assignCalleeSavedSpillSlots(MachineFunction &F, const TargetFrameLowering *TFI = F.getSubtarget().getFrameLowering(); MachineFrameInfo *MFI = F.getFrameInfo(); - if (!TFI->assignCalleeSavedSpillSlots(F, RegInfo, CSI, MinCSFrameIndex, - MaxCSFrameIndex)) { + if (!TFI->assignCalleeSavedSpillSlots(F, RegInfo, CSI)) { // If target doesn't implement this, use generic code. if (CSI.empty()) diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp index 601920ad5df31..0b775337be5f1 100644 --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -512,10 +512,10 @@ uint64_t AttributeImpl::getAttrMask(Attribute::AttrKind Val) { case Attribute::Convergent: return 1ULL << 46; case Attribute::SafeStack: return 1ULL << 47; case Attribute::NoRecurse: return 1ULL << 48; - case Attribute::SwiftSelf: return 1ULL << 49; - case Attribute::SwiftError: return 1ULL << 50; - case Attribute::InaccessibleMemOnly: return 1ULL << 51; - case Attribute::InaccessibleMemOrArgMemOnly: return 1ULL << 52; + case Attribute::InaccessibleMemOnly: return 1ULL << 49; + case Attribute::InaccessibleMemOrArgMemOnly: return 1ULL << 50; + case Attribute::SwiftSelf: return 1ULL << 51; + case Attribute::SwiftError: return 1ULL << 52; case Attribute::Dereferenceable: llvm_unreachable("dereferenceable attribute not supported in raw format"); break; diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp index fb3493536d300..3c08eaabe18b1 100644 --- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp +++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp @@ -1152,8 +1152,7 @@ static void dump_registers(BitVector &Regs, const TargetRegisterInfo &TRI) { bool HexagonFrameLowering::assignCalleeSavedSpillSlots(MachineFunction &MF, - const TargetRegisterInfo *TRI, std::vector &CSI, - unsigned &MinCSFrameIndex, unsigned &MaxCSFrameIndex) const { + const TargetRegisterInfo *TRI, std::vector &CSI) const { DEBUG(dbgs() << LLVM_FUNCTION_NAME << " on " << MF.getFunction()->getName() << '\n'); MachineFrameInfo *MFI = MF.getFrameInfo(); diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.h b/llvm/lib/Target/Hexagon/HexagonFrameLowering.h index 187d7919b0f96..1f578f30c77fc 100644 --- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.h +++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.h @@ -72,8 +72,7 @@ class HexagonFrameLowering : public TargetFrameLowering { } bool assignCalleeSavedSpillSlots(MachineFunction &MF, - const TargetRegisterInfo *TRI, std::vector &CSI, - unsigned &MinCSFrameIndex, unsigned &MaxCSFrameIndex) + const TargetRegisterInfo *TRI, std::vector &CSI) const override; bool needsAligna(const MachineFunction &MF) const; diff --git a/llvm/lib/Target/X86/X86FrameLowering.cpp b/llvm/lib/Target/X86/X86FrameLowering.cpp index e65bf52a7bf79..ec858671c42b9 100644 --- a/llvm/lib/Target/X86/X86FrameLowering.cpp +++ b/llvm/lib/Target/X86/X86FrameLowering.cpp @@ -1799,8 +1799,7 @@ int X86FrameLowering::getFrameIndexReferenceFromSP(const MachineFunction &MF, bool X86FrameLowering::assignCalleeSavedSpillSlots( MachineFunction &MF, const TargetRegisterInfo *TRI, - std::vector &CSI, - unsigned &MinCSFrameIndex, unsigned &MaxCSFrameIndex) const { + std::vector &CSI) const { MachineFrameInfo *MFI = MF.getFrameInfo(); X86MachineFunctionInfo *X86FI = MF.getInfo(); diff --git a/llvm/lib/Target/X86/X86FrameLowering.h b/llvm/lib/Target/X86/X86FrameLowering.h index dbe6dfc92acd4..49a46a12407ef 100644 --- a/llvm/lib/Target/X86/X86FrameLowering.h +++ b/llvm/lib/Target/X86/X86FrameLowering.h @@ -80,9 +80,7 @@ class X86FrameLowering : public TargetFrameLowering { bool assignCalleeSavedSpillSlots(MachineFunction &MF, const TargetRegisterInfo *TRI, - std::vector &CSI, - unsigned &MinCSFrameIndex, - unsigned &MaxCSFrameIndex) const override; + std::vector &CSI) const override; bool spillCalleeSavedRegisters(MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, From c1d42180719646df77c35e8e6403e36258e890fe Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Thu, 14 Apr 2016 09:10:42 -0700 Subject: [PATCH 059/585] Remove LoopInfoBase::getBlockMap(). This was only used in one place for Swift and Arnold removed that use yesterday (001998c). apple-llvm-split-commit: f17708b0136063153b20965a335e6b8e417be6bc apple-llvm-split-dir: llvm/ --- llvm/include/llvm/Analysis/LoopInfo.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/llvm/include/llvm/Analysis/LoopInfo.h b/llvm/include/llvm/Analysis/LoopInfo.h index 6b5f0b3a8ff79..934db11abf597 100644 --- a/llvm/include/llvm/Analysis/LoopInfo.h +++ b/llvm/include/llvm/Analysis/LoopInfo.h @@ -550,8 +550,6 @@ class LoopInfoBase { /// loop (for example the entry node), null is returned. LoopT *getLoopFor(const BlockT *BB) const { return BBMap.lookup(BB); } - const DenseMap &getBlockMap() const { return BBMap; } - /// Same as getLoopFor. const LoopT *operator[](const BlockT *BB) const { return getLoopFor(BB); From 14281ed06731147e6312e6dbf87b9c8e96f4ff8a Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 6 Apr 2016 13:02:52 -0700 Subject: [PATCH 060/585] [Import as member] Default initialize ModuleOptions fields apple-llvm-split-commit: e519fc7b6e9fa281814de24a7f5980465fd813a5 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 7b4d0647ef24d..b19eea5199125 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -476,7 +476,7 @@ class TypedefInfo : public CommonTypeInfo { /// Descripts a series of options for a module struct ModuleOptions { - bool SwiftInferImportAsMember; + bool SwiftInferImportAsMember = false; }; } // end namespace api_notes From 92bd48c7be6bb66ca91f085b41724cd8745d08bd Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 6 Apr 2016 13:07:47 -0700 Subject: [PATCH 061/585] [Import as member] Fix my screw up apple-llvm-split-commit: 08c04436b73131323e5b360429b46ea96d33010a apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 6fe87bc7fa256..606825c0ce107 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -252,7 +252,7 @@ namespace { TagsSeq Tags; TypedefsSeq Typedefs; - llvm::Optional SwiftInferImportAsMember; + llvm::Optional SwiftInferImportAsMember = {llvm::None}; LLVM_ATTRIBUTE_DEPRECATED( void dump() LLVM_ATTRIBUTE_USED, @@ -776,8 +776,11 @@ namespace { Writer->addTypedef(t.Name, typedefInfo); } - if (TheModule.SwiftInferImportAsMember) - Writer->addModuleOptions({true}); + if (TheModule.SwiftInferImportAsMember) { + ModuleOptions opts; + opts.SwiftInferImportAsMember = true; + Writer->addModuleOptions(opts); + } if (!ErrorOccured) Writer->writeToStream(OS); From 54a3624b20b4bbbb2004c81d2688020ba5819f73 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 20 Apr 2016 09:41:48 -0700 Subject: [PATCH 062/585] [swift_newtype] Support for swift_wrapper/newtype attribute. Adds in compiler support, and tests. apple-llvm-split-commit: 4c2a76b39a7d446e10188f6ceeec79c5fd948c95 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Attr.td | 9 ++++ clang/include/clang/Basic/AttrDocs.td | 10 +++++ .../clang/Basic/DiagnosticSemaKinds.td | 3 ++ clang/include/clang/Parse/Parser.h | 8 ++++ clang/lib/Parse/ParseDecl.cpp | 36 ++++++++++++++++ clang/lib/Sema/SemaDeclAttr.cpp | 42 +++++++++++++++++++ clang/test/SemaObjC/attr-swift_newtype.c | 20 +++++++++ 7 files changed, 128 insertions(+) create mode 100644 clang/test/SemaObjC/attr-swift_newtype.c diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index e59cc88b92cc6..1f158437d34de 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1385,6 +1385,15 @@ def SwiftName : InheritableAttr { let Documentation = [Undocumented]; } +def SwiftNewtype : Attr { + let Spellings = [GNU<"swift_newtype">, GNU<"swift_wrapper">]; + let Subjects = SubjectList<[TypedefName], ErrorDiag, "ExpectedType">; + let Args = [EnumArgument<"NewtypeKind", "NewtypeKind", + ["struct", "enum"], + ["NK_Struct", "NK_Enum"]>]; + let Documentation = [SwiftNewtypeDocs]; +} + def SwiftPrivate : InheritableAttr { let Spellings = [GCC<"swift_private">]; let Documentation = [Undocumented]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 08274e18de626..8f7c3c2b0eda4 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1982,6 +1982,16 @@ All of these conventions except ``none`` require the function to have an error p }]; } +def SwiftNewtypeDocs : Documentation { + let Category = SwiftDocs; + let Content = [{ +The ``swift_newtype`` attribute indicates that the typedef to which the attribute appertains is imported as a new Swift type of the typedef's name. +* ``swift_newtype(struct)`` means that a Swift struct will be created for this typedef. +* ``swift_newtype(enum)`` means that a Swift enum will be created for this typedef. + }]; +} + + def OMPDeclareTargetDocs : Documentation { let Category = DocCatFunction; let Heading = "#pragma omp declare target"; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index cdafd8cfb99f0..82d0004954c9c 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3056,6 +3056,9 @@ def err_attr_swift_error_no_error_parameter : Error< def err_attr_swift_error_return_type : Error< "%0 attribute with '%1' convention can only be applied to a " "%select{function|method}2 returning %select{an integral type|a pointer}3">; +def warn_swift_newtype_attribute_non_typedef : Warning< + "'swift_newtype' attribute may be put on a typedef only; " + "attribute is ignored">; // Function Parameter Semantic Analysis. def err_param_with_void_type : Error<"argument may not have 'void' type">; diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 66406ba36ef70..9bbcbf70bdef1 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2241,6 +2241,14 @@ class Parser : public CodeCompletionHandler { SourceLocation ScopeLoc, AttributeList::Syntax Syntax); + void ParseSwiftNewtypeAttribute(IdentifierInfo &SwiftNewtype, + SourceLocation SwiftNewtypeLoc, + ParsedAttributes &attrs, + SourceLocation *endLoc, + IdentifierInfo *ScopeName, + SourceLocation ScopeLoc, + AttributeList::Syntax Syntax); + void ParseAttributeWithTypeArg(IdentifierInfo &AttrName, SourceLocation AttrNameLoc, ParsedAttributes &Attrs, diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index b06eda9d23ca6..323ce537c1340 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -238,6 +238,38 @@ IdentifierLoc *Parser::ParseIdentifierLoc() { return IL; } +void Parser::ParseSwiftNewtypeAttribute( + IdentifierInfo &SwiftNewtype, SourceLocation SwiftNewtypeLoc, + ParsedAttributes &attrs, SourceLocation *endLoc, IdentifierInfo *ScopeName, + SourceLocation ScopeLoc, AttributeList::Syntax Syntax) { + + BalancedDelimiterTracker Parens(*this, tok::l_paren); + Parens.consumeOpen(); + + if (Tok.is(tok::r_paren)) { + Diag(Tok.getLocation(), diag::err_argument_required_after_attribute); + Parens.consumeClose(); + return; + } + if (Tok.isNot(tok::kw_struct) && Tok.isNot(tok::kw_enum)) { + Diag(Tok.getLocation(), diag::warn_attribute_type_not_supported) + << &SwiftNewtype << Tok.getIdentifierInfo(); + if (!isTokenSpecial()) + ConsumeToken(); + Parens.consumeClose(); + return; + } + auto IL = IdentifierLoc::create(Actions.Context, Tok.getLocation(), + Tok.getIdentifierInfo()); + ConsumeToken(); + auto identLoc = ArgsUnion(IL); + + attrs.addNew(&SwiftNewtype, + SourceRange(SwiftNewtypeLoc, Parens.getCloseLocation()), + ScopeName, ScopeLoc, &identLoc, 1, Syntax); + Parens.consumeClose(); +} + void Parser::ParseAttributeWithTypeArg(IdentifierInfo &AttrName, SourceLocation AttrNameLoc, ParsedAttributes &Attrs, @@ -357,6 +389,10 @@ void Parser::ParseGNUAttributeArgs(IdentifierInfo *AttrName, ParseTypeTagForDatatypeAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName, ScopeLoc, Syntax); return; + } else if (AttrKind == AttributeList::AT_SwiftNewtype) { + ParseSwiftNewtypeAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc, + ScopeName, ScopeLoc, Syntax); + return; } else if (attributeIsTypeArgAttr(*AttrName)) { ParseAttributeWithTypeArg(*AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName, ScopeLoc, Syntax); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index b8ab12eae946a..d257c2486e6fd 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4971,6 +4971,45 @@ static void handleSwiftBridgeAttr(Sema &S, Decl *D, const AttributeList &Attr) { Attr.getAttributeSpellingListIndex())); } +static void handleSwiftNewtypeAttr(Sema &S, Decl *D, const AttributeList &Attr) { + // Make sure that there is an identifier as the annotation's single + // argument. + if (Attr.getNumArgs() != 1) { + S.Diag(Attr.getLoc(), diag::err_attribute_wrong_number_arguments) + << Attr.getName() << 1; + Attr.setInvalid(); + return; + } + if (!Attr.isArgIdent(0)) { + S.Diag(Attr.getLoc(), diag::err_attribute_argument_type) + << Attr.getName() << AANT_ArgumentIdentifier; + Attr.setInvalid(); + return; + } + + IdentifierInfo *II = Attr.getArgAsIdent(0)->Ident; + SwiftNewtypeAttr::NewtypeKind Kind; + if (II->isStr("struct")) + Kind = SwiftNewtypeAttr::NK_Struct; + else if (II->isStr("enum")) + Kind = SwiftNewtypeAttr::NK_Enum; + else { + S.Diag(Attr.getLoc(), diag::warn_attribute_type_not_supported) + << Attr.getName() << II; + Attr.setInvalid(); + return; + } + + if (!isa(D)) { + S.Diag(Attr.getLoc(), diag::warn_swift_newtype_attribute_non_typedef); + return; + } + + D->addAttr(::new (S.Context) + SwiftNewtypeAttr(Attr.getRange(), S.Context, Kind, + Attr.getAttributeSpellingListIndex())); +} + //===----------------------------------------------------------------------===// // Microsoft specific attribute handlers. //===----------------------------------------------------------------------===// @@ -6283,6 +6322,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_SwiftBridge: handleSwiftBridgeAttr(S, D, Attr); break; + case AttributeList::AT_SwiftNewtype: + handleSwiftNewtypeAttr(S, D, Attr); + break; } } diff --git a/clang/test/SemaObjC/attr-swift_newtype.c b/clang/test/SemaObjC/attr-swift_newtype.c new file mode 100644 index 0000000000000..61e4d89642a7c --- /dev/null +++ b/clang/test/SemaObjC/attr-swift_newtype.c @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -verify -fsyntax-only %s + +typedef int T1 __attribute__((swift_newtype(struct))); +typedef int T2 __attribute__((swift_newtype(enum))); + +typedef int T3 __attribute__((swift_wrapper(struct))); +typedef int T4 __attribute__((swift_wrapper(enum))); + + +typedef int Bad1 __attribute__((swift_newtype(bad))); // expected-warning{{'swift_newtype' attribute argument not supported: 'bad'}} +typedef int Bad2 __attribute__((swift_newtype())); // expected-error{{argument required after attribute}} +typedef int Bad3 __attribute__((swift_newtype(bad, badder))); + // expected-error@-1{{expected ')'}} + // expected-note@-2{{to match this '('}} + // expected-warning@-3{{'swift_newtype' attribute argument not supported: 'bad'}} + + +// TODO: better error message below +// FIXME: why is this a parse error, rather than Sema error triggering? +struct Bad4 __attribute__((swift_newtype(struct))) { }; // expected-error{{expected identifier or '('}} From a3db799d69ebfe8179803b7ff33b60a78b6e7f93 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 20 Apr 2016 12:47:18 -0700 Subject: [PATCH 063/585] [swift_name] add warning group, to enable flags apple-llvm-split-commit: a64266f95c3c173440294c23b5b8980d3d7cfc89 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 82d0004954c9c..9b09c33c1ef00 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3058,7 +3058,7 @@ def err_attr_swift_error_return_type : Error< "%select{function|method}2 returning %select{an integral type|a pointer}3">; def warn_swift_newtype_attribute_non_typedef : Warning< "'swift_newtype' attribute may be put on a typedef only; " - "attribute is ignored">; + "attribute is ignored">, InGroup>; // Function Parameter Semantic Analysis. def err_param_with_void_type : Error<"argument may not have 'void' type">; From 1f4ca9724538c1ff5183dd4cc3b29f2e0cfeb628 Mon Sep 17 00:00:00 2001 From: Vedant Kumar Date: Thu, 21 Apr 2016 21:07:25 +0000 Subject: [PATCH 064/585] [ProfileData] Report errors from InstrProfSymtab::create InstrProfSymtab::create can fail with instrprof_error::malformed, but this error is silently dropped. Propagate the error up to the caller so we fail early. Eventually, I'd like to transition ProfileData over to the new Error class so we can't ignore hard failures like this. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@267055 91177308-0d34-0410-b5e6-96231b3b80d8 (cherry picked from commit 089f755c042bc9cab7a7e8899f1b0c706611146c) apple-llvm-split-commit: a47bc3328dbf87c0d334354dc3c9e2678faef85e apple-llvm-split-dir: llvm/ --- llvm/include/llvm/ProfileData/InstrProfReader.h | 2 +- llvm/lib/ProfileData/InstrProfReader.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/llvm/include/llvm/ProfileData/InstrProfReader.h b/llvm/include/llvm/ProfileData/InstrProfReader.h index 74610f7ff558b..303d9af90f51d 100644 --- a/llvm/include/llvm/ProfileData/InstrProfReader.h +++ b/llvm/include/llvm/ProfileData/InstrProfReader.h @@ -196,7 +196,7 @@ class RawInstrProfReader : public InstrProfReader { } private: - void createSymtab(InstrProfSymtab &Symtab); + std::error_code createSymtab(InstrProfSymtab &Symtab); std::error_code readNextHeader(const char *CurrentPos); std::error_code readHeader(const RawInstrProf::Header &Header); template IntT swap(IntT Int) const { diff --git a/llvm/lib/ProfileData/InstrProfReader.cpp b/llvm/lib/ProfileData/InstrProfReader.cpp index 27142e0533f5c..b3b89d74dfb9d 100644 --- a/llvm/lib/ProfileData/InstrProfReader.cpp +++ b/llvm/lib/ProfileData/InstrProfReader.cpp @@ -297,8 +297,11 @@ RawInstrProfReader::readNextHeader(const char *CurrentPos) { } template -void RawInstrProfReader::createSymtab(InstrProfSymtab &Symtab) { - Symtab.create(StringRef(NamesStart, NamesSize)); +std::error_code +RawInstrProfReader::createSymtab(InstrProfSymtab &Symtab) { + std::error_code EC = Symtab.create(StringRef(NamesStart, NamesSize)); + if (EC) + return EC; for (const RawInstrProf::ProfileData *I = Data; I != DataEnd; ++I) { const IntPtrT FPtr = swap(I->FunctionPointer); if (!FPtr) @@ -306,6 +309,7 @@ void RawInstrProfReader::createSymtab(InstrProfSymtab &Symtab) { Symtab.mapAddress(FPtr, I->NameRef); } Symtab.finalizeSymtab(); + return success(); } template @@ -345,7 +349,9 @@ RawInstrProfReader::readHeader(const RawInstrProf::Header &Header) { ProfileEnd = Start + ProfileSize; std::unique_ptr NewSymtab = make_unique(); - createSymtab(*NewSymtab.get()); + if (auto EC = createSymtab(*NewSymtab.get())) + return EC; + Symtab = std::move(NewSymtab); return success(); } From 08065b224a60f1cea6e7557ae088779b5c8994c7 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 21 Apr 2016 15:03:37 -0700 Subject: [PATCH 065/585] [swift_newtype] Add heading to doc apple-llvm-split-commit: 11e75401c4007dce102f7eac3ddb6d81292d8a38 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/AttrDocs.td | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 8f7c3c2b0eda4..73ef8e59a2f2b 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1984,6 +1984,7 @@ All of these conventions except ``none`` require the function to have an error p def SwiftNewtypeDocs : Documentation { let Category = SwiftDocs; + let Heading = "swift_newtype"; let Content = [{ The ``swift_newtype`` attribute indicates that the typedef to which the attribute appertains is imported as a new Swift type of the typedef's name. * ``swift_newtype(struct)`` means that a Swift struct will be created for this typedef. From 0a395e26f5271cd1f3985d88b4b04302f4499f14 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 21 Apr 2016 23:23:40 -0700 Subject: [PATCH 066/585] [API Notes] Add support for SwiftPrivate -> swift_private Fixes rdar://problem/25872038. apple-llvm-split-commit: e6bdd6d24a6651fa890dd9954702bb2268cb302e apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 9 ++++++- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 1 + clang/lib/APINotes/APINotesWriter.cpp | 4 +++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 24 ++++++++++++++++++- clang/lib/Sema/SemaAPINotes.cpp | 5 ++++ clang/test/APINotes/Inputs/roundtrip.apinotes | 16 +++++++++++++ 7 files changed, 57 insertions(+), 4 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index b19eea5199125..11a1e7acf54d6 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -70,16 +70,20 @@ class CommonEntityInfo { /// Whether this entity is marked unavailable in Swift. unsigned UnavailableInSwift : 1; + /// Whether this entity is considered "private" to a Swift overlay. + unsigned SwiftPrivate : 1; + /// Swift name of this entity. std::string SwiftName; - CommonEntityInfo() : Unavailable(0), UnavailableInSwift(0) { } + CommonEntityInfo() : Unavailable(0), UnavailableInSwift(0), SwiftPrivate(0) { } friend bool operator==(const CommonEntityInfo &lhs, const CommonEntityInfo &rhs) { return lhs.UnavailableMsg == rhs.UnavailableMsg && lhs.Unavailable == rhs.Unavailable && lhs.UnavailableInSwift == rhs.UnavailableInSwift && + lhs.SwiftPrivate == rhs.SwiftPrivate && lhs.SwiftName == rhs.SwiftName; } @@ -107,6 +111,9 @@ class CommonEntityInfo { } } + if (rhs.SwiftPrivate) + lhs.SwiftPrivate = true; + if (rhs.SwiftName.length() != 0 && lhs.SwiftName.length() == 0) lhs.SwiftName = rhs.SwiftName; diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 79143c48533db..2d878ef866308 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 11; // SwiftInferImportAsMember +const uint16_t VERSION_MINOR = 12; // SwiftPrivate using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index b05eb9d603db6..6de82ebe64be2 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -33,6 +33,7 @@ namespace { uint8_t unavailableBits = *data++; info.Unavailable = (unavailableBits >> 1) & 0x01; info.UnavailableInSwift = unavailableBits & 0x01; + info.SwiftPrivate = (unavailableBits >> 2) & 0x01; unsigned msgLength = endian::readNext(data); info.UnavailableMsg diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index d2bd6f19ec88c..3d4938e7cd002 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -296,7 +296,9 @@ namespace { static void emitCommonEntityInfo(raw_ostream &out, const CommonEntityInfo &info) { endian::Writer writer(out); - writer.write(info.Unavailable << 1 | info.UnavailableInSwift); + writer.write(info.SwiftPrivate << 2 + | info.Unavailable << 1 + | info.UnavailableInSwift); writer.write(info.UnavailableMsg.size()); out.write(info.UnavailableMsg.c_str(), info.UnavailableMsg.size()); writer.write(info.SwiftName.size()); diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 606825c0ce107..a5e4fa82ceb17 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -174,6 +174,7 @@ namespace { NullabilitySeq Nullability; llvm::Optional NullabilityOfRet; AvailabilityItem Availability; + bool SwiftPrivate = false; StringRef SwiftName; api_notes::FactoryAsInitKind FactoryAsInit = api_notes::FactoryAsInitKind::Infer; @@ -186,6 +187,7 @@ namespace { StringRef Name; llvm::Optional Nullability; AvailabilityItem Availability; + bool SwiftPrivate = false; StringRef SwiftName; }; typedef std::vector PropertiesSeq; @@ -194,6 +196,7 @@ namespace { StringRef Name; bool AuditedForNullability = false; AvailabilityItem Availability; + bool SwiftPrivate = false; StringRef SwiftName; StringRef SwiftBridge; MethodsSeq Methods; @@ -206,6 +209,7 @@ namespace { NullabilitySeq Nullability; llvm::Optional NullabilityOfRet; AvailabilityItem Availability; + bool SwiftPrivate = false; StringRef SwiftName; }; typedef std::vector FunctionsSeq; @@ -214,6 +218,7 @@ namespace { StringRef Name; llvm::Optional Nullability; AvailabilityItem Availability; + bool SwiftPrivate = false; StringRef SwiftName; }; typedef std::vector GlobalVariablesSeq; @@ -221,6 +226,7 @@ namespace { struct EnumConstant { StringRef Name; AvailabilityItem Availability; + bool SwiftPrivate = false; StringRef SwiftName; }; typedef std::vector EnumConstantsSeq; @@ -229,6 +235,7 @@ namespace { StringRef Name; AvailabilityItem Availability; StringRef SwiftName; + bool SwiftPrivate = false; StringRef SwiftBridge; }; typedef std::vector TagsSeq; @@ -237,6 +244,7 @@ namespace { StringRef Name; AvailabilityItem Availability; StringRef SwiftName; + bool SwiftPrivate = false; StringRef SwiftBridge; }; typedef std::vector TypedefsSeq; @@ -321,6 +329,7 @@ namespace llvm { AbsentNullability); io.mapOptional("Availability", p.Availability.Mode); io.mapOptional("AvailabilityMsg", p.Availability.Msg); + io.mapOptional("SwiftPrivate", p.SwiftPrivate); io.mapOptional("SwiftName", p.SwiftName); } }; @@ -335,6 +344,7 @@ namespace llvm { AbsentNullability); io.mapOptional("Availability", m.Availability.Mode); io.mapOptional("AvailabilityMsg", m.Availability.Msg); + io.mapOptional("SwiftPrivate", m.SwiftPrivate); io.mapOptional("SwiftName", m.SwiftName); io.mapOptional("FactoryAsInit", m.FactoryAsInit, api_notes::FactoryAsInitKind::Infer); @@ -350,6 +360,7 @@ namespace llvm { io.mapOptional("AuditedForNullability", c.AuditedForNullability, false); io.mapOptional("Availability", c.Availability.Mode); io.mapOptional("AvailabilityMsg", c.Availability.Msg); + io.mapOptional("SwiftPrivate", c.SwiftPrivate); io.mapOptional("SwiftName", c.SwiftName); io.mapOptional("SwiftBridge", c.SwiftBridge); io.mapOptional("Methods", c.Methods); @@ -366,6 +377,7 @@ namespace llvm { AbsentNullability); io.mapOptional("Availability", f.Availability.Mode); io.mapOptional("AvailabilityMsg", f.Availability.Msg); + io.mapOptional("SwiftPrivate", f.SwiftPrivate); io.mapOptional("SwiftName", f.SwiftName); } }; @@ -378,6 +390,7 @@ namespace llvm { AbsentNullability); io.mapOptional("Availability", v.Availability.Mode); io.mapOptional("AvailabilityMsg", v.Availability.Msg); + io.mapOptional("SwiftPrivate", v.SwiftPrivate); io.mapOptional("SwiftName", v.SwiftName); } }; @@ -388,6 +401,7 @@ namespace llvm { io.mapRequired("Name", v.Name); io.mapOptional("Availability", v.Availability.Mode); io.mapOptional("AvailabilityMsg", v.Availability.Msg); + io.mapOptional("SwiftPrivate", v.SwiftPrivate); io.mapOptional("SwiftName", v.SwiftName); } }; @@ -398,6 +412,7 @@ namespace llvm { io.mapRequired("Name", t.Name); io.mapOptional("Availability", t.Availability.Mode); io.mapOptional("AvailabilityMsg", t.Availability.Msg); + io.mapOptional("SwiftPrivate", t.SwiftPrivate); io.mapOptional("SwiftName", t.SwiftName); io.mapOptional("SwiftBridge", t.SwiftBridge); } @@ -409,6 +424,7 @@ namespace llvm { io.mapRequired("Name", t.Name); io.mapOptional("Availability", t.Availability.Mode); io.mapOptional("AvailabilityMsg", t.Availability.Msg); + io.mapOptional("SwiftPrivate", t.SwiftPrivate); io.mapOptional("SwiftName", t.SwiftName); io.mapOptional("SwiftBridge", t.SwiftBridge); } @@ -543,6 +559,7 @@ namespace { return true; convertAvailability(common.Availability, info, apiName); + info.SwiftPrivate = common.SwiftPrivate; info.SwiftName = common.SwiftName; return false; } @@ -644,6 +661,7 @@ namespace { if (!isAvailable(prop.Availability)) continue; convertAvailability(prop.Availability, pInfo, prop.Name); + pInfo.SwiftPrivate = prop.SwiftPrivate; pInfo.SwiftName = prop.SwiftName; if (prop.Nullability) pInfo.setNullabilityAudited(*prop.Nullability); @@ -698,6 +716,7 @@ namespace { if (!isAvailable(global.Availability)) continue; convertAvailability(global.Availability, info, global.Name); + info.SwiftPrivate = global.SwiftPrivate; info.SwiftName = global.SwiftName; if (global.Nullability) info.setNullabilityAudited(*global.Nullability); @@ -718,6 +737,7 @@ namespace { if (!isAvailable(function.Availability)) continue; convertAvailability(function.Availability, info, function.Name); + info.SwiftPrivate = function.SwiftPrivate; info.SwiftName = function.SwiftName; convertNullability(function.Nullability, function.NullabilityOfRet, @@ -740,6 +760,7 @@ namespace { if (!isAvailable(enumConstant.Availability)) continue; convertAvailability(enumConstant.Availability, info, enumConstant.Name); + info.SwiftPrivate = enumConstant.SwiftPrivate; info.SwiftName = enumConstant.SwiftName; Writer->addEnumConstant(enumConstant.Name, info); } @@ -749,7 +770,7 @@ namespace { for (const auto &t : TheModule.Tags) { // Check for duplicate tag definitions. if (!knownTags.insert(t.Name).second) { - emitError("multiple definitions of tag '" + t.Name + "'"); + emitError("multiple definitions Of tag '" + t.Name + "'"); continue; } @@ -861,6 +882,7 @@ namespace { template void handleCommon(T &record, const CommonEntityInfo &info) { handleAvailability(record.Availability, info); + record.SwiftPrivate = info.SwiftPrivate; record.SwiftName = copyString(info.SwiftName); } diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index bef88e2f87227..5072824313e6c 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -122,6 +122,11 @@ static void ProcessAPINotes(Sema &S, Decl *D, /*Replacement=*/StringRef())); } + // swift_private + if (Info.SwiftPrivate && !D->hasAttr()) { + D->addAttr(SwiftPrivateAttr::CreateImplicit(S.Context)); + } + // swift_name if (!Info.SwiftName.empty() && !D->hasAttr()) { D->addAttr(SwiftNameAttr::CreateImplicit(S.Context, diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 8509c79389b71..f02aa003dc0b3 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -7,6 +7,7 @@ Classes: - Name: NSCell Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: '' SwiftBridge: '' Methods: @@ -15,6 +16,7 @@ Classes: NullabilityOfRet: U Availability: available AvailabilityMsg: '' + SwiftPrivate: true SwiftName: '' DesignatedInit: true - Selector: 'initImageCell:' @@ -23,6 +25,7 @@ Classes: NullabilityOfRet: U Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: '' DesignatedInit: true - Selector: 'initTextCell:' @@ -31,6 +34,7 @@ Classes: NullabilityOfRet: U Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: '' DesignatedInit: true - Selector: 'initWithCoder:' @@ -39,6 +43,7 @@ Classes: NullabilityOfRet: U Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: '' DesignatedInit: true Required: true @@ -46,6 +51,7 @@ Classes: AuditedForNullability: true Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: '' SwiftBridge: View Methods: @@ -55,6 +61,7 @@ Classes: NullabilityOfRet: N Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: '' - Selector: 'addSubview:positioned:relativeTo:' MethodKind: Instance @@ -62,6 +69,7 @@ Classes: NullabilityOfRet: N Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: '' - Selector: 'beginDraggingSessionWithItems:event:source:' MethodKind: Instance @@ -69,44 +77,52 @@ Classes: NullabilityOfRet: N Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: 'beginDragginSession(_:event:source:)' Properties: - Name: enclosingScrollView Nullability: O Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: enclosing - Name: makeBackingLayer Nullability: N Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: '' Functions: - Name: NSAvailableWindowDepths NullabilityOfRet: N Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: 'availableWindowDepths()' Globals: - Name: NSCalibratedWhiteColorSpace Nullability: N Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: calibratedWhite Enumerators: - Name: NSColorRed Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: Red Tags: - Name: NSSomeStruct Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: SomeStruct SwiftBridge: '' Typedefs: - Name: NSTypedef Availability: available AvailabilityMsg: '' + SwiftPrivate: false SwiftName: Typedef SwiftBridge: '' From 4bace0c25885baff6b96370a735dfe1796d2b978 Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Tue, 17 May 2016 18:24:56 -0700 Subject: [PATCH 067/585] Tighten up validation of swift_name for properties and subscripts. Fix a logic bug that turned our check for parameter counts on getters and setters into a no-op. Add support for subscript getters and setters, and validate that they're imported as instance methods, and that subscript setters have a 'newValue:' labeled argument. Improve QoI by producing more specific diagnostics for problems with swift names. apple-llvm-split-commit: e3985c9f45c5b8462caaa5c5be383dbb582fa3e9 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticIDs.h | 2 +- .../clang/Basic/DiagnosticSemaKinds.td | 26 +++++ clang/lib/Sema/SemaDeclAttr.cpp | 96 +++++++++++++++---- clang/test/SemaObjC/attr-swift.m | 40 ++++++-- 4 files changed, 137 insertions(+), 27 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticIDs.h b/clang/include/clang/Basic/DiagnosticIDs.h index fcd04a0be187d..d13ae4c95a52e 100644 --- a/clang/include/clang/Basic/DiagnosticIDs.h +++ b/clang/include/clang/Basic/DiagnosticIDs.h @@ -36,7 +36,7 @@ namespace clang { DIAG_START_AST = DIAG_START_PARSE + 500, DIAG_START_COMMENT = DIAG_START_AST + 110, DIAG_START_SEMA = DIAG_START_COMMENT + 100, - DIAG_START_ANALYSIS = DIAG_START_SEMA + 3500, + DIAG_START_ANALYSIS = DIAG_START_SEMA + 4000, DIAG_UPPER_LIMIT = DIAG_START_ANALYSIS + 100 }; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 1e768588d1174..de146008d3cb4 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3063,6 +3063,32 @@ def err_attr_swift_name_identifier : Error< "parameter of %0 attribute must be an ASCII identifier string">; def err_attr_swift_name_function : Error< "parameter of %0 attribute must be a Swift function name string">; +def err_attr_swift_name_context_name_invalid_identifier : Error< + "%0 attribute has invalid identifier for context name">; +def err_attr_swift_name_basename_invalid_identifier : Error< + "%0 attribute has invalid identifier for base name">; +def err_attr_swift_name_parameter_invalid_identifier : Error< + "%0 attribute has invalid identifier for parameter name">; +def err_attr_swift_name_missing_parameters : Error< + "%0 attribute is missing parameter label clause">; +def err_attr_swift_name_subscript_not_accessor : Error< + "%0 attribute for 'subscript' must be a getter or setter">; +def err_attr_swift_name_subscript_no_parameter : Error< + "%0 attribute for 'subscript' must take at least one parameter">; +def err_attr_swift_name_subscript_getter_newValue : Error< + "%0 attribute for 'subscript' getter cannot take a 'newValue:' parameter">; +def err_attr_swift_name_subscript_setter_no_newValue : Error< + "%0 attribute for 'subscript' setter must take a 'newValue:' parameter">; +def err_attr_swift_name_subscript_setter_multiple_newValues : Error< + "%0 attribute for 'subscript' setter cannot take multiple 'newValue:' parameters">; +def err_attr_swift_name_getter_parameters : Error< + "%0 attribute for getter must not take any parameters besides 'self:'">; +def err_attr_swift_name_setter_parameters : Error< + "%0 attribute for setter must take one parameter for new value">; +def err_attr_swift_name_multiple_selfs : Error< + "%0 attribute cannot specify more than one 'self:' parameter">; +def err_attr_swift_name_static_subscript : Error< + "%0 attribute for 'subscript' must take a 'self:' parameter">; def warn_attr_swift_name_num_params : Warning< "too %select{few|many}0 parameters in %1 attribute (expected %2; got %3)">, InGroup>; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 84ed524a3012d..3c131b6436c76 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4671,8 +4671,10 @@ static void handleObjCPreciseLifetimeAttr(Sema &S, Decl *D, /// Do a very rough check to make sure \p Name looks like a Swift function name, /// e.g. init(foo:bar:baz:) or controllerForName(_:), -/// and return the number of parameter names. -static bool validateSwiftFunctionName(StringRef Name, +/// and output the number of parameter names, whether this is a single-arg +/// initializer. Returns None if the name is valid, otherwise returns a +/// one-parameter diagnostic ID describing the problem. +static Optional validateSwiftFunctionName(StringRef Name, unsigned &ParamCount, bool &IsSingleParamInit) { ParamCount = 0; @@ -4690,7 +4692,7 @@ static bool validateSwiftFunctionName(StringRef Name, } if (Name.back() != ')') - return false; + return diag::err_attr_swift_name_function; StringRef BaseName, Parameters; std::tie(BaseName, Parameters) = Name.split('('); @@ -4704,63 +4706,116 @@ static bool validateSwiftFunctionName(StringRef Name, BaseName = ContextName; ContextName = StringRef(); } else if (ContextName.empty() || !isValidIdentifier(ContextName)) { - return false; + return diag::err_attr_swift_name_context_name_invalid_identifier; } else { IsMember = true; } if (!isValidIdentifier(BaseName) || BaseName == "_") - return false; + return diag::err_attr_swift_name_basename_invalid_identifier; + bool IsSubscript = BaseName == "subscript"; + // A subscript accessor must be a getter or setter. + if (IsSubscript && !isGetter && !isSetter) + return diag::err_attr_swift_name_subscript_not_accessor; + if (Parameters.empty()) - return false; + return diag::err_attr_swift_name_missing_parameters; Parameters = Parameters.drop_back(); // ')' if (Parameters.empty()) { - // Setters must have at least one parameter. - if (isSetter) return false; - - return true; + // Setters and subscripts must have at least one parameter. + if (IsSubscript) + return diag::err_attr_swift_name_subscript_no_parameter; + if (isSetter) + return diag::err_attr_swift_name_setter_parameters; + + return None; } if (Parameters.back() != ':') - return false; + return diag::err_attr_swift_name_function; Optional SelfLocation; + Optional NewValueLocation; + unsigned NewValueCount = 0; StringRef NextParam; do { std::tie(NextParam, Parameters) = Parameters.split(':'); if (!isValidIdentifier(NextParam)) - return false; + return diag::err_attr_swift_name_parameter_invalid_identifier; // "self" indicates the "self" argument for a member. if (IsMember && NextParam == "self") { // More than one "self"? - if (SelfLocation) return false; + if (SelfLocation) return diag::err_attr_swift_name_multiple_selfs; // The "self" location is the current parameter. SelfLocation = ParamCount; } - + + // "newValue" indicates the "newValue" argument for a setter. + if (NextParam == "newValue") { + // There should only be one 'newValue', but it's only significant for + // subscript accessors, so don't error right away. + ++NewValueCount; + + NewValueLocation = ParamCount; + } ++ParamCount; } while (!Parameters.empty()); + // Only instance subscripts are currently supported. + if (IsSubscript && !SelfLocation) + return diag::err_attr_swift_name_static_subscript; + IsSingleParamInit = (ParamCount == 1 && BaseName == "init" && NextParam != "_"); // Check the number of parameters for a getter/setter. if (isGetter || isSetter) { // Setters have one parameter for the new value. - unsigned NumExpectedParams = isSetter ? 1 : 0; + unsigned NumExpectedParams; + unsigned ParamDiag; + + if (isSetter) { + NumExpectedParams = 1; + ParamDiag = diag::err_attr_swift_name_setter_parameters; + } else { + NumExpectedParams = 0; + ParamDiag = diag::err_attr_swift_name_getter_parameters; + } // Instance methods have one parameter for "self". if (SelfLocation) ++NumExpectedParams; - - if (ParamCount != NumExpectedParams) return true; + + // Subscripts may have additional parameters beyond the expected params for + // the index. + if (IsSubscript) { + if (ParamCount < NumExpectedParams) + return ParamDiag; + // A subscript setter must explicitly label its newValue parameter to + // distinguish it from index parameters. + if (isSetter) { + if (!NewValueLocation) + return diag::err_attr_swift_name_subscript_setter_no_newValue; + // There can only be one. + if (NewValueCount > 1) + return diag::err_attr_swift_name_subscript_setter_multiple_newValues; + } else { + // Subscript getters should have no 'newValue:' parameter. + if (NewValueLocation) + return diag::err_attr_swift_name_subscript_getter_newValue; + } + } else { + // Property accessors must have exactly the number of expected params. + if (ParamCount != NumExpectedParams) + return ParamDiag; + } } - return true; + return None; } static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { @@ -4784,8 +4839,9 @@ static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { bool IsSingleParamInit; unsigned SwiftParamCount; - if (!validateSwiftFunctionName(Name, SwiftParamCount, IsSingleParamInit)) { - S.Diag(ArgLoc, diag::err_attr_swift_name_function) << Attr.getName(); + if (auto diagID + = validateSwiftFunctionName(Name, SwiftParamCount, IsSingleParamInit)){ + S.Diag(ArgLoc, *diagID) << Attr.getName(); return; } diff --git a/clang/test/SemaObjC/attr-swift.m b/clang/test/SemaObjC/attr-swift.m index effcfefff2f05..d28430dd1d43f 100644 --- a/clang/test/SemaObjC/attr-swift.m +++ b/clang/test/SemaObjC/attr-swift.m @@ -70,8 +70,8 @@ + (instancetype)specialGarply __attribute__((swift_name("foo(options:)"))); // e + (instancetype)trailingParen __attribute__((swift_name("foo("))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} + (instancetype)trailingColon:(int)value __attribute__((swift_name("foo(value)"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} -+ (instancetype)initialIgnore:(int)value __attribute__((swift_name("_(value:)"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} -+ (instancetype)middleOmitted:(int)value __attribute__((swift_name("foo(:)"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} ++ (instancetype)initialIgnore:(int)value __attribute__((swift_name("_(value:)"))); // expected-error {{'swift_name' attribute has invalid identifier for base name}} ++ (instancetype)middleOmitted:(int)value __attribute__((swift_name("foo(:)"))); // expected-error {{'swift_name' attribute has invalid identifier for parameter name}} @property(strong) id someProp __attribute__((swift_name("prop"))); @end @@ -106,19 +106,47 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { // Getters and setters. float Point3DGetMagnitude(Point3D point) __attribute__((swift_name("getter:Point3D.magnitude(self:)"))); +float Point3DGetMagnitudeAndSomethingElse(Point3D point, float wat) __attribute__((swift_name("getter:Point3D.magnitude(self:wat:)"))); // expected-error {{'swift_name' attribute for getter must not take any parameters besides 'self:'}} + float Point3DGetRadius(Point3D point) __attribute__((swift_name("getter:Point3D.radius(self:)"))); -void Point3DSetRadius(Point3D point, float radius) __attribute__((swift_name("setter:Point3D.radius(self:_:)"))); +void Point3DSetRadius(Point3D point, float radius) __attribute__((swift_name("setter:Point3D.radius(self:newValue:)"))); + +float Point3DPreGetRadius(Point3D point) __attribute__((swift_name("getter:Point3D.preRadius(self:)"))); +void Point3DPreSetRadius(float radius, Point3D point) __attribute__((swift_name("setter:Point3D.preRadius(newValue:self:)"))); + +void Point3DSetRadiusAndSomethingElse(Point3D point, float radius, float wat) __attribute__((swift_name("setter:Point3D.radius(self:newValue:wat:)"))); // expected-error {{'swift_name' attribute for setter must take one parameter for new value}} + +float Point3DGetComponent(Point3D point, unsigned index) __attribute__((swift_name("getter:Point3D.subscript(self:_:)"))); +float Point3DSetComponent(Point3D point, unsigned index, float value) __attribute__((swift_name("setter:Point3D.subscript(self:_:newValue:)"))); + +float Point3DGetMatrixComponent(Point3D point, unsigned x, unsigned y) __attribute__((swift_name("getter:Point3D.subscript(self:x:y:)"))); +void Point3DSetMatrixComponent(Point3D point, unsigned x, float value, unsigned y) __attribute__((swift_name("setter:Point3D.subscript(self:x:newValue:y:)"))); + +float Point3DSetWithoutNewValue(Point3D point, unsigned x, unsigned y) __attribute__((swift_name("setter:Point3D.subscript(self:x:y:)"))); // expected-error {{'swift_name' attribute for 'subscript' setter must take a 'newValue:' parameter}} + +float Point3DSubscriptButNotGetterSetter(Point3D point, unsigned x) __attribute__((swift_name("Point3D.subscript(self:_:)"))); // expected-error {{'swift_name' attribute for 'subscript' must be a getter or setter}} + +void Point3DSubscriptSetterTwoNewValues(Point3D point, unsigned x, float a, float b) __attribute__((swift_name("setter:Point3D.subscript(self:_:newValue:newValue:)"))); // expected-error {{'swift_name' attribute for 'subscript' setter cannot take multiple 'newValue:' parameters}} +float Point3DSubscriptGetterNewValue(Point3D point, unsigned x, float a, float b) __attribute__((swift_name("getter:Point3D.subscript(self:_:newValue:newValue:)"))); // expected-error {{'swift_name' attribute for 'subscript' getter cannot take a 'newValue:' parameter}} + +void Point3DMethodWithNewValue(Point3D point, float newValue) __attribute__((swift_name("Point3D.method(self:newValue:)"))); +void Point3DMethodWithNewValues(Point3D point, float newValue, float newValueB) __attribute__((swift_name("Point3D.method(self:newValue:newValue:)"))); + +float Point3DStaticSubscript(unsigned x) __attribute__((swift_name("getter:Point3D.subscript(_:)"))); // expected-error {{'swift_name' attribute for 'subscript' must take a 'self:' parameter}} +float Point3DStaticSubscriptNoArgs(void) __attribute__((swift_name("getter:Point3D.subscript()"))); // expected-error {{'swift_name' attribute for 'subscript' must take at least one parameter}} + +float Point3DPreGetComponent(Point3D point, unsigned index) __attribute__((swift_name("getter:Point3D.subscript(self:_:)"))); Point3D getCurrentPoint3D(void) __attribute__((swift_name("getter:currentPoint3D()"))); -void setCurrentPoint3D(Point3D point) __attribute__((swift_name("setter:currentPoint3D(_:)"))); +void setCurrentPoint3D(Point3D point) __attribute__((swift_name("setter:currentPoint3D(newValue:)"))); Point3D getLastPoint3D(void) __attribute__((swift_name("getter:lastPoint3D()"))); -void setLastPoint3D(Point3D point) __attribute__((swift_name("setter:lastPoint3D(_:)"))); +void setLastPoint3D(Point3D point) __attribute__((swift_name("setter:lastPoint3D(newValue:)"))); Point3D getZeroPoint() __attribute__((swift_name("getter:Point3D.zero()"))); -void setZeroPoint(Point3D point) __attribute__((swift_name("setter:Point3D.zero(_:)"))); +void setZeroPoint(Point3D point) __attribute__((swift_name("setter:Point3D.zero(newValue:)"))); Point3D badGetter1(int x) __attribute__((swift_name("getter:bad1(_:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} void badSetter1() __attribute__((swift_name("getter:bad1())"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} From 02cfc2945fc86dd00bbc915e36b57b4c7786df1e Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Wed, 18 May 2016 11:33:32 -0700 Subject: [PATCH 068/585] Only allow swift_name attributes on prototyped declarations. apple-llvm-split-commit: 6138e27b79204f47b8057de3e3f97b7647912cfb apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 ++ clang/lib/Sema/SemaDeclAttr.cpp | 4 ++++ clang/test/SemaObjC/attr-swift.m | 5 +++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index de146008d3cb4..4929af3d05c7d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3063,6 +3063,8 @@ def err_attr_swift_name_identifier : Error< "parameter of %0 attribute must be an ASCII identifier string">; def err_attr_swift_name_function : Error< "parameter of %0 attribute must be a Swift function name string">; +def err_attr_swift_name_function_no_prototype : Error< + "%0 attribute can only be applied to function declarations with prototypes">; def err_attr_swift_name_context_name_invalid_identifier : Error< "%0 attribute has invalid identifier for context name">; def err_attr_swift_name_basename_invalid_identifier : Error< diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index b9e080f368d92..8c12dd230e9b4 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4836,6 +4836,10 @@ static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { const auto *Function = cast(D); ParamCount = Function->getNumParams(); Params = Function->parameters(); + + if (!Function->hasWrittenPrototype()) + S.Diag(ArgLoc, diag::err_attr_swift_name_function_no_prototype) + << Attr.getName(); } bool IsSingleParamInit; diff --git a/clang/test/SemaObjC/attr-swift.m b/clang/test/SemaObjC/attr-swift.m index d28430dd1d43f..c8b316cbe94f6 100644 --- a/clang/test/SemaObjC/attr-swift.m +++ b/clang/test/SemaObjC/attr-swift.m @@ -145,11 +145,12 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { void setLastPoint3D(Point3D point) __attribute__((swift_name("setter:lastPoint3D(newValue:)"))); -Point3D getZeroPoint() __attribute__((swift_name("getter:Point3D.zero()"))); +Point3D getZeroPoint(void) __attribute__((swift_name("getter:Point3D.zero()"))); +Point3D getZeroPointNoPrototype() __attribute__((swift_name("getter:Point3D.zeroNoPrototype()"))); // expected-error{{'swift_name' attribute can only be applied to function declarations with prototypes}} void setZeroPoint(Point3D point) __attribute__((swift_name("setter:Point3D.zero(newValue:)"))); Point3D badGetter1(int x) __attribute__((swift_name("getter:bad1(_:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} -void badSetter1() __attribute__((swift_name("getter:bad1())"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} +void badSetter1(void) __attribute__((swift_name("getter:bad1())"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} Point3D badGetter2(Point3D point) __attribute__((swift_name("getter:bad2(_:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} From 1a1ef7565539c95e3d0dd7519805591ddaf374f9 Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Thu, 19 May 2016 12:56:16 -0700 Subject: [PATCH 069/585] Validate swift_names that come from API notes. And relax swift_name validation errors into warnings, so that SDKs don't break when we change or tighten the rules. apple-llvm-split-commit: 7ef73896edc6b84fce64fb472fb62c393ed3f223 apple-llvm-split-dir: clang/ --- .../clang/Basic/DiagnosticSemaKinds.td | 34 ++--- clang/include/clang/Sema/Sema.h | 18 +++ clang/lib/Sema/SemaAPINotes.cpp | 11 +- clang/lib/Sema/SemaDeclAttr.cpp | 138 ++++++++++-------- clang/test/SemaObjC/attr-swift.m | 40 ++--- 5 files changed, 141 insertions(+), 100 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 4929af3d05c7d..c4fe8546f4501 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3057,39 +3057,37 @@ def err_objc_attr_protocol_requires_definition : Error< "attribute %0 can only be applied to @protocol definitions, not forward declarations">; // Swift attributes -def err_attr_swift_name_decl_kind : Error< +def warn_attr_swift_name_decl_kind : Warning< "%0 attribute cannot be applied to this declaration">; -def err_attr_swift_name_identifier : Error< - "parameter of %0 attribute must be an ASCII identifier string">; -def err_attr_swift_name_function : Error< +def warn_attr_swift_name_function : Warning< "parameter of %0 attribute must be a Swift function name string">; -def err_attr_swift_name_function_no_prototype : Error< +def warn_attr_swift_name_function_no_prototype : Warning< "%0 attribute can only be applied to function declarations with prototypes">; -def err_attr_swift_name_context_name_invalid_identifier : Error< +def warn_attr_swift_name_context_name_invalid_identifier : Warning< "%0 attribute has invalid identifier for context name">; -def err_attr_swift_name_basename_invalid_identifier : Error< +def warn_attr_swift_name_basename_invalid_identifier : Warning< "%0 attribute has invalid identifier for base name">; -def err_attr_swift_name_parameter_invalid_identifier : Error< +def warn_attr_swift_name_parameter_invalid_identifier : Warning< "%0 attribute has invalid identifier for parameter name">; -def err_attr_swift_name_missing_parameters : Error< +def warn_attr_swift_name_missing_parameters : Warning< "%0 attribute is missing parameter label clause">; -def err_attr_swift_name_subscript_not_accessor : Error< +def warn_attr_swift_name_subscript_not_accessor : Warning< "%0 attribute for 'subscript' must be a getter or setter">; -def err_attr_swift_name_subscript_no_parameter : Error< +def warn_attr_swift_name_subscript_no_parameter : Warning< "%0 attribute for 'subscript' must take at least one parameter">; -def err_attr_swift_name_subscript_getter_newValue : Error< +def warn_attr_swift_name_subscript_getter_newValue : Warning< "%0 attribute for 'subscript' getter cannot take a 'newValue:' parameter">; -def err_attr_swift_name_subscript_setter_no_newValue : Error< +def warn_attr_swift_name_subscript_setter_no_newValue : Warning< "%0 attribute for 'subscript' setter must take a 'newValue:' parameter">; -def err_attr_swift_name_subscript_setter_multiple_newValues : Error< +def warn_attr_swift_name_subscript_setter_multiple_newValues : Warning< "%0 attribute for 'subscript' setter cannot take multiple 'newValue:' parameters">; -def err_attr_swift_name_getter_parameters : Error< +def warn_attr_swift_name_getter_parameters : Warning< "%0 attribute for getter must not take any parameters besides 'self:'">; -def err_attr_swift_name_setter_parameters : Error< +def warn_attr_swift_name_setter_parameters : Warning< "%0 attribute for setter must take one parameter for new value">; -def err_attr_swift_name_multiple_selfs : Error< +def warn_attr_swift_name_multiple_selfs : Warning< "%0 attribute cannot specify more than one 'self:' parameter">; -def err_attr_swift_name_static_subscript : Error< +def warn_attr_swift_name_static_subscript : Warning< "%0 attribute for 'subscript' must take a 'self:' parameter">; def warn_attr_swift_name_num_params : Warning< "too %select{few|many}0 parameters in %1 attribute (expected %2; got %3)">, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6c7c60c2d4aed..d72c1912f2bfe 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1382,6 +1382,24 @@ class Sema { } }; + /// Do a check to make sure \p Name looks like a legal swift_name + /// attribute for the decl \p D. Raise a diagnostic if the name is invalid + /// for the given declaration. + /// + /// For a function, this will validate a compound Swift name, + /// e.g. init(foo:bar:baz:) or controllerForName(_:), + /// and the function will output the number of parameter names, and whether + /// this is a single-arg initializer. + /// + /// For a type, enum constant, property, or variable declaration, this will + /// validate either a simple identifier, or a qualified + /// context.identifier name. + /// + /// \returns true if the name is a valid swift name for \p D, false otherwise. + bool DiagnoseSwiftName(Decl *D, StringRef Name, + SourceLocation ArgLoc, + IdentifierInfo *AttrName); + private: bool RequireCompleteTypeImpl(SourceLocation Loc, QualType T, TypeDiagnoser *Diagnoser); diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 5072824313e6c..4468d22f30a53 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -111,7 +111,7 @@ static void ProcessAPINotes(Sema &S, Decl *D, if (Info.UnavailableInSwift) { D->addAttr(AvailabilityAttr::CreateImplicit( - S.Context, + S.Context, &S.Context.Idents.get("swift"), VersionTuple(), VersionTuple(), @@ -129,9 +129,14 @@ static void ProcessAPINotes(Sema &S, Decl *D, // swift_name if (!Info.SwiftName.empty() && !D->hasAttr()) { + auto &APINoteName = S.getASTContext().Idents.get("SwiftName API Note"); + + if (!S.DiagnoseSwiftName(D, Info.SwiftName, D->getLocation(), + &APINoteName)) { + return; + } D->addAttr(SwiftNameAttr::CreateImplicit(S.Context, - CopyString(S.Context, - Info.SwiftName))); + CopyString(S.Context, Info.SwiftName))); } } diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 8c12dd230e9b4..9e9c28f92cdc3 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4670,15 +4670,11 @@ static void handleObjCPreciseLifetimeAttr(Sema &S, Decl *D, Attr.getAttributeSpellingListIndex())); } -/// Do a very rough check to make sure \p Name looks like a Swift function name, -/// e.g. init(foo:bar:baz:) or controllerForName(_:), -/// and output the number of parameter names, whether this is a single-arg -/// initializer. Returns None if the name is valid, otherwise returns a -/// one-parameter diagnostic ID describing the problem. -static Optional validateSwiftFunctionName(StringRef Name, - unsigned &ParamCount, - bool &IsSingleParamInit) { - ParamCount = 0; +static Optional +validateSwiftFunctionName(StringRef Name, + unsigned &SwiftParamCount, + bool &IsSingleParamInit) { + SwiftParamCount = 0; // Check whether this will be mapped to a getter or setter of a // property. @@ -4693,7 +4689,7 @@ static Optional validateSwiftFunctionName(StringRef Name, } if (Name.back() != ')') - return diag::err_attr_swift_name_function; + return diag::warn_attr_swift_name_function; StringRef BaseName, Parameters; std::tie(BaseName, Parameters) = Name.split('('); @@ -4707,35 +4703,35 @@ static Optional validateSwiftFunctionName(StringRef Name, BaseName = ContextName; ContextName = StringRef(); } else if (ContextName.empty() || !isValidIdentifier(ContextName)) { - return diag::err_attr_swift_name_context_name_invalid_identifier; + return diag::warn_attr_swift_name_context_name_invalid_identifier; } else { IsMember = true; } if (!isValidIdentifier(BaseName) || BaseName == "_") - return diag::err_attr_swift_name_basename_invalid_identifier; + return diag::warn_attr_swift_name_basename_invalid_identifier; bool IsSubscript = BaseName == "subscript"; // A subscript accessor must be a getter or setter. if (IsSubscript && !isGetter && !isSetter) - return diag::err_attr_swift_name_subscript_not_accessor; + return diag::warn_attr_swift_name_subscript_not_accessor; if (Parameters.empty()) - return diag::err_attr_swift_name_missing_parameters; + return diag::warn_attr_swift_name_missing_parameters; Parameters = Parameters.drop_back(); // ')' if (Parameters.empty()) { // Setters and subscripts must have at least one parameter. if (IsSubscript) - return diag::err_attr_swift_name_subscript_no_parameter; + return diag::warn_attr_swift_name_subscript_no_parameter; if (isSetter) - return diag::err_attr_swift_name_setter_parameters; + return diag::warn_attr_swift_name_setter_parameters; return None; } if (Parameters.back() != ':') - return diag::err_attr_swift_name_function; + return diag::warn_attr_swift_name_function; Optional SelfLocation; Optional NewValueLocation; @@ -4745,15 +4741,15 @@ static Optional validateSwiftFunctionName(StringRef Name, std::tie(NextParam, Parameters) = Parameters.split(':'); if (!isValidIdentifier(NextParam)) - return diag::err_attr_swift_name_parameter_invalid_identifier; + return diag::warn_attr_swift_name_parameter_invalid_identifier; // "self" indicates the "self" argument for a member. if (IsMember && NextParam == "self") { // More than one "self"? - if (SelfLocation) return diag::err_attr_swift_name_multiple_selfs; + if (SelfLocation) return diag::warn_attr_swift_name_multiple_selfs; // The "self" location is the current parameter. - SelfLocation = ParamCount; + SelfLocation = SwiftParamCount; } // "newValue" indicates the "newValue" argument for a setter. @@ -4762,17 +4758,17 @@ static Optional validateSwiftFunctionName(StringRef Name, // subscript accessors, so don't error right away. ++NewValueCount; - NewValueLocation = ParamCount; + NewValueLocation = SwiftParamCount; } - ++ParamCount; + ++SwiftParamCount; } while (!Parameters.empty()); // Only instance subscripts are currently supported. if (IsSubscript && !SelfLocation) - return diag::err_attr_swift_name_static_subscript; + return diag::warn_attr_swift_name_static_subscript; IsSingleParamInit = - (ParamCount == 1 && BaseName == "init" && NextParam != "_"); + (SwiftParamCount == 1 && BaseName == "init" && NextParam != "_"); // Check the number of parameters for a getter/setter. if (isGetter || isSetter) { @@ -4782,10 +4778,10 @@ static Optional validateSwiftFunctionName(StringRef Name, if (isSetter) { NumExpectedParams = 1; - ParamDiag = diag::err_attr_swift_name_setter_parameters; + ParamDiag = diag::warn_attr_swift_name_setter_parameters; } else { NumExpectedParams = 0; - ParamDiag = diag::err_attr_swift_name_getter_parameters; + ParamDiag = diag::warn_attr_swift_name_getter_parameters; } // Instance methods have one parameter for "self". @@ -4794,24 +4790,24 @@ static Optional validateSwiftFunctionName(StringRef Name, // Subscripts may have additional parameters beyond the expected params for // the index. if (IsSubscript) { - if (ParamCount < NumExpectedParams) + if (SwiftParamCount < NumExpectedParams) return ParamDiag; // A subscript setter must explicitly label its newValue parameter to // distinguish it from index parameters. if (isSetter) { if (!NewValueLocation) - return diag::err_attr_swift_name_subscript_setter_no_newValue; + return diag::warn_attr_swift_name_subscript_setter_no_newValue; // There can only be one. if (NewValueCount > 1) - return diag::err_attr_swift_name_subscript_setter_multiple_newValues; + return diag::warn_attr_swift_name_subscript_setter_multiple_newValues; } else { // Subscript getters should have no 'newValue:' parameter. if (NewValueLocation) - return diag::err_attr_swift_name_subscript_getter_newValue; + return diag::warn_attr_swift_name_subscript_getter_newValue; } } else { // Property accessors must have exactly the number of expected params. - if (ParamCount != NumExpectedParams) + if (SwiftParamCount != NumExpectedParams) return ParamDiag; } } @@ -4819,12 +4815,23 @@ static Optional validateSwiftFunctionName(StringRef Name, return None; } -static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { - StringRef Name; - SourceLocation ArgLoc; - if (!S.checkStringLiteralArgumentAttr(Attr, 0, Name, &ArgLoc)) - return; - +/// Do a check to make sure \p Name looks like a legal swift_name +/// attribute for the decl \p D. Raise a diagnostic if the name is invalid +/// for the given declaration. +/// +/// For a function, this will validate a compound Swift name, +/// e.g. init(foo:bar:baz:) or controllerForName(_:), +/// and the function will output the number of parameter names, and whether this +/// is a single-arg initializer. +/// +/// For a type, enum constant, property, or variable declaration, this will +/// validate either a simple identifier, or a qualified +/// context.identifier name. +/// +/// \returns true if the name is a valid swift name for \p D, false otherwise. +bool Sema::DiagnoseSwiftName(Decl *D, StringRef Name, + SourceLocation ArgLoc, + IdentifierInfo *AttrName) { if (isa(D) || isa(D)) { ArrayRef Params; unsigned ParamCount; @@ -4837,19 +4844,21 @@ static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { ParamCount = Function->getNumParams(); Params = Function->parameters(); - if (!Function->hasWrittenPrototype()) - S.Diag(ArgLoc, diag::err_attr_swift_name_function_no_prototype) - << Attr.getName(); + if (!Function->hasWrittenPrototype()) { + Diag(ArgLoc, diag::warn_attr_swift_name_function_no_prototype) + << AttrName; + return false; + } } - bool IsSingleParamInit; unsigned SwiftParamCount; - if (auto diagID - = validateSwiftFunctionName(Name, SwiftParamCount, IsSingleParamInit)){ - S.Diag(ArgLoc, *diagID) << Attr.getName(); - return; + bool IsSingleParamInit; + if (auto diagID = validateSwiftFunctionName(Name, SwiftParamCount, + IsSingleParamInit)) { + Diag(ArgLoc, *diagID) << AttrName; + return false; } - + bool ParamsOK; if (SwiftParamCount == ParamCount) { ParamsOK = true; @@ -4871,10 +4880,10 @@ static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { } if (!ParamsOK) { - S.Diag(ArgLoc, diag::warn_attr_swift_name_num_params) - << (SwiftParamCount > ParamCount) << Attr.getName() + Diag(ArgLoc, diag::warn_attr_swift_name_num_params) + << (SwiftParamCount > ParamCount) << AttrName << ParamCount << SwiftParamCount; - return; + return false; } } else if (isa(D) || isa(D) || @@ -4887,24 +4896,35 @@ static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { BaseName = ContextName; ContextName = StringRef(); } else if (!isValidIdentifier(ContextName)) { - S.Diag(ArgLoc, diag::err_attr_swift_name_identifier) << Attr.getName(); - return; + Diag(ArgLoc, diag::warn_attr_swift_name_context_name_invalid_identifier) + << AttrName; + return false; } if (!isValidIdentifier(BaseName)) { - S.Diag(ArgLoc, diag::err_attr_swift_name_identifier) << Attr.getName(); - return; + Diag(ArgLoc, diag::warn_attr_swift_name_basename_invalid_identifier) + << AttrName; + return false; } } else { - S.Diag(Attr.getLoc(), diag::err_attr_swift_name_decl_kind) - << Attr.getName(); - return; + Diag(ArgLoc, diag::warn_attr_swift_name_decl_kind) << AttrName; + return false; } + return true; +} - D->addAttr(::new (S.Context) - SwiftNameAttr(Attr.getRange(), S.Context, Name, - Attr.getAttributeSpellingListIndex())); +static void handleSwiftName(Sema &S, Decl *D, const AttributeList &Attr) { + StringRef Name; + SourceLocation ArgLoc; + if (!S.checkStringLiteralArgumentAttr(Attr, 0, Name, &ArgLoc)) + return; + + if (!S.DiagnoseSwiftName(D, Name, ArgLoc, Attr.getName())) + return; + + D->addAttr(::new (S.Context) SwiftNameAttr(Attr.getRange(), S.Context, Name, + Attr.getAttributeSpellingListIndex())); } static bool isErrorParameter(Sema &S, QualType paramType) { diff --git a/clang/test/SemaObjC/attr-swift.m b/clang/test/SemaObjC/attr-swift.m index c8b316cbe94f6..bdcbbd0ace605 100644 --- a/clang/test/SemaObjC/attr-swift.m +++ b/clang/test/SemaObjC/attr-swift.m @@ -50,7 +50,7 @@ + (SNFoo *)fooWithValue:(int)value __attribute__((swift_name("foo(value:)"))); + (SNFoo *)fooWithValue:(int)value value:(int)value2 __attribute__((swift_name("foo(value:extra:)"))); + (SNFoo *)fooWithConvertingValue:(int)value value:(int)value2 __attribute__((swift_name("init(_:extra:)"))); -+ (SNFoo *)fooWithOtherValue:(int)value __attribute__((swift_name("init"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} ++ (SNFoo *)fooWithOtherValue:(int)value __attribute__((swift_name("init"))); // expected-warning {{parameter of 'swift_name' attribute must be a Swift function name string}} + (SNFoo *)fooWithAnotherValue:(int)value __attribute__((swift_name("foo()"))); // expected-warning {{too few parameters in 'swift_name' attribute (expected 1; got 0)}} + (SNFoo *)fooWithYetAnotherValue:(int)value __attribute__((swift_name("foo(value:extra:)"))); // expected-warning {{too many parameters in 'swift_name' attribute (expected 1; got 2)}} @@ -68,10 +68,10 @@ + (instancetype)specialBar __attribute__((swift_name("init(options:extra:)"))); + (instancetype)specialBaz __attribute__((swift_name("init(_:)"))); // expected-warning {{too many parameters in 'swift_name' attribute (expected 0; got 1)}} + (instancetype)specialGarply __attribute__((swift_name("foo(options:)"))); // expected-warning {{too many parameters in 'swift_name' attribute (expected 0; got 1)}} -+ (instancetype)trailingParen __attribute__((swift_name("foo("))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} -+ (instancetype)trailingColon:(int)value __attribute__((swift_name("foo(value)"))); // expected-error {{parameter of 'swift_name' attribute must be a Swift function name string}} -+ (instancetype)initialIgnore:(int)value __attribute__((swift_name("_(value:)"))); // expected-error {{'swift_name' attribute has invalid identifier for base name}} -+ (instancetype)middleOmitted:(int)value __attribute__((swift_name("foo(:)"))); // expected-error {{'swift_name' attribute has invalid identifier for parameter name}} ++ (instancetype)trailingParen __attribute__((swift_name("foo("))); // expected-warning {{parameter of 'swift_name' attribute must be a Swift function name string}} ++ (instancetype)trailingColon:(int)value __attribute__((swift_name("foo(value)"))); // expected-warning {{parameter of 'swift_name' attribute must be a Swift function name string}} ++ (instancetype)initialIgnore:(int)value __attribute__((swift_name("_(value:)"))); // expected-warning {{'swift_name' attribute has invalid identifier for base name}} ++ (instancetype)middleOmitted:(int)value __attribute__((swift_name("foo(:)"))); // expected-warning {{'swift_name' attribute has invalid identifier for parameter name}} @property(strong) id someProp __attribute__((swift_name("prop"))); @end @@ -80,7 +80,7 @@ enum __attribute__((swift_name("MoreColors"))) MoreColors { Cyan, Magenta, Yellow __attribute__((swift_name("RoseGold"))), - Black __attribute__((swift_name("SpaceGrey()"))) // expected-error {{parameter of 'swift_name' attribute must be an ASCII identifier string}} + Black __attribute__((swift_name("SpaceGrey()"))) // expected-warning {{'swift_name' attribute has invalid identifier for base name}} }; struct __attribute__((swift_name("FooStruct"))) BarStruct { @@ -89,7 +89,7 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { int global_int __attribute__((swift_name("GlobalInt"))); -void foo1(int i) __attribute__((swift_name("foo"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} +void foo1(int i) __attribute__((swift_name("foo"))); // expected-warning{{parameter of 'swift_name' attribute must be a Swift function name string}} void foo2(int i) __attribute__((swift_name("foo()"))); // expected-warning{{too few parameters in 'swift_name' attribute (expected 1; got 0)}} void foo2(int i) __attribute__((swift_name("foo(a:b:)"))); // expected-warning{{too many parameters in 'swift_name' attribute (expected 1; got 2)}} void foo3(int i, int j) __attribute__((swift_name("fooWithX(_:y:)"))); // okay @@ -106,7 +106,7 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { // Getters and setters. float Point3DGetMagnitude(Point3D point) __attribute__((swift_name("getter:Point3D.magnitude(self:)"))); -float Point3DGetMagnitudeAndSomethingElse(Point3D point, float wat) __attribute__((swift_name("getter:Point3D.magnitude(self:wat:)"))); // expected-error {{'swift_name' attribute for getter must not take any parameters besides 'self:'}} +float Point3DGetMagnitudeAndSomethingElse(Point3D point, float wat) __attribute__((swift_name("getter:Point3D.magnitude(self:wat:)"))); // expected-warning {{'swift_name' attribute for getter must not take any parameters besides 'self:'}} float Point3DGetRadius(Point3D point) __attribute__((swift_name("getter:Point3D.radius(self:)"))); void Point3DSetRadius(Point3D point, float radius) __attribute__((swift_name("setter:Point3D.radius(self:newValue:)"))); @@ -114,7 +114,7 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { float Point3DPreGetRadius(Point3D point) __attribute__((swift_name("getter:Point3D.preRadius(self:)"))); void Point3DPreSetRadius(float radius, Point3D point) __attribute__((swift_name("setter:Point3D.preRadius(newValue:self:)"))); -void Point3DSetRadiusAndSomethingElse(Point3D point, float radius, float wat) __attribute__((swift_name("setter:Point3D.radius(self:newValue:wat:)"))); // expected-error {{'swift_name' attribute for setter must take one parameter for new value}} +void Point3DSetRadiusAndSomethingElse(Point3D point, float radius, float wat) __attribute__((swift_name("setter:Point3D.radius(self:newValue:wat:)"))); // expected-warning {{'swift_name' attribute for setter must take one parameter for new value}} float Point3DGetComponent(Point3D point, unsigned index) __attribute__((swift_name("getter:Point3D.subscript(self:_:)"))); float Point3DSetComponent(Point3D point, unsigned index, float value) __attribute__((swift_name("setter:Point3D.subscript(self:_:newValue:)"))); @@ -122,18 +122,18 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { float Point3DGetMatrixComponent(Point3D point, unsigned x, unsigned y) __attribute__((swift_name("getter:Point3D.subscript(self:x:y:)"))); void Point3DSetMatrixComponent(Point3D point, unsigned x, float value, unsigned y) __attribute__((swift_name("setter:Point3D.subscript(self:x:newValue:y:)"))); -float Point3DSetWithoutNewValue(Point3D point, unsigned x, unsigned y) __attribute__((swift_name("setter:Point3D.subscript(self:x:y:)"))); // expected-error {{'swift_name' attribute for 'subscript' setter must take a 'newValue:' parameter}} +float Point3DSetWithoutNewValue(Point3D point, unsigned x, unsigned y) __attribute__((swift_name("setter:Point3D.subscript(self:x:y:)"))); // expected-warning {{'swift_name' attribute for 'subscript' setter must take a 'newValue:' parameter}} -float Point3DSubscriptButNotGetterSetter(Point3D point, unsigned x) __attribute__((swift_name("Point3D.subscript(self:_:)"))); // expected-error {{'swift_name' attribute for 'subscript' must be a getter or setter}} +float Point3DSubscriptButNotGetterSetter(Point3D point, unsigned x) __attribute__((swift_name("Point3D.subscript(self:_:)"))); // expected-warning {{'swift_name' attribute for 'subscript' must be a getter or setter}} -void Point3DSubscriptSetterTwoNewValues(Point3D point, unsigned x, float a, float b) __attribute__((swift_name("setter:Point3D.subscript(self:_:newValue:newValue:)"))); // expected-error {{'swift_name' attribute for 'subscript' setter cannot take multiple 'newValue:' parameters}} -float Point3DSubscriptGetterNewValue(Point3D point, unsigned x, float a, float b) __attribute__((swift_name("getter:Point3D.subscript(self:_:newValue:newValue:)"))); // expected-error {{'swift_name' attribute for 'subscript' getter cannot take a 'newValue:' parameter}} +void Point3DSubscriptSetterTwoNewValues(Point3D point, unsigned x, float a, float b) __attribute__((swift_name("setter:Point3D.subscript(self:_:newValue:newValue:)"))); // expected-warning {{'swift_name' attribute for 'subscript' setter cannot take multiple 'newValue:' parameters}} +float Point3DSubscriptGetterNewValue(Point3D point, unsigned x, float a, float b) __attribute__((swift_name("getter:Point3D.subscript(self:_:newValue:newValue:)"))); // expected-warning {{'swift_name' attribute for 'subscript' getter cannot take a 'newValue:' parameter}} void Point3DMethodWithNewValue(Point3D point, float newValue) __attribute__((swift_name("Point3D.method(self:newValue:)"))); void Point3DMethodWithNewValues(Point3D point, float newValue, float newValueB) __attribute__((swift_name("Point3D.method(self:newValue:newValue:)"))); -float Point3DStaticSubscript(unsigned x) __attribute__((swift_name("getter:Point3D.subscript(_:)"))); // expected-error {{'swift_name' attribute for 'subscript' must take a 'self:' parameter}} -float Point3DStaticSubscriptNoArgs(void) __attribute__((swift_name("getter:Point3D.subscript()"))); // expected-error {{'swift_name' attribute for 'subscript' must take at least one parameter}} +float Point3DStaticSubscript(unsigned x) __attribute__((swift_name("getter:Point3D.subscript(_:)"))); // expected-warning {{'swift_name' attribute for 'subscript' must take a 'self:' parameter}} +float Point3DStaticSubscriptNoArgs(void) __attribute__((swift_name("getter:Point3D.subscript()"))); // expected-warning {{'swift_name' attribute for 'subscript' must take at least one parameter}} float Point3DPreGetComponent(Point3D point, unsigned index) __attribute__((swift_name("getter:Point3D.subscript(self:_:)"))); @@ -146,15 +146,15 @@ struct __attribute__((swift_name("FooStruct"))) BarStruct { void setLastPoint3D(Point3D point) __attribute__((swift_name("setter:lastPoint3D(newValue:)"))); Point3D getZeroPoint(void) __attribute__((swift_name("getter:Point3D.zero()"))); -Point3D getZeroPointNoPrototype() __attribute__((swift_name("getter:Point3D.zeroNoPrototype()"))); // expected-error{{'swift_name' attribute can only be applied to function declarations with prototypes}} +Point3D getZeroPointNoPrototype() __attribute__((swift_name("getter:Point3D.zeroNoPrototype()"))); // expected-warning{{'swift_name' attribute can only be applied to function declarations with prototypes}} void setZeroPoint(Point3D point) __attribute__((swift_name("setter:Point3D.zero(newValue:)"))); -Point3D badGetter1(int x) __attribute__((swift_name("getter:bad1(_:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} -void badSetter1(void) __attribute__((swift_name("getter:bad1())"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} +Point3D badGetter1(int x) __attribute__((swift_name("getter:bad1(_:))"))); // expected-warning{{parameter of 'swift_name' attribute must be a Swift function name string}} +void badSetter1(void) __attribute__((swift_name("getter:bad1())"))); // expected-warning{{parameter of 'swift_name' attribute must be a Swift function name string}} -Point3D badGetter2(Point3D point) __attribute__((swift_name("getter:bad2(_:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} +Point3D badGetter2(Point3D point) __attribute__((swift_name("getter:bad2(_:))"))); // expected-warning{{parameter of 'swift_name' attribute must be a Swift function name string}} -void badSetter2(Point3D point) __attribute__((swift_name("setter:bad2(self:))"))); // expected-error{{parameter of 'swift_name' attribute must be a Swift function name string}} +void badSetter2(Point3D point) __attribute__((swift_name("setter:bad2(self:))"))); // expected-warning{{parameter of 'swift_name' attribute must be a Swift function name string}} // --- swift_error --- From e226ae71c546ba35152a1f54f28aa1aa8f1ff98d Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Thu, 19 May 2016 14:47:39 -0700 Subject: [PATCH 070/585] Collect swift_name warnings under a swift-name-attribute warning group. apple-llvm-split-commit: c23b39088cfb1aa1ddaaaee4fbd22410b05ca17c apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticGroups.td | 3 ++ .../clang/Basic/DiagnosticSemaKinds.td | 50 ++++++++++++------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 7b4740fe22a2f..5335545cad8ca 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -365,6 +365,9 @@ def StringCompare : DiagGroup<"string-compare">; def StringPlusInt : DiagGroup<"string-plus-int">; def StringPlusChar : DiagGroup<"string-plus-char">; def StrncatSize : DiagGroup<"strncat-size">; + +def SwiftNameAttribute : DiagGroup<"swift-name-attribute">; + def TautologicalOutOfRangeCompare : DiagGroup<"tautological-constant-out-of-range-compare">; def TautologicalPointerCompare : DiagGroup<"tautological-pointer-compare">; def TautologicalOverlapCompare : DiagGroup<"tautological-overlap-compare">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index c4fe8546f4501..d61de2428859e 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3058,40 +3058,56 @@ def err_objc_attr_protocol_requires_definition : Error< // Swift attributes def warn_attr_swift_name_decl_kind : Warning< - "%0 attribute cannot be applied to this declaration">; + "%0 attribute cannot be applied to this declaration">, + InGroup; def warn_attr_swift_name_function : Warning< - "parameter of %0 attribute must be a Swift function name string">; + "parameter of %0 attribute must be a Swift function name string">, + InGroup; def warn_attr_swift_name_function_no_prototype : Warning< - "%0 attribute can only be applied to function declarations with prototypes">; + "%0 attribute can only be applied to function declarations with prototypes">, + InGroup; def warn_attr_swift_name_context_name_invalid_identifier : Warning< - "%0 attribute has invalid identifier for context name">; + "%0 attribute has invalid identifier for context name">, + InGroup; def warn_attr_swift_name_basename_invalid_identifier : Warning< - "%0 attribute has invalid identifier for base name">; + "%0 attribute has invalid identifier for base name">, + InGroup; def warn_attr_swift_name_parameter_invalid_identifier : Warning< - "%0 attribute has invalid identifier for parameter name">; + "%0 attribute has invalid identifier for parameter name">, + InGroup; def warn_attr_swift_name_missing_parameters : Warning< - "%0 attribute is missing parameter label clause">; + "%0 attribute is missing parameter label clause">, + InGroup; def warn_attr_swift_name_subscript_not_accessor : Warning< - "%0 attribute for 'subscript' must be a getter or setter">; + "%0 attribute for 'subscript' must be a getter or setter">, + InGroup; def warn_attr_swift_name_subscript_no_parameter : Warning< - "%0 attribute for 'subscript' must take at least one parameter">; + "%0 attribute for 'subscript' must take at least one parameter">, + InGroup; def warn_attr_swift_name_subscript_getter_newValue : Warning< - "%0 attribute for 'subscript' getter cannot take a 'newValue:' parameter">; + "%0 attribute for 'subscript' getter cannot take a 'newValue:' parameter">, + InGroup; def warn_attr_swift_name_subscript_setter_no_newValue : Warning< - "%0 attribute for 'subscript' setter must take a 'newValue:' parameter">; + "%0 attribute for 'subscript' setter must take a 'newValue:' parameter">, + InGroup; def warn_attr_swift_name_subscript_setter_multiple_newValues : Warning< - "%0 attribute for 'subscript' setter cannot take multiple 'newValue:' parameters">; + "%0 attribute for 'subscript' setter cannot take multiple 'newValue:' parameters">, + InGroup; def warn_attr_swift_name_getter_parameters : Warning< - "%0 attribute for getter must not take any parameters besides 'self:'">; + "%0 attribute for getter must not take any parameters besides 'self:'">, + InGroup; def warn_attr_swift_name_setter_parameters : Warning< - "%0 attribute for setter must take one parameter for new value">; + "%0 attribute for setter must take one parameter for new value">, + InGroup; def warn_attr_swift_name_multiple_selfs : Warning< - "%0 attribute cannot specify more than one 'self:' parameter">; + "%0 attribute cannot specify more than one 'self:' parameter">, + InGroup; def warn_attr_swift_name_static_subscript : Warning< - "%0 attribute for 'subscript' must take a 'self:' parameter">; + "%0 attribute for 'subscript' must take a 'self:' parameter">, + InGroup; def warn_attr_swift_name_num_params : Warning< "too %select{few|many}0 parameters in %1 attribute (expected %2; got %3)">, - InGroup>; + InGroup; def err_attr_swift_error_no_error_parameter : Error< "%0 attribute can only be applied to a %select{function|method}1 " "with an error parameter">; From 5e9ad90ce7e9aea3ac7c90cb17f045636dfbc974 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Mon, 13 Jun 2016 10:27:18 -0700 Subject: [PATCH 071/585] Add support for __builtin_os_log_format[_buffer_size] These new builtins support a mechanism for logging OS events, using a printf-like format string to specify the layout of data in a buffer. The _buffer_size version of the builtin can be used to determine the size of the buffer to allocate to hold the data, and then __builtin_os_log_format can write data into that buffer. This implements format checking to report mismatches between the format string and the data arguments. Most of this code was written by Chris Willmore. apple-llvm-split-commit: 7378eb19555c81a736d85b966512e5803db69e03 apple-llvm-split-dir: clang/ --- .../clang/Analysis/Analyses/FormatString.h | 19 +- clang/include/clang/Analysis/Analyses/OSLog.h | 146 ++++++++++++ clang/include/clang/Basic/Builtins.def | 3 + .../clang/Basic/DiagnosticSemaKinds.td | 13 ++ clang/include/clang/Sema/Sema.h | 3 + clang/lib/Analysis/CMakeLists.txt | 1 + clang/lib/Analysis/FormatString.cpp | 2 + clang/lib/Analysis/OSLog.cpp | 153 +++++++++++++ clang/lib/Analysis/PrintfFormatString.cpp | 37 ++- clang/lib/CodeGen/CGBuiltin.cpp | 82 +++++++ clang/lib/Sema/SemaChecking.cpp | 214 +++++++++++++++--- clang/lib/Sema/SemaDeclAttr.cpp | 1 + clang/test/CodeGen/builtins.c | 59 +++++ clang/test/CodeGenObjC/os_log.m | 39 ++++ clang/test/Sema/format-strings.c | 27 +++ clang/test/SemaObjC/format-strings-objc.m | 15 ++ 16 files changed, 784 insertions(+), 30 deletions(-) create mode 100644 clang/include/clang/Analysis/Analyses/OSLog.h create mode 100644 clang/lib/Analysis/OSLog.cpp create mode 100644 clang/test/CodeGenObjC/os_log.m diff --git a/clang/include/clang/Analysis/Analyses/FormatString.h b/clang/include/clang/Analysis/Analyses/FormatString.h index 74803a295d712..170cfad0ca3cf 100644 --- a/clang/include/clang/Analysis/Analyses/FormatString.h +++ b/clang/include/clang/Analysis/Analyses/FormatString.h @@ -35,7 +35,7 @@ class OptionalFlag { public: OptionalFlag(const char *Representation) : representation(Representation), flag(false) {} - bool isSet() { return flag; } + bool isSet() const { return flag; } void set() { flag = true; } void clear() { flag = false; } void setPosition(const char *position) { @@ -154,6 +154,11 @@ class ConversionSpecifier { CArg, SArg, + // Apple extension: P specifies to os_log that the data being pointed to is + // to be copied by os_log. The precision indicates the number of bytes to + // copy. + PArg, + // ** Printf-specific ** ZArg, // MS extension @@ -437,13 +442,15 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier { OptionalFlag HasAlternativeForm; // '#' OptionalFlag HasLeadingZeroes; // '0' OptionalFlag HasObjCTechnicalTerm; // '[tt]' + OptionalFlag IsPrivate; // '{private}' + OptionalFlag IsPublic; // '{public}' OptionalAmount Precision; public: PrintfSpecifier() : FormatSpecifier(/* isPrintf = */ true), HasThousandsGrouping("'"), IsLeftJustified("-"), HasPlusPrefix("+"), HasSpacePrefix(" "), HasAlternativeForm("#"), HasLeadingZeroes("0"), - HasObjCTechnicalTerm("tt") {} + HasObjCTechnicalTerm("tt"), IsPrivate("private"), IsPublic("public") {} static PrintfSpecifier Parse(const char *beg, const char *end); @@ -472,6 +479,12 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier { void setHasObjCTechnicalTerm(const char *position) { HasObjCTechnicalTerm.setPosition(position); } + void setIsPrivate(const char *position) { + IsPrivate.setPosition(position); + } + void setIsPublic(const char *position) { + IsPublic.setPosition(position); + } void setUsesPositionalArg() { UsesPositionalArg = true; } // Methods for querying the format specifier. @@ -509,6 +522,8 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier { const OptionalFlag &hasLeadingZeros() const { return HasLeadingZeroes; } const OptionalFlag &hasSpacePrefix() const { return HasSpacePrefix; } const OptionalFlag &hasObjCTechnicalTerm() const { return HasObjCTechnicalTerm; } + const OptionalFlag &isPrivate() const { return IsPrivate; } + const OptionalFlag &isPublic() const { return IsPublic; } bool usesPositionalArg() const { return UsesPositionalArg; } /// Changes the specifier and length according to a QualType, retaining any diff --git a/clang/include/clang/Analysis/Analyses/OSLog.h b/clang/include/clang/Analysis/Analyses/OSLog.h new file mode 100644 index 0000000000000..9fde26aaea4dd --- /dev/null +++ b/clang/include/clang/Analysis/Analyses/OSLog.h @@ -0,0 +1,146 @@ +//= OSLog.h - Analysis of calls to os_log builtins --*- C++ -*-===============// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines APIs for determining the layout of the data buffer for +// os_log() and os_trace(). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_OSLOG_H +#define LLVM_CLANG_ANALYSIS_ANALYSES_OSLOG_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" + +namespace clang { +namespace analyze_os_log { + +/// An OSLogBufferItem represents a single item in the data written by a call +/// to os_log() or os_trace(). +class OSLogBufferItem { +public: + enum Kind { + // The item is a scalar (int, float, raw pointer, etc.). No further copying + // is required. This is the only kind allowed by os_trace(). + ScalarKind = 0, + + // The item is a count, which describes the length of the following item to + // be copied. A count may only be followed by an item of kind StringKind or + // PointerKind. + CountKind, + + // The item is a pointer to a C string. If preceded by a count 'n', + // os_log() will copy at most 'n' bytes from the pointer. + StringKind, + + // The item is a pointer to a block of raw data. This item must be preceded + // by a count 'n'. os_log() will copy exactly 'n' bytes from the pointer. + PointerKind, + + // The item is a pointer to an Objective-C object. os_log() may retain the + // object for later processing. + ObjCObjKind + }; + + enum { + // The item is marked "private" in the format string. + IsPrivate = 0x1, + + // The item is marked "public" in the format string. + IsPublic = 0x2 + }; + +private: + Kind TheKind = ScalarKind; + const Expr *TheExpr = nullptr; + CharUnits ConstValue; + CharUnits Size; // size of the data, not including the header bytes + unsigned Flags = 0; + +public: + OSLogBufferItem(Kind kind, const Expr *expr, CharUnits size, unsigned flags) + : TheKind(kind), TheExpr(expr), Size(size), Flags(flags) {} + + OSLogBufferItem(ASTContext &Ctx, CharUnits value, unsigned flags) + : TheKind(CountKind), ConstValue(value), + Size(Ctx.getTypeSizeInChars(Ctx.IntTy)), Flags(flags) {} + + unsigned char getDescriptorByte() const { + unsigned char result = 0; + if (getIsPrivate()) result |= 0x01; + if (getIsPublic()) result |= 0x02; + result |= ((unsigned)getKind()) << 4; + return result; + } + + unsigned char getSizeByte() const { + return getSize().getQuantity(); + } + + Kind getKind() const { return TheKind; } + bool getIsPrivate() const { return (Flags & IsPrivate) != 0; } + bool getIsPublic() const { return (Flags & IsPublic) != 0; } + + const Expr *getExpr() const { return TheExpr; } + CharUnits getConstValue() const { return ConstValue; } + CharUnits getSize() const { return Size; } +}; + +class OSLogBufferLayout { +public: + SmallVector Items; + + CharUnits getSize() const { + CharUnits result; + result += CharUnits::fromQuantity(2); // summary byte, num-args byte + for (auto &item : Items) { + // descriptor byte, size byte + result += item.getSize() + CharUnits::fromQuantity(2); + } + return result; + } + + bool getHasPrivateItems() const { + return std::any_of(Items.begin(), Items.end(), + [](const OSLogBufferItem &item) { return item.getIsPrivate(); }); + } + + bool getHasPublicItems() const { + return std::any_of(Items.begin(), Items.end(), + [](const OSLogBufferItem &item) { return item.getIsPublic(); }); + } + + bool getHasNonScalar() const { + return std::any_of(Items.begin(), Items.end(), + [](const OSLogBufferItem &item) { + return item.getKind() != OSLogBufferItem::ScalarKind; + }); + } + + unsigned char getSummaryByte() const { + unsigned char result = 0; + if (getHasPrivateItems()) result |= 0x01; + if (getHasNonScalar()) result |= 0x02; + return result; + } + + unsigned char getNumArgsByte() const { + return Items.size(); + } +}; + +// Given a call 'E' to one of the builtins __builtin_os_log_format() or +// __builtin_os_log_format_buffer_size(), compute the layout of the buffer that +// the call will write into and store it in 'layout'. Returns 'false' if there +// was some error encountered while computing the layout, and 'true' otherwise. +bool computeOSLogBufferLayout(clang::ASTContext &Ctx, const clang::CallExpr *E, OSLogBufferLayout &layout); + +} // namespace analyze_os_log +} // namespace clang +#endif diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def index 532fa8f755dfe..e99aed5760d66 100644 --- a/clang/include/clang/Basic/Builtins.def +++ b/clang/include/clang/Basic/Builtins.def @@ -1279,6 +1279,9 @@ BUILTIN(__builtin___get_unsafe_stack_ptr, "v*", "Fn") BUILTIN(__builtin_nontemporal_store, "v.", "t") BUILTIN(__builtin_nontemporal_load, "v.", "t") +// Builtins for os_log/os_trace +BUILTIN(__builtin_os_log_format_buffer_size, "zcC*.", "p:0:nut") +BUILTIN(__builtin_os_log_format, "v*v*cC*.", "p:0:nt") // OpenCL v2.0 s6.13.16, s9.17.3.5 - Pipe functions. // We need the generic prototype, since the packet type could be anything. LANGBUILTIN(read_pipe, "i.", "tn", OCLC_LANG) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 86fd60b1c4819..3b00079edbeb1 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7300,6 +7300,12 @@ def warn_format_non_standard: Warning< def warn_format_non_standard_conversion_spec: Warning< "using length modifier '%0' with conversion specifier '%1' is not supported by ISO C">, InGroup, DefaultIgnore; +def warn_format_invalid_annotation : Warning< + "using '%0' format specifier annotation outside of os_log()/os_trace()">, + InGroup; +def warn_format_P_no_precision : Warning< + "using '%%P' format specifier without precision">, + InGroup; def warn_printf_ignored_flag: Warning< "flag '%0' is ignored when flag '%1' is present">, InGroup; @@ -7436,6 +7442,13 @@ def warn_cfstring_truncated : Warning< "belong to the input codeset UTF-8">, InGroup>; +// os_log checking +// TODO: separate diagnostic for os_trace() +def err_os_log_format_not_string_constant : Error< + "os_log() format argument is not a string constant">; +def err_os_log_argument_too_big : Error< + "os_log() argument %d is too big (%d bytes, max %d)">; + // Statements. def err_continue_not_in_loop : Error< "'continue' statement not in loop statement">; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 78e84f0dc6e64..ab1be9e559529 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -9294,6 +9294,7 @@ class Sema { VariadicCallType CallType); bool CheckObjCString(Expr *Arg); + ExprResult CheckOSLogFormatStringArg(Expr *Arg); ExprResult CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, CallExpr *TheCall); @@ -9315,6 +9316,7 @@ class Sema { bool SemaBuiltinVAStartARM(CallExpr *Call); bool SemaBuiltinUnorderedCompare(CallExpr *TheCall); bool SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs); + bool SemaBuiltinOSLogFormat(CallExpr *TheCall); public: // Used by C++ template instantiation. @@ -9350,6 +9352,7 @@ class Sema { FST_Kprintf, FST_FreeBSDKPrintf, FST_OSTrace, + FST_OSLog, FST_Unknown }; static FormatStringType GetFormatStringType(const FormatAttr *Format); diff --git a/clang/lib/Analysis/CMakeLists.txt b/clang/lib/Analysis/CMakeLists.txt index 1df093d850983..a3990d6de3ecf 100644 --- a/clang/lib/Analysis/CMakeLists.txt +++ b/clang/lib/Analysis/CMakeLists.txt @@ -15,6 +15,7 @@ add_clang_library(clangAnalysis Dominators.cpp FormatString.cpp LiveVariables.cpp + OSLog.cpp ObjCNoReturn.cpp PostOrderCFGView.cpp PrintfFormatString.cpp diff --git a/clang/lib/Analysis/FormatString.cpp b/clang/lib/Analysis/FormatString.cpp index 83d08b55427fd..a0a357d0a8a58 100644 --- a/clang/lib/Analysis/FormatString.cpp +++ b/clang/lib/Analysis/FormatString.cpp @@ -579,6 +579,7 @@ const char *ConversionSpecifier::toString() const { case cArg: return "c"; case sArg: return "s"; case pArg: return "p"; + case PArg: return "P"; case nArg: return "n"; case PercentArg: return "%"; case ScanListArg: return "["; @@ -854,6 +855,7 @@ bool FormatSpecifier::hasStandardConversionSpecifier( case ConversionSpecifier::ObjCObjArg: case ConversionSpecifier::ScanListArg: case ConversionSpecifier::PercentArg: + case ConversionSpecifier::PArg: return true; case ConversionSpecifier::CArg: case ConversionSpecifier::SArg: diff --git a/clang/lib/Analysis/OSLog.cpp b/clang/lib/Analysis/OSLog.cpp new file mode 100644 index 0000000000000..804cee2c88929 --- /dev/null +++ b/clang/lib/Analysis/OSLog.cpp @@ -0,0 +1,153 @@ +// TODO: header template + +#include "clang/Analysis/Analyses/OSLog.h" +#include "clang/Analysis/Analyses/FormatString.h" +#include "clang/Basic/Builtins.h" +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprObjC.h" +#include "llvm/ADT/SmallBitVector.h" + +using namespace clang; +using llvm::APInt; + +using clang::analyze_os_log::OSLogBufferItem; +using clang::analyze_os_log::OSLogBufferLayout; + +class OSLogFormatStringHandler + : public analyze_format_string::FormatStringHandler { +private: + ArrayRef Args; + SmallVector, 4> ArgKind; + SmallVector, 4> ArgSize; + SmallVector ArgFlags; + +public: + OSLogFormatStringHandler(ArrayRef args) + : FormatStringHandler(), Args(args), ArgKind(args.size(), None), + ArgSize(args.size(), None), ArgFlags(args.size(), 0) + {} + + virtual bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS, + const char *startSpecifier, + unsigned specifierLen) { + + // Cases to handle: + // * "%f", "%d"... scalar (assumed for anything that doesn't fit the below + // cases) + // * "%s" pointer to null-terminated string + // * "%.*s" strlen (arg), pointer to string + // * "%.16s" strlen (non-arg), pointer to string + // * "%.*P" len (arg), pointer to data + // * "%.16P" len (non-arg), pointer to data + // * "%@" pointer to objc object + + unsigned argIndex = FS.getArgIndex(); + if (argIndex >= Args.size()) { + return false; + } + switch (FS.getConversionSpecifier().getKind()) { + case clang::analyze_format_string::ConversionSpecifier::sArg: { // "%s" + ArgKind[argIndex] = OSLogBufferItem::StringKind; + auto &precision = FS.getPrecision(); + switch (precision.getHowSpecified()) { + case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%s" + break; + case clang::analyze_format_string::OptionalAmount::Constant: // "%.16s" + ArgSize[argIndex] = precision.getConstantAmount(); + break; + case clang::analyze_format_string::OptionalAmount::Arg: // "%.*s" + ArgKind[precision.getArgIndex()] = OSLogBufferItem::CountKind; + break; + case clang::analyze_format_string::OptionalAmount::Invalid: + return false; + } + break; + } + case clang::analyze_format_string::ConversionSpecifier::PArg: { // "%P" + ArgKind[argIndex] = OSLogBufferItem::PointerKind; + auto &precision = FS.getPrecision(); + switch (precision.getHowSpecified()) { + case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%P" + return false; // length must be supplied with pointer format specifier + case clang::analyze_format_string::OptionalAmount::Constant: // "%.16P" + ArgSize[argIndex] = precision.getConstantAmount(); + break; + case clang::analyze_format_string::OptionalAmount::Arg: // "%.*P" + ArgKind[precision.getArgIndex()] = OSLogBufferItem::CountKind; + break; + case clang::analyze_format_string::OptionalAmount::Invalid: + return false; + } + break; + } + case clang::analyze_format_string::ConversionSpecifier::ObjCObjArg: // "%@" + ArgKind[argIndex] = OSLogBufferItem::ObjCObjKind; + break; + default: + ArgKind[argIndex] = OSLogBufferItem::ScalarKind; + break; + } + + if (FS.isPrivate()) { + ArgFlags[argIndex] |= OSLogBufferItem::IsPrivate; + } + if (FS.isPublic()) { + ArgFlags[argIndex] |= OSLogBufferItem::IsPublic; + } + return true; + } + + void computeLayout(ASTContext &Ctx, OSLogBufferLayout &layout) const { + layout.Items.clear(); + for (unsigned i = 0; i < Args.size(); i++) { + const Expr *arg = Args[i]; + if (ArgSize[i]) { + layout.Items.emplace_back(Ctx, CharUnits::fromQuantity(*ArgSize[i]), + ArgFlags[i]); + } + CharUnits size = Ctx.getTypeSizeInChars(arg->getType()); + if (ArgKind[i]) { + layout.Items.emplace_back(*ArgKind[i], arg, size, ArgFlags[i]); + } else { + layout.Items.emplace_back(OSLogBufferItem::ScalarKind, arg, size, + ArgFlags[i]); + } + } + } +}; + +bool clang::analyze_os_log::computeOSLogBufferLayout(ASTContext &Ctx, const CallExpr *E, OSLogBufferLayout &layout) +{ + ArrayRef Args(E->getArgs(), E->getArgs() + E->getNumArgs()); + + const Expr *StringArg; + ArrayRef VarArgs; + switch (E->getBuiltinCallee()) { + case Builtin::BI__builtin_os_log_format_buffer_size: + assert(E->getNumArgs() >= 1 && + "__builtin_os_log_format_buffer_size takes at least 1 argument"); + StringArg = E->getArg(0); + VarArgs = Args.slice(1); + break; + case Builtin::BI__builtin_os_log_format: + assert(E->getNumArgs() >= 2 && + "__builtin_os_log_format takes at least 2 arguments"); + StringArg = E->getArg(1); + VarArgs = Args.slice(2); + break; + default: + llvm_unreachable("non-os_log builtin passed to computeOSLogBufferLayout"); + } + + const StringLiteral *Lit = cast(StringArg->IgnoreParenCasts()); + assert(Lit && (Lit->isAscii() || Lit->isUTF8())); + StringRef data = Lit->getString(); + OSLogFormatStringHandler H(VarArgs); + ParsePrintfString(H, data.begin(), data.end(), Ctx.getLangOpts(), + Ctx.getTargetInfo(), /*isFreeBSDKPrintf*/false); + + H.computeLayout(Ctx, layout); + return true; +} diff --git a/clang/lib/Analysis/PrintfFormatString.cpp b/clang/lib/Analysis/PrintfFormatString.cpp index ac6cef9d08420..1966344254a2b 100644 --- a/clang/lib/Analysis/PrintfFormatString.cpp +++ b/clang/lib/Analysis/PrintfFormatString.cpp @@ -119,6 +119,35 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H, return true; } + const char *OSLogVisibilityFlagsStart = nullptr, + *OSLogVisibilityFlagsEnd = nullptr; + if (*I == '{') { + OSLogVisibilityFlagsStart = I++; + // Find the end of the modifier. + while (I != E && *I != '}') { I++; } + if (I == E) { + if (Warn) + H.HandleIncompleteSpecifier(Start, E - Start); + return true; + } + assert(*I == '}'); + OSLogVisibilityFlagsEnd = I++; + + // Just see if 'private' or 'public' is the first word. os_log itself will + // do any further parsing. + const char *P = OSLogVisibilityFlagsStart + 1; + while (P < OSLogVisibilityFlagsEnd && isspace(*P)) P++; + const char *WordStart = P; + while (P < OSLogVisibilityFlagsEnd && (isalnum(*P) || *P == '_')) P++; + const char *WordEnd = P; + StringRef Word(WordStart, WordEnd - WordStart); + if (Word == "private") { + FS.setIsPrivate(WordStart); + } else if (Word == "public") { + FS.setIsPublic(WordStart); + } + } + // Look for flags (if any). bool hasMore = true; for ( ; I != E; ++I) { @@ -253,6 +282,8 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H, // POSIX specific. case 'C': k = ConversionSpecifier::CArg; break; case 'S': k = ConversionSpecifier::SArg; break; + // Apple extension for os_log + case 'P': k = ConversionSpecifier::PArg; break; // Objective-C. case '@': k = ConversionSpecifier::ObjCObjArg; break; // Glibc specific. @@ -301,7 +332,7 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H, conversionPosition); return true; } - + PrintfConversionSpecifier CS(conversionPosition, k); FS.setConversionSpecifier(CS); if (CS.consumesDataArgument() && !FS.usesPositionalArg()) @@ -541,6 +572,7 @@ ArgType PrintfSpecifier::getArgType(ASTContext &Ctx, return Ctx.IntTy; return ArgType(Ctx.WideCharTy, "wchar_t"); case ConversionSpecifier::pArg: + case ConversionSpecifier::PArg: return ArgType::CPointerTy; case ConversionSpecifier::ObjCObjArg: return ArgType::ObjCPointerTy; @@ -900,7 +932,7 @@ bool PrintfSpecifier::hasValidPrecision() const { if (Precision.getHowSpecified() == OptionalAmount::NotSpecified) return true; - // Precision is only valid with the diouxXaAeEfFgGs conversions + // Precision is only valid with the diouxXaAeEfFgGsP conversions switch (CS.getKind()) { case ConversionSpecifier::dArg: case ConversionSpecifier::DArg: @@ -922,6 +954,7 @@ bool PrintfSpecifier::hasValidPrecision() const { case ConversionSpecifier::sArg: case ConversionSpecifier::FreeBSDrArg: case ConversionSpecifier::FreeBSDyArg: + case ConversionSpecifier::PArg: return true; default: diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index bf450b9ec2336..b997747c02062 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -18,6 +18,7 @@ #include "TargetInfo.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/Analysis/Analyses/OSLog.h" #include "clang/Basic/TargetBuiltins.h" #include "clang/Basic/TargetInfo.h" #include "clang/CodeGen/CGFunctionInfo.h" @@ -439,6 +440,17 @@ CodeGenFunction::emitBuiltinObjectSize(const Expr *E, unsigned Type, return Builder.CreateCall(F, {EmitScalarExpr(E), CI}); } +namespace { + struct CallObjCArcUse final : EHScopeStack::Cleanup { + CallObjCArcUse(llvm::Value *object) : object(object) {} + llvm::Value *object; + + void Emit(CodeGenFunction &CGF, Flags flags) override { + CGF.EmitARCIntrinsicUse(object); + } + }; +} + RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue) { @@ -1991,6 +2003,76 @@ RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD, return RValue::get(llvm::ConstantExpr::getBitCast(GV, CGM.Int8PtrTy)); break; } + case Builtin::BI__builtin_os_log_format: { + assert(E->getNumArgs() >= 2 && + "__builtin_os_log_format takes at least 2 arguments"); + analyze_os_log::OSLogBufferLayout Layout; + analyze_os_log::computeOSLogBufferLayout(CGM.getContext(), E, Layout); + Address BufAddr = EmitPointerWithAlignment(E->getArg(0)); + // Ignore argument 1, the format string. It is not currently used. + CharUnits offset; + Builder.CreateStore( + Builder.getInt8(Layout.getSummaryByte()), + Builder.CreateConstByteGEP(BufAddr, offset++, "summary")); + Builder.CreateStore( + Builder.getInt8(Layout.getNumArgsByte()), + Builder.CreateConstByteGEP(BufAddr, offset++, "numArgs")); + + llvm::SmallVector RetainableOperands; + for (const auto &item : Layout.Items) { + Builder.CreateStore( + Builder.getInt8(item.getDescriptorByte()), + Builder.CreateConstByteGEP(BufAddr, offset++, "argDescriptor")); + Builder.CreateStore( + Builder.getInt8(item.getSizeByte()), + Builder.CreateConstByteGEP(BufAddr, offset++, "argSize")); + Address addr = Builder.CreateConstByteGEP(BufAddr, offset); + if (const Expr *expr = item.getExpr()) { + addr = Builder.CreateElementBitCast(addr, + ConvertTypeForMem(expr->getType())); + // Check if this is a retainable type. + if (expr->getType()->isObjCRetainableType()) { + assert(getEvaluationKind(expr->getType()) == TEK_Scalar && + "Only scalar can be a ObjC retainable type"); + llvm::Value *SV = EmitScalarExpr(expr, /*Ignore*/ false); + RValue RV = RValue::get(SV); + LValue LV = MakeAddrLValue(addr, expr->getType()); + EmitStoreThroughLValue(RV, LV); + // Check if the object is constant, if not, save it in + // RetainableOperands. + if (!isa(SV)) + RetainableOperands.push_back(SV); + } else { + EmitAnyExprToMem(expr, addr, Qualifiers(), /*isInit*/true); + } + } else { + addr = Builder.CreateElementBitCast(addr, Int32Ty); + Builder.CreateStore( + Builder.getInt32(item.getConstValue().getQuantity()), addr); + } + offset += item.getSize(); + } + + // Push a clang.arc.use cleanup for each object in RetainableOperands. The + // cleanup will cause the use to appear after the final log call, keeping + // the object valid while it’s held in the log buffer. Note that if there’s + // a release cleanup on the object, it will already be active; since + // cleanups are emitted in reverse order, the use will occur before the + // object is released. + if (!RetainableOperands.empty() && getLangOpts().ObjCAutoRefCount && + CGM.getCodeGenOpts().OptimizationLevel != 0) + for (llvm::Value *object : RetainableOperands) + pushFullExprCleanup(getARCCleanupKind(), object); + + return RValue::get(BufAddr.getPointer()); + } + + case Builtin::BI__builtin_os_log_format_buffer_size: { + analyze_os_log::OSLogBufferLayout Layout; + analyze_os_log::computeOSLogBufferLayout(CGM.getContext(), E, Layout); + return RValue::get(ConstantInt::get(ConvertType(E->getType()), + Layout.getSize().getQuantity())); + } // OpenCL v2.0 s6.13.16.2, Built-in pipe read and write functions case Builtin::BIread_pipe: diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 6bd910ed586a1..76929dd5c2b77 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -835,6 +835,12 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, if (SemaBuiltinPipePackets(*this, TheCall)) return ExprError(); break; + case Builtin::BI__builtin_os_log_format: + case Builtin::BI__builtin_os_log_format_buffer_size: + if (SemaBuiltinOSLogFormat(TheCall)) { + return ExprError(); + } + break; case Builtin::BIto_global: case Builtin::BIto_local: case Builtin::BIto_private: @@ -2904,6 +2910,31 @@ bool Sema::CheckObjCString(Expr *Arg) { return false; } +/// CheckObjCString - Checks that the format string argument to the os_log() +/// and os_trace() functions is correct, and converts it to const char *. +ExprResult Sema::CheckOSLogFormatStringArg(Expr *Arg) { + Arg = Arg->IgnoreParenCasts(); + StringLiteral *Literal = dyn_cast(Arg); + if (!Literal) { + if (auto *ObjcLiteral = dyn_cast(Arg)) { + Literal = ObjcLiteral->getString(); + } + } + + if (!Literal || (!Literal->isAscii() && !Literal->isUTF8())) { + return ExprError( + Diag(Arg->getLocStart(), diag::err_os_log_format_not_string_constant) + << Arg->getSourceRange()); + } + + ExprResult Result(Literal); + QualType ResultTy = Context.getPointerType(Context.CharTy.withConst()); + InitializedEntity Entity = InitializedEntity::InitializeParameter(Context, + ResultTy, false); + Result = PerformCopyInitialization(Entity, SourceLocation(), Result); + return Result; +} + /// Check the arguments to '__builtin_va_start' or '__builtin_ms_va_start' /// for validity. Emit an error and return true on failure; return false /// on success. @@ -3356,6 +3387,86 @@ bool Sema::SemaBuiltinAssumeAligned(CallExpr *TheCall) { return false; } +bool Sema::SemaBuiltinOSLogFormat(CallExpr *TheCall) { + unsigned BuiltinID = + cast(TheCall->getCalleeDecl())->getBuiltinID(); + bool IsSizeCall = BuiltinID == Builtin::BI__builtin_os_log_format_buffer_size; + + unsigned NumArgs = TheCall->getNumArgs(); + unsigned NumRequiredArgs = IsSizeCall ? 1 : 2; + if (NumArgs < NumRequiredArgs) { + return Diag(TheCall->getLocEnd(), + diag::err_typecheck_call_too_few_args) + << 0 /* function call */ << NumRequiredArgs << NumArgs + << TheCall->getSourceRange(); + } + if (NumArgs >= NumRequiredArgs + 0x100) { + return Diag(TheCall->getLocEnd(), + diag::err_typecheck_call_too_many_args_at_most) + << 0 /* function call */ << (NumRequiredArgs + 0xff) + << NumArgs << TheCall->getSourceRange(); + } + unsigned i = 0; + + // For formatting call, check buffer arg. + if (!IsSizeCall) { + ExprResult Arg(TheCall->getArg(i)); + InitializedEntity Entity = + InitializedEntity::InitializeParameter(Context, Context.VoidPtrTy, false); + Arg = PerformCopyInitialization(Entity, SourceLocation(), Arg); + if (Arg.isInvalid()) return true; + TheCall->setArg(i, Arg.get()); + i++; + } + + // Check string literal arg. + unsigned FormatIdx = i; + { + ExprResult Arg = CheckOSLogFormatStringArg(TheCall->getArg(i)); + if (Arg.isInvalid()) return true; + TheCall->setArg(i, Arg.get()); + i++; + } + + // Make sure variadic args are scalar. + unsigned FirstDataArg = i; + while (i < NumArgs) { + ExprResult Arg = DefaultVariadicArgumentPromotion(TheCall->getArg(i), + VariadicFunction, + nullptr); + if (Arg.isInvalid()) return true; + CharUnits ArgSize = Context.getTypeSizeInChars(Arg.get()->getType()); + if (ArgSize.getQuantity() >= 0x100) { + return Diag(Arg.get()->getLocEnd(), + diag::err_os_log_argument_too_big) + << i << (int)ArgSize.getQuantity() << 0xff + << TheCall->getSourceRange(); + } + TheCall->setArg(i, Arg.get()); + i++; + } + + // Check formatting specifiers. NOTE: We're only doing this for the non-size + // call to avoid duplicate diagnostics. + if (!IsSizeCall) { + llvm::SmallBitVector CheckedVarArgs(NumArgs, false); + ArrayRef Args(TheCall->getArgs(), TheCall->getNumArgs()); + bool Success = CheckFormatArguments(Args, /*HasVAListArg*/false, FormatIdx, + FirstDataArg, FST_OSLog, + VariadicFunction, + TheCall->getLocStart(), SourceRange(), + CheckedVarArgs); + if (!Success) return true; + } + + if (IsSizeCall) { + TheCall->setType(Context.getSizeType()); + } else { + TheCall->setType(Context.VoidPtrTy); + } + return false; +} + /// SemaBuiltinConstantArg - Handle a check if argument ArgNum of CallExpr /// TheCall is a constant expression. bool Sema::SemaBuiltinConstantArg(CallExpr *TheCall, int ArgNum, @@ -3811,7 +3922,8 @@ Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) { .Case("strfmon", FST_Strfmon) .Cases("kprintf", "cmn_err", "vcmn_err", "zcmn_err", FST_Kprintf) .Case("freebsd_kprintf", FST_FreeBSDKPrintf) - .Case("os_trace", FST_OSTrace) + .Case("os_trace", FST_OSLog) + .Case("os_log", FST_OSLog) .Default(FST_Unknown); } @@ -3921,6 +4033,7 @@ class CheckFormatHandler : public analyze_format_string::FormatStringHandler { Sema &S; const StringLiteral *FExpr; const Expr *OrigFormatExpr; + const Sema::FormatStringType FSType; const unsigned FirstDataArg; const unsigned NumDataArgs; const char *Beg; // Start of format string. @@ -3937,14 +4050,14 @@ class CheckFormatHandler : public analyze_format_string::FormatStringHandler { public: CheckFormatHandler(Sema &s, const StringLiteral *fexpr, - const Expr *origFormatExpr, unsigned firstDataArg, - unsigned numDataArgs, const char *beg, bool hasVAListArg, - ArrayRef Args, + const Expr *origFormatExpr, const Sema::FormatStringType + type, unsigned firstDataArg, unsigned numDataArgs, const + char *beg, bool hasVAListArg, ArrayRef Args, unsigned formatIdx, bool inFunctionCall, Sema::VariadicCallType callType, llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg) - : S(s), FExpr(fexpr), OrigFormatExpr(origFormatExpr), + : S(s), FExpr(fexpr), OrigFormatExpr(origFormatExpr), FSType(type), FirstDataArg(firstDataArg), NumDataArgs(numDataArgs), Beg(beg), HasVAListArg(hasVAListArg), Args(Args), FormatIdx(formatIdx), @@ -4376,25 +4489,32 @@ void CheckFormatHandler::EmitFormatDiagnostic(Sema &S, bool InFunctionCall, namespace { class CheckPrintfHandler : public CheckFormatHandler { - bool ObjCContext; - public: CheckPrintfHandler(Sema &s, const StringLiteral *fexpr, - const Expr *origFormatExpr, unsigned firstDataArg, - unsigned numDataArgs, bool isObjC, + const Expr *origFormatExpr, const Sema::FormatStringType type, + unsigned firstDataArg, unsigned numDataArgs, const char *beg, bool hasVAListArg, ArrayRef Args, unsigned formatIdx, bool inFunctionCall, Sema::VariadicCallType CallType, llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg) - : CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg, + : CheckFormatHandler(s, fexpr, origFormatExpr, type, firstDataArg, numDataArgs, beg, hasVAListArg, Args, formatIdx, inFunctionCall, CallType, CheckedVarArgs, - UncoveredArg), - ObjCContext(isObjC) + UncoveredArg) {} + bool isObjCContext() const { + return FSType == Sema::FST_NSString; + } + + /// Returns true if '%@' specifiers are allowed in the format string. + bool allowsObjCArg() const { + return FSType == Sema::FST_NSString || FSType == Sema::FST_OSLog || + FSType == Sema::FST_OSTrace; + } + bool HandleInvalidPrintfConversionSpecifier( const analyze_printf::PrintfSpecifier &FS, const char *startSpecifier, @@ -4747,11 +4867,45 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier // Check for using an Objective-C specific conversion specifier // in a non-ObjC literal. - if (!ObjCContext && CS.isObjCArg()) { + if (!allowsObjCArg() && CS.isObjCArg()) { + return HandleInvalidPrintfConversionSpecifier(FS, startSpecifier, + specifierLen); + } + + // %P can only be used with os_log. + if (FSType != Sema::FST_OSLog && + CS.getKind() == ConversionSpecifier::PArg) { return HandleInvalidPrintfConversionSpecifier(FS, startSpecifier, specifierLen); } + // Only scalars are allowed for os_trace. + if (FSType == Sema::FST_OSTrace && + (CS.getKind() == ConversionSpecifier::PArg || + CS.getKind() == ConversionSpecifier::sArg || + CS.getKind() == ConversionSpecifier::ObjCObjArg)) { + return HandleInvalidPrintfConversionSpecifier(FS, startSpecifier, + specifierLen); + } + + // Check for use of public/private annotation outside of os_log(). + if (FSType != Sema::FST_OSLog) { + if (FS.isPublic().isSet()) { + EmitFormatDiagnostic( + S.PDiag(diag::warn_format_invalid_annotation) << "public", + getLocationOfByte(FS.isPublic().getPosition()), + /*IsStringLocation*/false, + getSpecifierRange(startSpecifier, specifierLen)); + } + if (FS.isPrivate().isSet()) { + EmitFormatDiagnostic( + S.PDiag(diag::warn_format_invalid_annotation) << "private", + getLocationOfByte(FS.isPrivate().getPosition()), + /*IsStringLocation*/false, + getSpecifierRange(startSpecifier, specifierLen)); + } + } + // Check for invalid use of field width if (!FS.hasValidFieldWidth()) { HandleInvalidAmount(FS, FS.getFieldWidth(), /* field width */ 0, @@ -4764,6 +4918,15 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier startSpecifier, specifierLen); } + // Precision is mandatory for %P specifier. + if (CS.getKind() == ConversionSpecifier::PArg && + FS.getPrecision().getHowSpecified() == OptionalAmount::NotSpecified) { + EmitFormatDiagnostic( + S.PDiag(diag::warn_format_P_no_precision), + getLocationOfByte(startSpecifier), /*IsStringLocation*/false, + getSpecifierRange(startSpecifier, specifierLen)); + } + // Check each flag does not conflict with any other component. if (!FS.hasValidThousandsGroupingPrefix()) HandleFlag(FS, FS.hasThousandsGrouping(), startSpecifier, specifierLen); @@ -4914,7 +5077,7 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS, // Now type check the data expression that matches the // format specifier. const analyze_printf::ArgType &AT = FS.getArgType(S.Context, - ObjCContext); + isObjCContext()); if (!AT.isValid()) return true; @@ -4969,7 +5132,7 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS, // If the argument is an integer of some kind, believe the %C and suggest // a cast instead of changing the conversion specifier. QualType IntendedTy = ExprTy; - if (ObjCContext && + if (isObjCContext() && FS.getConversionSpecifier().getKind() == ConversionSpecifier::CArg) { if (ExprTy->isIntegralOrUnscopedEnumerationType() && !ExprTy->isCharType()) { @@ -5011,7 +5174,7 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS, // We may be able to offer a FixItHint if it is a supported type. PrintfSpecifier fixedFS = FS; bool success = fixedFS.fixType(IntendedTy, S.getLangOpts(), - S.Context, ObjCContext); + S.Context, isObjCContext()); if (success) { // Get the fix string from the fixed format specifier @@ -5167,14 +5330,14 @@ namespace { class CheckScanfHandler : public CheckFormatHandler { public: CheckScanfHandler(Sema &s, const StringLiteral *fexpr, - const Expr *origFormatExpr, unsigned firstDataArg, - unsigned numDataArgs, const char *beg, bool hasVAListArg, - ArrayRef Args, + const Expr *origFormatExpr, Sema::FormatStringType type, + unsigned firstDataArg, unsigned numDataArgs, const char + *beg, bool hasVAListArg, ArrayRef Args, unsigned formatIdx, bool inFunctionCall, Sema::VariadicCallType CallType, llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg) - : CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg, + : CheckFormatHandler(s, fexpr, origFormatExpr, type, firstDataArg, numDataArgs, beg, hasVAListArg, Args, formatIdx, inFunctionCall, CallType, CheckedVarArgs, UncoveredArg) @@ -5389,11 +5552,10 @@ static void CheckFormatString(Sema &S, const StringLiteral *FExpr, } if (Type == Sema::FST_Printf || Type == Sema::FST_NSString || - Type == Sema::FST_FreeBSDKPrintf || Type == Sema::FST_OSTrace) { - CheckPrintfHandler H(S, FExpr, OrigFormatExpr, firstDataArg, - numDataArgs, (Type == Sema::FST_NSString || - Type == Sema::FST_OSTrace), - Str, HasVAListArg, Args, format_idx, + Type == Sema::FST_FreeBSDKPrintf || Type == Sema::FST_OSLog || + Type == Sema::FST_OSTrace) { + CheckPrintfHandler H(S, FExpr, OrigFormatExpr, Type, firstDataArg, + numDataArgs, Str, HasVAListArg, Args, format_idx, inFunctionCall, CallType, CheckedVarArgs, UncoveredArg); @@ -5403,7 +5565,7 @@ static void CheckFormatString(Sema &S, const StringLiteral *FExpr, Type == Sema::FST_FreeBSDKPrintf)) H.DoneProcessing(); } else if (Type == Sema::FST_Scanf) { - CheckScanfHandler H(S, FExpr, OrigFormatExpr, firstDataArg, numDataArgs, + CheckScanfHandler H(S, FExpr, OrigFormatExpr, Type, firstDataArg, numDataArgs, Str, HasVAListArg, Args, format_idx, inFunctionCall, CallType, CheckedVarArgs, UncoveredArg); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index afe36f69b63d4..9ce607cd7c168 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2842,6 +2842,7 @@ static FormatAttrKind getFormatAttrKind(StringRef Format) { .Case("kprintf", SupportedFormat) // OpenBSD. .Case("freebsd_kprintf", SupportedFormat) // FreeBSD. .Case("os_trace", SupportedFormat) + .Case("os_log", SupportedFormat) .Cases("gcc_diag", "gcc_cdiag", "gcc_cxxdiag", "gcc_tdiag", IgnoredFormat) .Default(InvalidFormat); diff --git a/clang/test/CodeGen/builtins.c b/clang/test/CodeGen/builtins.c index 43ce8f13ade60..a0681516a1a6e 100644 --- a/clang/test/CodeGen/builtins.c +++ b/clang/test/CodeGen/builtins.c @@ -275,3 +275,62 @@ long long test_builtin_readcyclecounter() { // CHECK: call i64 @llvm.readcyclecounter() return __builtin_readcyclecounter(); } + +// Behavior of __builtin_os_log differs between platforms, so only test on X86 +#ifdef __x86_64__ +// CHECK-LABEL: define void @test_builtin_os_log +// CHECK: (i8* [[BUF:%.*]], i32 [[I:%.*]], i8* [[DATA:%.*]]) +void test_builtin_os_log(void *buf, int i, const char *data) { + volatile int len; + // CHECK: store i8* [[BUF]], i8** [[BUF_ADDR:%.*]], align 8 + // CHECK: store i32 [[I]], i32* [[I_ADDR:%.*]], align 4 + // CHECK: store i8* [[DATA]], i8** [[DATA_ADDR:%.*]], align 8 + + // CHECK: store volatile i32 34 + len = __builtin_os_log_format_buffer_size("%d %{public}s %{private}.16P", i, data, data); + + // CHECK: [[BUF2:%.*]] = load i8*, i8** [[BUF_ADDR]] + // CHECK: [[SUMMARY:%.*]] = getelementptr i8, i8* [[BUF2]], i64 0 + // CHECK: store i8 3, i8* [[SUMMARY]] + // CHECK: [[NUM_ARGS:%.*]] = getelementptr i8, i8* [[BUF2]], i64 1 + // CHECK: store i8 4, i8* [[NUM_ARGS]] + // + // CHECK: [[ARG1_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 2 + // CHECK: store i8 0, i8* [[ARG1_DESC]] + // CHECK: [[ARG1_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 3 + // CHECK: store i8 4, i8* [[ARG1_SIZE]] + // CHECK: [[ARG1:%.*]] = getelementptr i8, i8* [[BUF2]], i64 4 + // CHECK: [[ARG1_INT:%.*]] = bitcast i8* [[ARG1]] to i32* + // CHECK: [[I2:%.*]] = load i32, i32* [[I_ADDR]] + // CHECK: store i32 [[I2]], i32* [[ARG1_INT]] + + // CHECK: [[ARG2_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 8 + // CHECK: store i8 34, i8* [[ARG2_DESC]] + // CHECK: [[ARG2_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 9 + // CHECK: store i8 8, i8* [[ARG2_SIZE]] + // CHECK: [[ARG2:%.*]] = getelementptr i8, i8* [[BUF2]], i64 10 + // CHECK: [[ARG2_PTR:%.*]] = bitcast i8* [[ARG2]] to i8** + // CHECK: [[DATA2:%.*]] = load i8*, i8** [[DATA_ADDR]] + // CHECK: store i8* [[DATA2]], i8** [[ARG2_PTR]] + + // CHECK: [[ARG3_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 18 + // CHECK: store i8 17, i8* [[ARG3_DESC]] + // CHECK: [[ARG3_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 19 + // CHECK: store i8 4, i8* [[ARG3_SIZE]] + // CHECK: [[ARG3:%.*]] = getelementptr i8, i8* [[BUF2]], i64 20 + // CHECK: [[ARG3_INT:%.*]] = bitcast i8* [[ARG3]] to i32* + // CHECK: store i32 16, i32* [[ARG3_INT]] + + // CHECK: [[ARG4_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 24 + // CHECK: store i8 49, i8* [[ARG4_DESC]] + // CHECK: [[ARG4_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 25 + // CHECK: store i8 8, i8* [[ARG4_SIZE]] + // CHECK: [[ARG4:%.*]] = getelementptr i8, i8* [[BUF2]], i64 26 + // CHECK: [[ARG4_PTR:%.*]] = bitcast i8* [[ARG4]] to i8** + // CHECK: [[DATA3:%.*]] = load i8*, i8** [[DATA_ADDR]] + // CHECK: store i8* [[DATA3]], i8** [[ARG4_PTR]] + + __builtin_os_log_format(buf, "%d %{public}s %{private}.16P", i, data, data); +} + +#endif diff --git a/clang/test/CodeGenObjC/os_log.m b/clang/test/CodeGenObjC/os_log.m new file mode 100644 index 0000000000000..144d1cc6ba1f3 --- /dev/null +++ b/clang/test/CodeGenObjC/os_log.m @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 %s -emit-llvm -o - -triple x86_64-darwin-apple -fobjc-arc -O2 | FileCheck %s + +// Make sure we emit clang.arc.use before calling objc_release as part of the +// cleanup. This way we make sure the object will not be released until the +// end of the full expression. + +// rdar://problem/24528966 + +@class NSString; +extern __attribute__((visibility("default"))) NSString * GenString(); + +// Behavior of __builtin_os_log differs between platforms, so only test on X86 +#ifdef __x86_64__ +// CHECK-LABEL: define i8* @test_builtin_os_log +void *test_builtin_os_log(void *buf) { + return __builtin_os_log_format(buf, "capabilities: %@", GenString()); + + // CHECK: store i8 2, i8* + // CHECK: [[NUM_ARGS:%.*]] = getelementptr i8, i8* {{.*}}, i64 1 + // CHECK: store i8 1, i8* [[NUM_ARGS]] + // + // CHECK: [[ARG1_DESC:%.*]] = getelementptr i8, i8* {{.*}}, i64 2 + // CHECK: store i8 64, i8* [[ARG1_DESC]] + // CHECK: [[ARG1_SIZE:%.*]] = getelementptr i8, i8* {{.*}}, i64 3 + // CHECK: store i8 8, i8* [[ARG1_SIZE]] + // CHECK: [[ARG1:%.*]] = getelementptr i8, i8* {{.*}}, i64 4 + // CHECK: [[ARG1_CAST:%.*]] = bitcast i8* [[ARG1]] to + + // CHECK: [[STRING:%.*]] = {{.*}} call {{.*}} @GenString() + // CHECK: [[STRING_CAST:%.*]] = bitcast {{.*}} [[STRING]] to + // CHECK: call {{.*}} @objc_retainAutoreleasedReturnValue(i8* [[STRING_CAST]]) + // CHECK: store {{.*}} [[STRING]], {{.*}} [[ARG1_CAST]] + + // CHECK: call void (...) @clang.arc.use({{.*}} [[STRING]]) + // CHECK: call void @objc_release(i8* [[STRING_CAST]]) + // CHECK: ret i8* +} + +#endif diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c index 5559710c60355..28a2db836f554 100644 --- a/clang/test/Sema/format-strings.c +++ b/clang/test/Sema/format-strings.c @@ -652,3 +652,30 @@ void test_format_security_pos(char* string) { // expected-note@-1{{treat the string as an argument to avoid this}} } #pragma GCC diagnostic warning "-Wformat-nonliteral" + +void test_os_log_format(char c, const char *pc, int i, int *pi, void *p, void *buf) { + __builtin_os_log_format(buf, ""); + __builtin_os_log_format(buf, "%d"); // expected-warning {{more '%' conversions than data arguments}} + __builtin_os_log_format(buf, "%d", i); + __builtin_os_log_format(buf, "%P", p); // expected-warning {{using '%P' format specifier without precision}} + __builtin_os_log_format(buf, "%.10P", p); + __builtin_os_log_format(buf, "%.*P", p); // expected-warning {{field precision should have type 'int', but argument has type 'void *'}} + __builtin_os_log_format(buf, "%.*P", i, p); + __builtin_os_log_format(buf, "%.*P", i, i); // expected-warning {{format specifies type 'void *' but the argument has type 'int'}} + __builtin_os_log_format(buf, pc); // expected-error {{os_log() format argument is not a string constant}} + + printf("%{private}s", pc); // expected-warning {{using 'private' format specifier annotation outside of os_log()/os_trace()}} + __builtin_os_log_format(buf, "%{private}s", pc); + + // + __builtin_os_log_format_buffer_size("no-args"); + __builtin_os_log_format(buf, "%s", "hi"); + + // + wchar_t wc = 'a'; + __builtin_os_log_format(buf, "%C", wc); + printf("%C", wc); + wchar_t wcs[] = {'a', 0}; + __builtin_os_log_format(buf, "%S", wcs); + printf("%S", wcs); +} diff --git a/clang/test/SemaObjC/format-strings-objc.m b/clang/test/SemaObjC/format-strings-objc.m index d81f166a6540b..26dae2e3dbc87 100644 --- a/clang/test/SemaObjC/format-strings-objc.m +++ b/clang/test/SemaObjC/format-strings-objc.m @@ -264,3 +264,18 @@ void testObjCModifierFlags() { NSLog(@"%2$[tt]@ %1$[tt]@", @"Foo", @"Bar"); // no-warning NSLog(@"%2$[tt]@ %1$[tt]s", @"Foo", @"Bar"); // expected-warning {{object format flags cannot be used with 's' conversion specifier}} } + +// Test os_log_format primitive with ObjC string literal format argument. +void test_os_log_format(char c, const char *pc, int i, int *pi, void *p, void *buf, NSString *nss) { + __builtin_os_log_format(buf, @""); + __builtin_os_log_format(buf, @"%d"); // expected-warning {{more '%' conversions than data arguments}} + __builtin_os_log_format(buf, @"%d", i); + __builtin_os_log_format(buf, @"%P", p); // expected-warning {{using '%P' format specifier without precision}} + __builtin_os_log_format(buf, @"%.10P", p); + __builtin_os_log_format(buf, @"%.*P", p); // expected-warning {{field precision should have type 'int', but argument has type 'void *'}} + __builtin_os_log_format(buf, @"%.*P", i, p); + __builtin_os_log_format(buf, @"%.*P", i, i); // expected-warning {{format specifies type 'void *' but the argument has type 'int'}} + + __builtin_os_log_format(buf, @"%{private}s", pc); + __builtin_os_log_format(buf, @"%@", nss); +} From 33e7e500fa3987b3b0fc0e73161659da9d7bed95 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Mon, 13 Jun 2016 11:42:00 -0700 Subject: [PATCH 072/585] Clean up some whitespace and comments for the new logging builtins patch. apple-llvm-split-commit: 53636e55132f5e81d26c7f3c4d08ba7ccec052aa apple-llvm-split-dir: clang/ --- clang/include/clang/Analysis/Analyses/OSLog.h | 5 +++-- clang/lib/Analysis/OSLog.cpp | 18 ++++++++++++++-- clang/lib/Sema/SemaChecking.cpp | 21 +++++++++++-------- clang/test/CodeGen/builtins.c | 2 +- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/OSLog.h b/clang/include/clang/Analysis/Analyses/OSLog.h index 9fde26aaea4dd..084b07920e715 100644 --- a/clang/include/clang/Analysis/Analyses/OSLog.h +++ b/clang/include/clang/Analysis/Analyses/OSLog.h @@ -105,7 +105,7 @@ class OSLogBufferLayout { } return result; } - + bool getHasPrivateItems() const { return std::any_of(Items.begin(), Items.end(), [](const OSLogBufferItem &item) { return item.getIsPrivate(); }); @@ -139,7 +139,8 @@ class OSLogBufferLayout { // __builtin_os_log_format_buffer_size(), compute the layout of the buffer that // the call will write into and store it in 'layout'. Returns 'false' if there // was some error encountered while computing the layout, and 'true' otherwise. -bool computeOSLogBufferLayout(clang::ASTContext &Ctx, const clang::CallExpr *E, OSLogBufferLayout &layout); +bool computeOSLogBufferLayout(clang::ASTContext &Ctx, const clang::CallExpr *E, + OSLogBufferLayout &layout); } // namespace analyze_os_log } // namespace clang diff --git a/clang/lib/Analysis/OSLog.cpp b/clang/lib/Analysis/OSLog.cpp index 804cee2c88929..b6e9c49ef548e 100644 --- a/clang/lib/Analysis/OSLog.cpp +++ b/clang/lib/Analysis/OSLog.cpp @@ -1,4 +1,16 @@ -// TODO: header template +//===--- OSLog.cpp - Analysis of calls to os_log builtins -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines APIs for determining the layout of the data buffer for +// os_log() and os_trace(). +// +//===----------------------------------------------------------------------===// #include "clang/Analysis/Analyses/OSLog.h" #include "clang/Analysis/Analyses/FormatString.h" @@ -118,7 +130,9 @@ class OSLogFormatStringHandler } }; -bool clang::analyze_os_log::computeOSLogBufferLayout(ASTContext &Ctx, const CallExpr *E, OSLogBufferLayout &layout) +bool clang::analyze_os_log::computeOSLogBufferLayout(ASTContext &Ctx, + const CallExpr *E, + OSLogBufferLayout &layout) { ArrayRef Args(E->getArgs(), E->getArgs() + E->getNumArgs()); diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 76929dd5c2b77..820f6e2f3438f 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -3391,7 +3391,7 @@ bool Sema::SemaBuiltinOSLogFormat(CallExpr *TheCall) { unsigned BuiltinID = cast(TheCall->getCalleeDecl())->getBuiltinID(); bool IsSizeCall = BuiltinID == Builtin::BI__builtin_os_log_format_buffer_size; - + unsigned NumArgs = TheCall->getNumArgs(); unsigned NumRequiredArgs = IsSizeCall ? 1 : 2; if (NumArgs < NumRequiredArgs) { @@ -3445,7 +3445,7 @@ bool Sema::SemaBuiltinOSLogFormat(CallExpr *TheCall) { TheCall->setArg(i, Arg.get()); i++; } - + // Check formatting specifiers. NOTE: We're only doing this for the non-size // call to avoid duplicate diagnostics. if (!IsSizeCall) { @@ -4050,9 +4050,10 @@ class CheckFormatHandler : public analyze_format_string::FormatStringHandler { public: CheckFormatHandler(Sema &s, const StringLiteral *fexpr, - const Expr *origFormatExpr, const Sema::FormatStringType - type, unsigned firstDataArg, unsigned numDataArgs, const - char *beg, bool hasVAListArg, ArrayRef Args, + const Expr *origFormatExpr, + const Sema::FormatStringType type, unsigned firstDataArg, + unsigned numDataArgs, const char *beg, bool hasVAListArg, + ArrayRef Args, unsigned formatIdx, bool inFunctionCall, Sema::VariadicCallType callType, llvm::SmallBitVector &CheckedVarArgs, @@ -4491,8 +4492,9 @@ namespace { class CheckPrintfHandler : public CheckFormatHandler { public: CheckPrintfHandler(Sema &s, const StringLiteral *fexpr, - const Expr *origFormatExpr, const Sema::FormatStringType type, - unsigned firstDataArg, unsigned numDataArgs, + const Expr *origFormatExpr, + const Sema::FormatStringType type, unsigned firstDataArg, + unsigned numDataArgs, const char *beg, bool hasVAListArg, ArrayRef Args, unsigned formatIdx, bool inFunctionCall, @@ -5331,8 +5333,9 @@ class CheckScanfHandler : public CheckFormatHandler { public: CheckScanfHandler(Sema &s, const StringLiteral *fexpr, const Expr *origFormatExpr, Sema::FormatStringType type, - unsigned firstDataArg, unsigned numDataArgs, const char - *beg, bool hasVAListArg, ArrayRef Args, + unsigned firstDataArg, + unsigned numDataArgs, const char *beg, bool hasVAListArg, + ArrayRef Args, unsigned formatIdx, bool inFunctionCall, Sema::VariadicCallType CallType, llvm::SmallBitVector &CheckedVarArgs, diff --git a/clang/test/CodeGen/builtins.c b/clang/test/CodeGen/builtins.c index a0681516a1a6e..fa857598945f2 100644 --- a/clang/test/CodeGen/builtins.c +++ b/clang/test/CodeGen/builtins.c @@ -308,7 +308,7 @@ void test_builtin_os_log(void *buf, int i, const char *data) { // CHECK: store i8 34, i8* [[ARG2_DESC]] // CHECK: [[ARG2_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 9 // CHECK: store i8 8, i8* [[ARG2_SIZE]] - // CHECK: [[ARG2:%.*]] = getelementptr i8, i8* [[BUF2]], i64 10 + // CHECK: [[ARG2:%.*]] = getelementptr i8, i8* [[BUF2]], i64 10 // CHECK: [[ARG2_PTR:%.*]] = bitcast i8* [[ARG2]] to i8** // CHECK: [[DATA2:%.*]] = load i8*, i8** [[DATA_ADDR]] // CHECK: store i8* [[DATA2]], i8** [[ARG2_PTR]] From 5d5108b9e0860dd34a87eccf373ab1170815fe98 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Mon, 13 Jun 2016 23:12:58 -0700 Subject: [PATCH 073/585] Fix a build failure after merge from llvm.org. apple-llvm-split-commit: 06b8211db57b33659efce0690174724e4b7e9a79 apple-llvm-split-dir: llvm/ --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index 0d1bf88524780..b7d1c4993da77 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2316,7 +2316,7 @@ std::error_code BitcodeReader::parseMetadata(bool ModuleLevel) { return error("Invalid record"); MetadataList.assignValue( - GET_OR_DISTINCT(DIModule, Record[0], + GET_OR_DISTINCT(DIModule, (Context, getMDOrNull(Record[4]), getMDString(Record[5]), nullptr, nullptr, nullptr)), From 0be9146278b34130efef2a3e77b852e536e54ac4 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Sun, 19 Jun 2016 20:43:20 -0700 Subject: [PATCH 074/585] [lit] Refactor lit detection of macOS triples to use a centralized query in lit.util.isMacOSTriple. Currently, lit assumes that a triple is a macOS triple if it contains the string 'darwin' (pattern matching against $ARCH-apple-darwin$NUM). This is incorrect since the $ARCH-apple-macosx$NUM triples are /also/ valid triples for macOS. More importantly, turns out Swift is using this triple as well, preventing a build-script built llvm from passing all of its unit tests on a bot. Given where we are in the schedule I don't want to touch the triples on either projects, so instead I am adding this workaround, teaching lit that a triple with macosx in it should have a 'darwin' supported_feature. This commit centralizes all the places in LLVM to use the query, lit.util.isMacOSTriple, instead of checking if 'darwin' is in a triple directly. In subsequent commits I will: 1. Change clang/compiler-rt's lit to use this utility function as well. 2. Update isMacOSTriple to recognize other valid triples for macOS such as triples that replace darwin with macosx (i.e. x86_64-apple-darwin vs x86_64-apple-macosx). rdar://26780128 apple-llvm-split-commit: 01436117652b41eb389ddbad9c9fd8df0de9b4b2 apple-llvm-split-dir: llvm/ --- llvm/test/lit.cfg | 2 +- llvm/utils/lit/lit/util.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/llvm/test/lit.cfg b/llvm/test/lit.cfg index 67e851b6e81d3..b0126857b3d5e 100644 --- a/llvm/test/lit.cfg +++ b/llvm/test/lit.cfg @@ -445,7 +445,7 @@ if re.search(r'ON', llvm_config_cmd.stdout.read().decode('ascii')): config.available_features.add('asserts') llvm_config_cmd.wait() -if 'darwin' == sys.platform: +if lit.util.isMacOSTriple(config.target_triple): try: sysctl_cmd = subprocess.Popen(['sysctl', 'hw.optional.fma'], stdout = subprocess.PIPE) diff --git a/llvm/utils/lit/lit/util.py b/llvm/utils/lit/lit/util.py index 40a5771686924..f70d7968b6595 100644 --- a/llvm/utils/lit/lit/util.py +++ b/llvm/utils/lit/lit/util.py @@ -237,10 +237,26 @@ def killProcess(): return out, err, exitCode +# A predicate to determine whether or not a specific config's target_triple is +# referring to macOS. The reason that this is useful is that macOS has multiple +# valid triples. This just centralizes the query into a convenient place. +def isMacOSTriple(target): + arches = [ + 'x86_64', + 'i386', + 'x86_64h' + ] + for a in arches: + triple = '%s-apple-darwin' % a + if triple not in target: + continue + return True + return False + def usePlatformSdkOnDarwin(config, lit_config): # On Darwin, support relocatable SDKs by providing Clang with a # default system root path. - if 'darwin' in config.target_triple: + if isMacOSTriple(config.target_triple): try: cmd = subprocess.Popen(['xcrun', '--show-sdk-path'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) From 61d2664ad4a7d877f1e6be345c11c761030eae0a Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Sun, 19 Jun 2016 23:51:21 -0700 Subject: [PATCH 075/585] {x86_64,i386,x86_64h}-apple-macosx should also be XFAILED when 'darwin' is XFAILED. See 01436117652b41eb389ddbad9c9fd8df0de9b4b2 for more info. rdar://26780128 apple-llvm-split-commit: 9ecb0ea4b899f55f0d4a63fb3ca390203bec0204 apple-llvm-split-dir: llvm/ --- llvm/test/lit.cfg | 3 +++ llvm/utils/lit/lit/util.py | 15 +++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/llvm/test/lit.cfg b/llvm/test/lit.cfg index b0126857b3d5e..6ced60f5ed02b 100644 --- a/llvm/test/lit.cfg +++ b/llvm/test/lit.cfg @@ -378,6 +378,9 @@ if config.target_triple: if config.host_triple == config.target_triple: config.available_features.add("native") +if lit.util.isMacOSTriple(config.target_triple): + config.available_features.add('darwin') + import subprocess def have_ld_plugin_support(): diff --git a/llvm/utils/lit/lit/util.py b/llvm/utils/lit/lit/util.py index f70d7968b6595..ba44b56ff328d 100644 --- a/llvm/utils/lit/lit/util.py +++ b/llvm/utils/lit/lit/util.py @@ -246,11 +246,18 @@ def isMacOSTriple(target): 'i386', 'x86_64h' ] + + names = [ + 'darwin', + 'macosx' + ] + for a in arches: - triple = '%s-apple-darwin' % a - if triple not in target: - continue - return True + for n in names: + triple = '%s-apple-%s' % (a,n) + if triple not in target: + continue + return True return False def usePlatformSdkOnDarwin(config, lit_config): From 667b58378d86ada506e9b06ef18a529db6c0f366 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Sun, 19 Jun 2016 21:01:50 -0700 Subject: [PATCH 076/585] Use the new lit.util.isMacOSTriple function to determine if a target triple is a macOS target triple. See 01436117652b41eb389ddbad9c9fd8df0de9b4b2 in llvm. rdar://26780128 apple-llvm-split-commit: 466f3ab46e3bd128c8cf770c1a7ba729716b99af apple-llvm-split-dir: clang/ --- clang/utils/perf-training/lit.cfg | 2 +- clang/utils/perf-training/order-files.lit.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/utils/perf-training/lit.cfg b/clang/utils/perf-training/lit.cfg index 85d3551434102..eed87ba413c14 100644 --- a/clang/utils/perf-training/lit.cfg +++ b/clang/utils/perf-training/lit.cfg @@ -7,7 +7,7 @@ import lit.util def getSysrootFlagsOnDarwin(config, lit_config): # On Darwin, support relocatable SDKs by providing Clang with a # default system root path. - if 'darwin' in config.target_triple: + if lit.util.isMacOSTriple(config.target_triple): try: out = lit.util.capture(['xcrun', '--show-sdk-path']).strip() res = 0 diff --git a/clang/utils/perf-training/order-files.lit.cfg b/clang/utils/perf-training/order-files.lit.cfg index 75501f8c62979..2e8f7ed2da026 100644 --- a/clang/utils/perf-training/order-files.lit.cfg +++ b/clang/utils/perf-training/order-files.lit.cfg @@ -8,7 +8,7 @@ import os def getSysrootFlagsOnDarwin(config, lit_config): # On Darwin, support relocatable SDKs by providing Clang with a # default system root path. - if 'darwin' in config.target_triple: + if lit.util.isMacOSTriple(config.target_triple): try: out = lit.util.capture(['xcrun', '--show-sdk-path']).strip() res = 0 From bc3e41eb93ace9feb3a50d7565ac3c1ff15f8b02 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Mon, 20 Jun 2016 00:42:49 -0700 Subject: [PATCH 077/585] All triples that are valid macOS triples should have a darwin feature. See commit 01436117652b41eb389ddbad9c9fd8df0de9b4b2 in LLVM. rdar://26780128 apple-llvm-split-commit: b19c89de6c6784a3913c0ec48799bc2673cf7df5 apple-llvm-split-dir: compiler-rt/ --- compiler-rt/test/lit.common.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler-rt/test/lit.common.cfg b/compiler-rt/test/lit.common.cfg index c1a70ffbac719..53aa6eaed5f16 100644 --- a/compiler-rt/test/lit.common.cfg +++ b/compiler-rt/test/lit.common.cfg @@ -75,6 +75,9 @@ if platform.system() == 'Windows' and '-win' in config.target_triple: if re.match(r'^x86_64.*-linux', config.target_triple): config.available_features.add("x86_64-linux") +if lit.util.isMacOSTriple(config.target_triple): + config.available_features.add('darwin') + # Use ugly construction to explicitly prohibit "clang", "clang++" etc. # in RUN lines. config.substitutions.append( From d6ef652c64f4ee16467b16b66dad540730364816 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Wed, 22 Jun 2016 13:10:41 -0700 Subject: [PATCH 078/585] Allow iOS and tvOS version numbers with 2-digit major version numbers. rdar://problem/26921601 apple-llvm-split-commit: 0e2c76507a690e4b4c14e4b56587799d996d5690 apple-llvm-split-dir: clang/ --- clang/lib/Basic/Targets.cpp | 27 +++++++++++++++++++-------- clang/lib/Driver/ToolChains.cpp | 4 ++-- clang/test/Frontend/darwin-version.c | 4 ++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/clang/lib/Basic/Targets.cpp b/clang/lib/Basic/Targets.cpp index 626c5af8fdc35..7c0628252631e 100644 --- a/clang/lib/Basic/Targets.cpp +++ b/clang/lib/Basic/Targets.cpp @@ -157,14 +157,25 @@ static void getDarwinDefines(MacroBuilder &Builder, const LangOptions &Opts, // Set the appropriate OS version define. if (Triple.isiOS()) { - assert(Maj < 10 && Min < 100 && Rev < 100 && "Invalid version!"); - char Str[6]; - Str[0] = '0' + Maj; - Str[1] = '0' + (Min / 10); - Str[2] = '0' + (Min % 10); - Str[3] = '0' + (Rev / 10); - Str[4] = '0' + (Rev % 10); - Str[5] = '\0'; + assert(Maj < 100 && Min < 100 && Rev < 100 && "Invalid version!"); + char Str[7]; + if (Maj < 10) { + Str[0] = '0' + Maj; + Str[1] = '0' + (Min / 10); + Str[2] = '0' + (Min % 10); + Str[3] = '0' + (Rev / 10); + Str[4] = '0' + (Rev % 10); + Str[5] = '\0'; + } else { + // Handle versions >= 10. + Str[0] = '0' + (Maj / 10); + Str[1] = '0' + (Maj % 10); + Str[2] = '0' + (Min / 10); + Str[3] = '0' + (Min % 10); + Str[4] = '0' + (Rev / 10); + Str[5] = '0' + (Rev % 10); + Str[6] = '\0'; + } if (Triple.isTvOS()) Builder.defineMacro("__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__", Str); else diff --git a/clang/lib/Driver/ToolChains.cpp b/clang/lib/Driver/ToolChains.cpp index dec791c9358b3..f510a4b4c190d 100644 --- a/clang/lib/Driver/ToolChains.cpp +++ b/clang/lib/Driver/ToolChains.cpp @@ -694,13 +694,13 @@ void Darwin::AddDeploymentTarget(DerivedArgList &Args) const { assert(iOSVersion && "Unknown target platform!"); if (!Driver::GetReleaseVersion(iOSVersion->getValue(), Major, Minor, Micro, HadExtra) || - HadExtra || Major >= 10 || Minor >= 100 || Micro >= 100) + HadExtra || Major >= 100 || Minor >= 100 || Micro >= 100) getDriver().Diag(diag::err_drv_invalid_version_number) << iOSVersion->getAsString(Args); } else if (Platform == TvOS) { if (!Driver::GetReleaseVersion(TvOSVersion->getValue(), Major, Minor, Micro, HadExtra) || HadExtra || - Major >= 10 || Minor >= 100 || Micro >= 100) + Major >= 100 || Minor >= 100 || Micro >= 100) getDriver().Diag(diag::err_drv_invalid_version_number) << TvOSVersion->getAsString(Args); } else if (Platform == WatchOS) { diff --git a/clang/test/Frontend/darwin-version.c b/clang/test/Frontend/darwin-version.c index e7bc41117e3fc..eb05a48cfd36e 100644 --- a/clang/test/Frontend/darwin-version.c +++ b/clang/test/Frontend/darwin-version.c @@ -10,6 +10,8 @@ // RUN: %clang_cc1 -triple armv6-apple-ios2.3.1 -dM -E -o %t %s // RUN: grep '__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__' %t | grep '20301' | count 1 // RUN: not grep '__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__' %t +// RUN: %clang_cc1 -triple armv7-apple-ios10.1.2 -dM -E -o %t %s +// RUN: grep '__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__' %t | grep '100102' | count 1 // RUN: %clang_cc1 -triple i386-apple-macosx10.4.0 -dM -E -o %t %s // RUN: grep '__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__' %t | grep '1040' | count 1 // RUN: not grep '__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__' %t @@ -32,6 +34,8 @@ // RUN: grep '__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__' %t | grep '80300' | count 1 // RUN: not grep '__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__' %t // RUN: not grep '__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__' %t +// RUN: %clang_cc1 -triple arm64-apple-tvos10.2.3 -dM -E -o %t %s +// RUN: grep '__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__' %t | grep '100203' | count 1 // RUN: %clang_cc1 -triple x86_64-apple-tvos8.3 -dM -E -o %t %s // RUN: grep '__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__' %t | grep '80300' | count 1 From 9c7c55c48d3b9e99ad67a1e457669d2f1aa02258 Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Wed, 29 Jun 2016 14:27:30 -0700 Subject: [PATCH 079/585] Fix a bad merge where we include an extra '}'. apple-llvm-split-commit: 6d7b57a6eba55a0def8a84adccb3dd1b03e2218d apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticFrontendKinds.td | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index d684caa35e096..757c329757463 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -218,7 +218,6 @@ def err_invalid_vfs_overlay : Error< def err_no_apinotes_cache_path : Error< "-fapinotes was provided without -fapinotes-cache-path=">, DefaultFatal; -} def warn_option_invalid_ocl_version : Warning< "OpenCL version %0 does not support the option '%1'">, InGroup; From 9798ae2281290dcf0df862b77549388380aebd12 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 5 Jul 2016 23:25:45 -0700 Subject: [PATCH 080/585] [API Notes] Add support for the ns_error_domain attribute. Addresses rdar://problem/27185793. apple-llvm-split-commit: 7c3fa473f90459af9342386e1c423b6e01cf82a3 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 11 ++++++++++- clang/lib/APINotes/APINotesReader.cpp | 6 ++++++ clang/lib/APINotes/APINotesWriter.cpp | 6 +++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 8 ++++++++ clang/lib/Sema/SemaAPINotes.cpp | 9 +++++++++ clang/test/APINotes/Inputs/roundtrip.apinotes | 11 +++++++++++ 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 11a1e7acf54d6..5d5a347336385 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -129,24 +129,33 @@ class CommonTypeInfo : public CommonEntityInfo { /// Reflects the swift_bridge attribute. std::string SwiftBridge; + /// The NS error domain for this type. + std::string NSErrorDomain; + public: CommonTypeInfo() : CommonEntityInfo() { } const std::string &getSwiftBridge() const { return SwiftBridge; } void setSwiftBridge(const std::string &swiftType) { SwiftBridge = swiftType; } + const std::string &getNSErrorDomain() const { return NSErrorDomain; } + void setNSErrorDomain(const std::string &domain) { NSErrorDomain = domain; } + friend CommonTypeInfo &operator|=(CommonTypeInfo &lhs, const CommonTypeInfo &rhs) { static_cast(lhs) |= rhs; if (lhs.SwiftBridge.empty() && !rhs.SwiftBridge.empty()) lhs.SwiftBridge = rhs.SwiftBridge; + if (lhs.NSErrorDomain.empty() && !rhs.NSErrorDomain.empty()) + lhs.NSErrorDomain = rhs.NSErrorDomain; return lhs; } friend bool operator==(const CommonTypeInfo &lhs, const CommonTypeInfo &rhs) { return static_cast(lhs) == rhs && - lhs.SwiftBridge == rhs.SwiftBridge; + lhs.SwiftBridge == rhs.SwiftBridge && + lhs.NSErrorDomain == rhs.NSErrorDomain; } friend bool operator!=(const CommonTypeInfo &lhs, diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 6de82ebe64be2..81cfed71cf3ae 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -58,6 +58,12 @@ namespace { info.setSwiftBridge( StringRef(reinterpret_cast(data), swiftBridgeLength)); data += swiftBridgeLength; + + unsigned errorDomainLength = + endian::readNext(data); + info.setNSErrorDomain( + StringRef(reinterpret_cast(data), errorDomainLength)); + data += errorDomainLength; } /// Used to deserialize the on-disk identifier table. diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 3d4938e7cd002..e2392c6f52fb8 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -308,7 +308,9 @@ namespace { // Retrieve the serialized size of the given CommonTypeInfo, for use // in on-disk hash tables. static unsigned getCommonTypeInfoSize(const CommonTypeInfo &info) { - return 2 + info.getSwiftBridge().size() + getCommonEntityInfoSize(info); + return 2 + info.getSwiftBridge().size() + + 2 + info.getNSErrorDomain().size() + + getCommonEntityInfoSize(info); } /// Emit a serialized representation of the common type information. @@ -317,6 +319,8 @@ namespace { endian::Writer writer(out); writer.write(info.getSwiftBridge().size()); out.write(info.getSwiftBridge().c_str(), info.getSwiftBridge().size()); + writer.write(info.getNSErrorDomain().size()); + out.write(info.getNSErrorDomain().c_str(), info.getNSErrorDomain().size()); } /// Used to serialize the on-disk Objective-C context table. diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index a5e4fa82ceb17..10648e29313a1 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -199,6 +199,7 @@ namespace { bool SwiftPrivate = false; StringRef SwiftName; StringRef SwiftBridge; + StringRef NSErrorDomain; MethodsSeq Methods; PropertiesSeq Properties; }; @@ -237,6 +238,7 @@ namespace { StringRef SwiftName; bool SwiftPrivate = false; StringRef SwiftBridge; + StringRef NSErrorDomain; }; typedef std::vector TagsSeq; @@ -246,6 +248,7 @@ namespace { StringRef SwiftName; bool SwiftPrivate = false; StringRef SwiftBridge; + StringRef NSErrorDomain; }; typedef std::vector TypedefsSeq; @@ -363,6 +366,7 @@ namespace llvm { io.mapOptional("SwiftPrivate", c.SwiftPrivate); io.mapOptional("SwiftName", c.SwiftName); io.mapOptional("SwiftBridge", c.SwiftBridge); + io.mapOptional("NSErrorDomain", c.NSErrorDomain); io.mapOptional("Methods", c.Methods); io.mapOptional("Properties", c.Properties); } @@ -415,6 +419,7 @@ namespace llvm { io.mapOptional("SwiftPrivate", t.SwiftPrivate); io.mapOptional("SwiftName", t.SwiftName); io.mapOptional("SwiftBridge", t.SwiftBridge); + io.mapOptional("NSErrorDomain", t.NSErrorDomain); } }; @@ -427,6 +432,7 @@ namespace llvm { io.mapOptional("SwiftPrivate", t.SwiftPrivate); io.mapOptional("SwiftName", t.SwiftName); io.mapOptional("SwiftBridge", t.SwiftBridge); + io.mapOptional("NSErrorDomain", t.NSErrorDomain); } }; @@ -572,6 +578,7 @@ namespace { return true; info.setSwiftBridge(common.SwiftBridge); + info.setNSErrorDomain(common.NSErrorDomain); return false; } @@ -890,6 +897,7 @@ namespace { void handleCommonType(T &record, const CommonTypeInfo &info) { handleCommon(record, info); record.SwiftBridge = copyString(info.getSwiftBridge()); + record.NSErrorDomain = copyString(info.getNSErrorDomain()); } /// Map Objective-C context info. diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 4468d22f30a53..0515be234e4ea 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -151,6 +151,15 @@ static void ProcessAPINotes(Sema &S, Decl *D, Info.getSwiftBridge()))); } + // ns_error_domain + if (!Info.getNSErrorDomain().empty() && + !D->getAttr()) { + D->addAttr( + NSErrorDomainAttr::CreateImplicit( + S.Context, + &S.Context.Idents.get(Info.getNSErrorDomain()))); + } + ProcessAPINotes(S, D, static_cast(Info)); } diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index f02aa003dc0b3..66f5ea7f8f2e9 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -10,6 +10,7 @@ Classes: SwiftPrivate: false SwiftName: '' SwiftBridge: '' + NSErrorDomain: '' Methods: - Selector: init MethodKind: Instance @@ -54,6 +55,7 @@ Classes: SwiftPrivate: false SwiftName: '' SwiftBridge: View + NSErrorDomain: '' Methods: - Selector: 'addSubview:' MethodKind: Instance @@ -113,12 +115,20 @@ Enumerators: SwiftPrivate: false SwiftName: Red Tags: + - Name: NSSomeEnum + Availability: available + AvailabilityMsg: '' + SwiftPrivate: false + SwiftName: SomeEnum + SwiftBridge: '' + NSErrorDomain: some_error_domain - Name: NSSomeStruct Availability: available AvailabilityMsg: '' SwiftPrivate: false SwiftName: SomeStruct SwiftBridge: '' + NSErrorDomain: '' Typedefs: - Name: NSTypedef Availability: available @@ -126,3 +136,4 @@ Typedefs: SwiftPrivate: false SwiftName: Typedef SwiftBridge: '' + NSErrorDomain: '' From 8a3d10919b3b62e353c9ce590d96585c3859270a Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 10 Jul 2016 17:51:30 -0700 Subject: [PATCH 081/585] [API Notes] Add support for parameters, 'noescape' attribute apple-llvm-split-commit: 54a75ad253996e21d65ae464b2611b57ef8c7335 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 39 ++++++++++++- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 16 ++++++ clang/lib/APINotes/APINotesWriter.cpp | 17 +++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 55 +++++++++++++++++++ clang/lib/Sema/SemaAPINotes.cpp | 46 ++++++++++------ .../Inputs/APINotes/HeaderLib.apinotes | 7 +++ .../test/APINotes/Inputs/Headers/HeaderLib.h | 2 + clang/test/APINotes/Inputs/roundtrip.apinotes | 7 +++ clang/test/APINotes/nullability.c | 1 + 10 files changed, 173 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 5d5a347336385..9761510ead2b6 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -268,7 +268,6 @@ class VariableInfo : public CommonEntityInfo { Nullable = static_cast(kind); } - friend bool operator==(const VariableInfo &lhs, const VariableInfo &rhs) { return static_cast(lhs) == rhs && lhs.NullabilityAudited == rhs.NullabilityAudited && @@ -279,6 +278,13 @@ class VariableInfo : public CommonEntityInfo { return !(lhs == rhs); } + friend VariableInfo &operator|=(VariableInfo &lhs, + const VariableInfo &rhs) { + static_cast(lhs) |= rhs; + if (!lhs.NullabilityAudited && rhs.NullabilityAudited) + lhs.setNullabilityAudited(*rhs.getNullability()); + return lhs; + } }; /// Describes API notes data for an Objective-C property. @@ -300,6 +306,34 @@ class ObjCPropertyInfo : public VariableInfo { } }; +/// Describes a function or method parameter. +class ParamInfo : public VariableInfo { + /// Whether the this parameter has the 'noescape' attribute. + unsigned NoEscape : 1; + +public: + ParamInfo() : VariableInfo(), NoEscape(false) { } + + bool isNoEscape() const { return NoEscape; } + void setNoEscape(bool noescape) { NoEscape = noescape; } + + friend ParamInfo &operator|=(ParamInfo &lhs, const ParamInfo &rhs) { + static_cast(lhs) |= rhs; + if (!lhs.NoEscape && rhs.NoEscape) + lhs.NoEscape = true; + return lhs; + } + + friend bool operator==(const ParamInfo &lhs, const ParamInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.NoEscape == rhs.NoEscape; + } + + friend bool operator!=(const ParamInfo &lhs, const ParamInfo &rhs) { + return !(lhs == rhs); + } +}; + /// A temporary reference to an Objective-C selector, suitable for /// referencing selector data on the stack. /// @@ -333,6 +367,9 @@ class FunctionInfo : public CommonEntityInfo { // of the parameters. uint64_t NullabilityPayload = 0; + /// The function parameters. + std::vector Params; + FunctionInfo() : CommonEntityInfo(), NullabilityAudited(false), diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 2d878ef866308..aad13f76126bc 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 12; // SwiftPrivate +const uint16_t VERSION_MINOR = 13; // Function/method parameters using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 81cfed71cf3ae..56c782a8c9ec0 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -228,6 +228,22 @@ namespace { = endian::readNext(data); info.NullabilityPayload = endian::readNext(data); + + unsigned numParams = endian::readNext(data); + while (numParams > 0) { + uint8_t payload = endian::readNext(data); + + ParamInfo pi; + uint8_t nullabilityValue = payload & 0x3; payload >>= 2; + if (payload & 0x01) + pi.setNullabilityAudited(static_cast(nullabilityValue)); + payload >>= 1; + pi.setNoEscape(payload & 0x01); + payload >>= 1; assert(payload == 0 && "Bad API notes"); + + info.Params.push_back(pi); + --numParams; + } } /// Used to deserialize the on-disk Objective-C method table. diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index e2392c6f52fb8..0c178becc16f7 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -493,7 +493,8 @@ namespace { /// Retrieve the serialized size of the given FunctionInfo, for use in /// on-disk hash tables. static unsigned getFunctionInfoSize(const FunctionInfo &info) { - return 2 + sizeof(uint64_t) + getCommonEntityInfoSize(info); + return 2 + sizeof(uint64_t) + getCommonEntityInfoSize(info) + + 2 + info.Params.size() * 1; } /// Emit a serialized representation of the function information. @@ -504,6 +505,20 @@ namespace { writer.write(info.NullabilityAudited); writer.write(info.NumAdjustedNullable); writer.write(info.NullabilityPayload); + + // Parameters. + writer.write(info.Params.size()); + for (const auto &pi : info.Params) { + uint8_t payload = pi.isNoEscape(); + + auto nullability = pi.getNullability(); + payload = (payload << 1) | nullability.hasValue(); + + payload = payload << 2; + if (nullability) + payload |= static_cast(*nullability); + writer.write(payload); + } } /// Used to serialize the on-disk Objective-C method table. diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 10648e29313a1..a218859077739 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -168,9 +168,17 @@ namespace { NullabilityKind::NonNull; typedef std::vector NullabilitySeq; + struct Param { + unsigned Position; + bool NoEscape = false; + llvm::Optional Nullability; + }; + typedef std::vector ParamsSeq; + struct Method { StringRef Selector; MethodKind Kind; + ParamsSeq Params; NullabilitySeq Nullability; llvm::Optional NullabilityOfRet; AvailabilityItem Availability; @@ -207,6 +215,7 @@ namespace { struct Function { StringRef Name; + ParamsSeq Params; NullabilitySeq Nullability; llvm::Optional NullabilityOfRet; AvailabilityItem Availability; @@ -274,6 +283,7 @@ namespace { LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(clang::NullabilityKind) LLVM_YAML_IS_SEQUENCE_VECTOR(Method) LLVM_YAML_IS_SEQUENCE_VECTOR(Property) +LLVM_YAML_IS_SEQUENCE_VECTOR(Param) LLVM_YAML_IS_SEQUENCE_VECTOR(Class) LLVM_YAML_IS_SEQUENCE_VECTOR(Function) LLVM_YAML_IS_SEQUENCE_VECTOR(GlobalVariable) @@ -324,6 +334,16 @@ namespace llvm { } }; + template <> + struct MappingTraits { + static void mapping(IO &io, Param& p) { + io.mapRequired("Position", p.Position); + io.mapOptional("Nullability", p.Nullability, + AbsentNullability); + io.mapOptional("NoEscape", p.NoEscape); + } + }; + template <> struct MappingTraits { static void mapping(IO &io, Property& p) { @@ -342,6 +362,7 @@ namespace llvm { static void mapping(IO &io, Method& m) { io.mapRequired("Selector", m.Selector); io.mapRequired("MethodKind", m.Kind); + io.mapOptional("Parameters", m.Params); io.mapOptional("Nullability", m.Nullability); io.mapOptional("NullabilityOfRet", m.NullabilityOfRet, AbsentNullability); @@ -376,6 +397,7 @@ namespace llvm { struct MappingTraits { static void mapping(IO &io, Function& f) { io.mapRequired("Name", f.Name); + io.mapOptional("Parameters", f.Params); io.mapOptional("Nullability", f.Nullability); io.mapOptional("NullabilityOfRet", f.NullabilityOfRet, AbsentNullability); @@ -529,6 +551,20 @@ namespace { return false; } + void convertParams(const ParamsSeq ¶ms, FunctionInfo &outInfo) { + for (const auto &p : params) { + ParamInfo pi; + if (p.Nullability) + pi.setNullabilityAudited(*p.Nullability); + pi.setNoEscape(p.NoEscape); + + while (outInfo.Params.size() <= p.Position) { + outInfo.Params.push_back(ParamInfo()); + } + outInfo.Params[p.Position] |= pi; + } + } + void convertNullability(const NullabilitySeq &nullability, Optional nullabilityOfRet, FunctionInfo &outInfo, @@ -612,6 +648,9 @@ namespace { if (meth.FactoryAsInit != FactoryAsInitKind::Infer) mInfo.setFactoryAsInitKind(meth.FactoryAsInit); + // Translate parameter information. + convertParams(meth.Params, mInfo); + // Translate nullability info. convertNullability(meth.Nullability, meth.NullabilityOfRet, mInfo, meth.Selector); @@ -746,6 +785,7 @@ namespace { convertAvailability(function.Availability, info, function.Name); info.SwiftPrivate = function.SwiftPrivate; info.SwiftName = function.SwiftName; + convertParams(function.Params, info); convertNullability(function.Nullability, function.NullabilityOfRet, info, function.Name); @@ -926,6 +966,19 @@ namespace { } } + /// Map parameter information for a function. + void handleParameters(ParamsSeq ¶ms, + const FunctionInfo &info) { + unsigned position = 0; + for (const auto &pi: info.Params) { + Param p; + p.Position = position++; + p.Nullability = pi.getNullability(); + p.NoEscape = pi.isNoEscape(); + params.push_back(p); + } + } + /// Map nullability information for a function. void handleNullability(NullabilitySeq &nullability, llvm::Optional &nullabilityOfRet, @@ -969,6 +1022,7 @@ namespace { method.Kind = isInstanceMethod ? MethodKind::Instance : MethodKind::Class; handleCommon(method, info); + handleParameters(method.Params, info); handleNullability(method.Nullability, method.NullabilityOfRet, info, selector.count(':')); method.FactoryAsInit = info.getFactoryAsInitKind(); @@ -1005,6 +1059,7 @@ namespace { Function function; function.Name = name; handleCommon(function, info); + handleParameters(function.Params, info); if (info.NumAdjustedNullable > 0) handleNullability(function.Nullability, function.NullabilityOfRet, info, info.NumAdjustedNullable-1); diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 0515be234e4ea..445a52c8ed1de 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -175,10 +175,20 @@ static void ProcessAPINotes(Sema &S, Decl *D, ProcessAPINotes(S, D, static_cast(Info)); } +/// Process API notes for a parameter. +static void ProcessAPINotes(Sema &S, ParmVarDecl *D, + const api_notes::ParamInfo &Info) { + // noescape + if (Info.isNoEscape() && !D->getAttr()) + D->addAttr(NoEscapeAttr::CreateImplicit(S.Context)); + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info)); +} + /// Process API notes for a global variable. static void ProcessAPINotes(Sema &S, VarDecl *D, const api_notes::GlobalVariableInfo &Info) { - // Handle common entity information. ProcessAPINotes(S, D, static_cast(Info)); } @@ -186,7 +196,6 @@ static void ProcessAPINotes(Sema &S, VarDecl *D, /// Process API notes for an Objective-C property. static void ProcessAPINotes(Sema &S, ObjCPropertyDecl *D, const api_notes::ObjCPropertyInfo &Info) { - // Handle common entity information. ProcessAPINotes(S, D, static_cast(Info)); } @@ -207,26 +216,31 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, D = MD; } - // Nullability. + // Nullability of return type. if (Info.NullabilityAudited) { - // Return type. applyNullability(S, D, Info.getReturnTypeInfo()); + } - // Parameters. - unsigned NumParams; + // Parameters. + unsigned NumParams; + if (FD) + NumParams = FD->getNumParams(); + else + NumParams = MD->param_size(); + + for (unsigned I = 0; I != NumParams; ++I) { + ParmVarDecl *Param; if (FD) - NumParams = FD->getNumParams(); + Param = FD->getParamDecl(I); else - NumParams = MD->param_size(); - - for (unsigned I = 0; I != NumParams; ++I) { - ParmVarDecl *Param; - if (FD) - Param = FD->getParamDecl(I); - else - Param = MD->param_begin()[I]; - + Param = MD->param_begin()[I]; + + // Nullability. + if (Info.NullabilityAudited) applyNullability(S, Param, Info.getParamTypeInfo(I)); + + if (I < Info.Params.size()) { + ProcessAPINotes(S, Param, Info.Params[I]); } } diff --git a/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes b/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes index 8d8ff11f69b57..9df8c3d5e464c 100644 --- a/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes +++ b/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes @@ -9,6 +9,13 @@ Functions: - Name: do_something_with_pointers NullabilityOfRet: O Nullability: [ N, O ] + - Name: take_pointer_and_int + Parameters: + - Position: 0 + Nullability: N + NoEscape: true + - Position: 1 + NoEscape: true Globals: - Name: global_int diff --git a/clang/test/APINotes/Inputs/Headers/HeaderLib.h b/clang/test/APINotes/Inputs/Headers/HeaderLib.h index 81a7d63d4684d..ec66166adb236 100644 --- a/clang/test/APINotes/Inputs/Headers/HeaderLib.h +++ b/clang/test/APINotes/Inputs/Headers/HeaderLib.h @@ -13,4 +13,6 @@ void do_something_with_pointers(int *ptr1, int *ptr2); typedef int unavailable_typedef; struct unavailable_struct { int x, y, z; }; +void take_pointer_and_int(int *ptr1, int value); + #endif diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 66f5ea7f8f2e9..090273479018b 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -67,6 +67,13 @@ Classes: SwiftName: '' - Selector: 'addSubview:positioned:relativeTo:' MethodKind: Instance + Parameters: + - Position: 0 + NoEscape: false + - Position: 1 + NoEscape: false + - Position: 2 + NoEscape: true Nullability: [ N, N, O ] NullabilityOfRet: N Availability: available diff --git a/clang/test/APINotes/nullability.c b/clang/test/APINotes/nullability.c index 86054eade82e4..1d5939bf92e2c 100644 --- a/clang/test/APINotes/nullability.c +++ b/clang/test/APINotes/nullability.c @@ -8,6 +8,7 @@ int main() { int i = 0; do_something_with_pointers(&i, 0); do_something_with_pointers(0, &i); // expected-warning{{null passed to a callee that requires a non-null argument}} + take_pointer_and_int(0, 0); // expected-warning{{null passed to a callee that requires a non-null argument}} float *fp = global_int; // expected-warning{{incompatible pointer types initializing 'float *' with an expression of type 'int * _Nonnull'}} return 0; From cda67b500f8cabb6c8b5c75c6423e8bac8bed00f Mon Sep 17 00:00:00 2001 From: Lang Hames Date: Mon, 18 Jul 2016 13:48:26 -0700 Subject: [PATCH 082/585] Add fixes left out of previous merge commit. (Actual fixes by John McCall -- thanks John!) apple-llvm-split-commit: b0e86c9a67f564a17710d822529573b2ad2e0458 apple-llvm-split-dir: clang/ --- clang/lib/CodeGen/CGObjCGNU.cpp | 2 +- clang/lib/CodeGen/CGObjCRuntime.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 6a1f9930ab668..0a6c60a73d5ea 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -592,7 +592,7 @@ class CGObjCGNU : public CGObjCRuntime { } llvm::Constant *GetClassGlobal(StringRef Name, bool ForDefinition, - bool Weak) override { + bool Weak, bool DLLImport) override { return nullptr; } }; diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h index dfe37b48e37a8..dee1e9cbc2c10 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.h +++ b/clang/lib/CodeGen/CGObjCRuntime.h @@ -281,7 +281,7 @@ class CGObjCRuntime { QualType T) = 0; virtual llvm::Constant *GetClassGlobal(StringRef Name, bool ForDefinition, - bool Weak = false) = 0; + bool Weak = false, bool DLLImport = false) = 0; struct MessageSendInfo { const CGFunctionInfo &CallInfo; From 4e46faf006cee2463f2cc0feec2076da82527fef Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Wed, 3 Aug 2016 14:16:35 -0700 Subject: [PATCH 083/585] Availability for Swift: support deprecated for all versions. rdar://27682300 apple-llvm-split-commit: e5b87f265aede41c8381094bbf54e2715c8293b0 apple-llvm-split-dir: clang/ --- .../clang/Basic/DiagnosticSemaKinds.td | 4 +-- clang/lib/Parse/ParseDecl.cpp | 17 ++++++++++- clang/lib/Sema/SemaDeclAttr.cpp | 7 +++-- clang/test/Sema/attr-availability-swift.c | 29 +++++++++++++++++++ clang/test/Sema/attr-availability.c | 16 ---------- 5 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 clang/test/Sema/attr-availability-swift.c diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3919b3d006f30..bc1d9f5a52d45 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2621,8 +2621,8 @@ def warn_mismatched_availability_override_unavail : Warning< InGroup; def note_overridden_method : Note< "overridden method is here">; -def warn_availability_swift_unavailable_only : Warning< - "only 'unavailable' is supported for Swift availability">, +def warn_availability_swift_unavailable_deprecated_only : Warning< + "only 'unavailable' and 'deprecated' are supported for Swift availability">, InGroup; def note_protocol_method : Note< "protocol method is here">; diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index a3a365ede1c07..bc510d04a1e2f 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -884,7 +884,7 @@ VersionTuple Parser::ParseVersionTuple(SourceRange &Range) { /// /// version-arg: /// 'introduced' '=' version -/// 'deprecated' '=' version +/// 'deprecated' ['=' version] /// 'obsoleted' = version /// 'unavailable' /// opt-replacement: @@ -972,6 +972,21 @@ void Parser::ParseAvailabilityAttribute(IdentifierInfo &Availability, continue; } + if (Keyword == Ident_deprecated && Platform->Ident && + Platform->Ident->getName() == "swift") { + // For swift, we deprecate for all versions. + if (!Changes[Deprecated].KeywordLoc.isInvalid()) { + Diag(KeywordLoc, diag::err_availability_redundant) + << Keyword + << SourceRange(Changes[Deprecated].KeywordLoc); + } + + Changes[Deprecated].KeywordLoc = KeywordLoc; + // Use a fake version here. + Changes[Deprecated].Version = VersionTuple(1); + continue; + } + if (Tok.isNot(tok::equal)) { Diag(Tok, diag::err_expected_after) << Keyword << tok::equal; SkipUntil(tok::r_paren, StopAtSemi); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 2a16ef2ead33d..c281e7dbb2a7c 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2173,9 +2173,10 @@ static void handleAvailabilityAttr(Sema &S, Decl *D, Replacement = SE->getString(); if (II->getName() == "swift") { - if (Introduced.isValid() || Deprecated.isValid() || Obsoleted.isValid() || - !IsUnavailable) { - S.Diag(Attr.getLoc(), diag::warn_availability_swift_unavailable_only); + if (Introduced.isValid() || Obsoleted.isValid() || + (!IsUnavailable && !Deprecated.isValid())) { + S.Diag(Attr.getLoc(), + diag::warn_availability_swift_unavailable_deprecated_only); return; } } diff --git a/clang/test/Sema/attr-availability-swift.c b/clang/test/Sema/attr-availability-swift.c new file mode 100644 index 0000000000000..42e75246d332f --- /dev/null +++ b/clang/test/Sema/attr-availability-swift.c @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 -fsyntax-only -fblocks -verify %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 -fsyntax-only -ast-dump %s | FileCheck %s +// + +#if !__has_feature(attribute_availability_with_message) +# error "Missing __has_feature" +#endif + +#if __has_feature(attribute_availability_swift) +# warning "okay" +// expected-warning@-1{{okay}} +#else +# error "Missing __has_feature" +#endif + +extern int noSwiftGlobal1 __attribute__((availability(swift, unavailable))); +// CHECK: AvailabilityAttr {{.*}}swift 0 0 0 Unavailable "" "" +extern int noSwiftGlobal1 __attribute__((availability(macosx, introduced=10.1))); // okay +// CHECK: AvailabilityAttr {{.*}}macos 10.1 0 0 "" "" +// CHECK: AvailabilityAttr {{.*}}Inherited swift 0 0 0 Unavailable "" "" +extern int noSwiftGlobal1 __attribute__((availability(swift, unavailable, message="and this one has a message"))); // okay +// CHECK: AvailabilityAttr {{.*}}swift 0 0 0 Unavailable "and this one has a message" "" +// CHECK: AvailabilityAttr {{.*}}Inherited macos 10.1 0 0 "" "" +extern int noSwiftGlobal2 __attribute__((availability(swift, introduced=5))); // expected-warning{{only 'unavailable' and 'deprecated' are supported for Swift availability}} +// CHECK: VarDecl +// CHECK-NOT: AvailabilityAttr +extern int noSwiftGlobal3 __attribute__((availability(swift, deprecated, message="t"))); +// CHECK: VarDecl +// CHECK: AvailabilityAttr {{.*}}swift 0 1 0 "t" "" diff --git a/clang/test/Sema/attr-availability.c b/clang/test/Sema/attr-availability.c index 6b1b3a643997e..e3322f1b19e8d 100644 --- a/clang/test/Sema/attr-availability.c +++ b/clang/test/Sema/attr-availability.c @@ -80,22 +80,6 @@ void f8() { extern int x2 __attribute__((availability(macosx,introduced=10.2))); // expected-note {{previous attribute is here}} extern int x2 __attribute__((availability(macosx,introduced=10.5))); // expected-warning {{availability does not match previous declaration}} - - -#if __has_feature(attribute_availability_swift) -# warning "okay" -// expected-warning@-1{{okay}} -#else -# error "Missing __has_feature" -#endif - - -extern int noSwiftGlobal1 __attribute__((availability(swift, unavailable))); -extern int noSwiftGlobal1 __attribute__((availability(macosx, introduced=10.1))); // okay -extern int noSwiftGlobal1 __attribute__((availability(swift, unavailable, message="and this one has a message"))); // okay - -extern int noSwiftGlobal2 __attribute__((availability(swift, introduced=5))); // expected-warning{{only 'unavailable' is supported for Swift availability}} - enum Original { OriginalDeprecated __attribute__((availability(macosx, deprecated=10.2))), // expected-note + {{'OriginalDeprecated' has been explicitly marked deprecated here}} OriginalUnavailable __attribute__((availability(macosx, unavailable))) // expected-note + {{'OriginalUnavailable' has been explicitly marked unavailable here}} From 4a1b4edbe1d1969284c1528e2950ac81b25edc8f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 27 Sep 2016 12:33:08 -0700 Subject: [PATCH 084/585] Add hash_value support for tuples. apple-llvm-split-commit: f018bd9ae7cf418188285c03d8fe2aacecbd4730 apple-llvm-split-dir: llvm/ --- llvm/include/llvm/ADT/Hashing.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/llvm/include/llvm/ADT/Hashing.h b/llvm/include/llvm/ADT/Hashing.h index c3b574102f69d..8b1a305852b64 100644 --- a/llvm/include/llvm/ADT/Hashing.h +++ b/llvm/include/llvm/ADT/Hashing.h @@ -53,6 +53,7 @@ #include #include #include +#include #include namespace llvm { @@ -656,6 +657,33 @@ hash_code hash_value(const std::basic_string &arg) { return hash_combine_range(arg.begin(), arg.end()); } +template +struct UnsignedConstantIndexSet { }; + +template +struct MakeUnsignedConstantIndexSet { + typedef typename MakeUnsignedConstantIndexSet::Type + Type; +}; + +template +struct MakeUnsignedConstantIndexSet { + typedef UnsignedConstantIndexSet Type; +}; + +template +hash_code hash_value_tuple_helper(const std::tuple &arg, + UnsignedConstantIndexSet indices) { + return hash_combine(hash_value(std::get(arg))...); +} + +template +hash_code hash_value(const std::tuple &arg) { + return hash_value_tuple_helper( + arg, + typename MakeUnsignedConstantIndexSet<0, sizeof...(Ts)>::Type()); +} + } // namespace llvm #endif From 098bcad53ea77c09476f6b81b8fc6ca1427d782c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 27 Sep 2016 12:34:02 -0700 Subject: [PATCH 085/585] [APINotes] Add support for distinguishing class vs. instance properties. Fixes rdar://problem/28455756. apple-llvm-split-commit: 7bde131b05f106eeeff0bc874a6c8e28305a9835 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 6 ++- clang/include/clang/APINotes/APINotesWriter.h | 1 + clang/lib/APINotes/APINotesFormat.h | 4 +- clang/lib/APINotes/APINotesReader.cpp | 22 +++++++---- clang/lib/APINotes/APINotesWriter.cpp | 20 ++++++---- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 38 ++++++++++++++++--- clang/lib/Sema/SemaAPINotes.cpp | 6 ++- clang/lib/Sema/SemaObjCProperty.cpp | 4 +- .../APINotes/Inputs/APINotes/SomeKit.apinotes | 8 ++++ .../APINotes/SomeKit.apinotes | 9 +++++ .../SomeKit.framework/Headers/SomeKit.h | 11 ++++++ clang/test/APINotes/Inputs/roundtrip.apinotes | 2 + clang/test/APINotes/nullability.m | 10 ++++- clang/test/APINotes/yaml-reader-errors.c | 2 +- 14 files changed, 115 insertions(+), 28 deletions(-) diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 29dcc1f56f920..cc6dafb863f65 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -74,10 +74,13 @@ class APINotesReader { /// /// \param contextID The ID that references the context we are looking for. /// \param name The name of the property we're looking for. + /// \param isInstance Whether we are looking for an instance property (vs. + /// a class property). /// /// \returns Information about the property, if known. Optional lookupObjCProperty(ContextID contextID, - StringRef name); + StringRef name, + bool isInstance); /// Look for information regarding the given Objective-C method in /// the given context. @@ -147,6 +150,7 @@ class APINotesReader { /// Visit an Objective-C property. virtual void visitObjCProperty(ContextID contextID, StringRef name, + bool isInstance, const ObjCPropertyInfo &info); /// Visit a global variable. diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h index aa41756b2e778..cc93a5f5899b6 100644 --- a/clang/include/clang/APINotes/APINotesWriter.h +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -66,6 +66,7 @@ class APINotesWriter { /// \param name The name of this property. /// \param info Information about this property. void addObjCProperty(ContextID contextID, StringRef name, + bool isInstanceProperty, const ObjCPropertyInfo &info); /// Add information about a specific Objective-C method. diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index aad13f76126bc..8e5210cc319d2 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 13; // Function/method parameters +const uint16_t VERSION_MINOR = 14; // Objective-C class properties using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; @@ -291,4 +291,6 @@ namespace llvm { }; } +} + #endif // LLVM_CLANG_API_NOTES_FORMAT_H diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 56c782a8c9ec0..2b49125fffc01 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -175,8 +175,8 @@ namespace { /// Used to deserialize the on-disk Objective-C property table. class ObjCPropertyTableInfo { public: - // (context ID, name ID) - using internal_key_type = std::pair; + // (context ID, name ID, isInstance) + using internal_key_type = std::tuple; using external_key_type = internal_key_type; using data_type = ObjCPropertyInfo; using hash_value_type = size_t; @@ -208,7 +208,8 @@ namespace { static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto classID = endian::readNext(data); auto nameID = endian::readNext(data); - return { classID, nameID }; + char isInstance = endian::readNext(data); + return std::make_tuple(classID, nameID, isInstance); } static data_type ReadData(internal_key_type key, const uint8_t *data, @@ -1510,7 +1511,8 @@ auto APINotesReader::lookupObjCProtocol(StringRef name) Optional APINotesReader::lookupObjCProperty( ContextID contextID, - StringRef name) { + StringRef name, + bool isInstance) { if (!Impl.ObjCPropertyTable) return None; @@ -1518,7 +1520,9 @@ Optional APINotesReader::lookupObjCProperty( if (!propertyID) return None; - auto known = Impl.ObjCPropertyTable->find({contextID.Value, *propertyID}); + auto known = Impl.ObjCPropertyTable->find(std::make_tuple(contextID.Value, + *propertyID, + (char)isInstance)); if (known == Impl.ObjCPropertyTable->end()) return None; @@ -1639,6 +1643,7 @@ void APINotesReader::Visitor::visitObjCMethod(ContextID contextID, void APINotesReader::Visitor::visitObjCProperty(ContextID contextID, StringRef name, + bool isInstance, const ObjCPropertyInfo &info) { } void APINotesReader::Visitor::visitGlobalVariable( @@ -1723,10 +1728,11 @@ void APINotesReader::visit(Visitor &visitor) { // Visit properties. if (Impl.ObjCPropertyTable) { for (auto key : Impl.ObjCPropertyTable->keys()) { - ContextID contextID(key.first); - auto name = identifiers[key.second]; + ContextID contextID(std::get<0>(key)); + auto name = identifiers[std::get<1>(key)]; + char isInstance = std::get<2>(key); auto info = *Impl.ObjCPropertyTable->find(key); - visitor.visitObjCProperty(contextID, name, info); + visitor.visitObjCProperty(contextID, name, isInstance, info); } } diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 0c178becc16f7..0e0bfaf1bf4ac 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -58,8 +58,9 @@ class APINotesWriter::Implementation { /// Information about Objective-C properties. /// - /// Indexed by the context ID and property name. - llvm::DenseMap, ObjCPropertyInfo> + /// Indexed by the context ID, property name, and whether this is an + /// instance property. + llvm::DenseMap, ObjCPropertyInfo> ObjCProperties; /// Information about Objective-C methods. @@ -430,7 +431,8 @@ namespace { /// Used to serialize the on-disk Objective-C property table. class ObjCPropertyTableInfo { public: - using key_type = std::pair; // (class ID, name ID) + // (class ID, name ID, isInstance) + using key_type = std::tuple; using key_type_ref = key_type; using data_type = ObjCPropertyInfo; using data_type_ref = const data_type &; @@ -444,7 +446,7 @@ namespace { std::pair EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { - uint32_t keyLength = sizeof(uint32_t) + sizeof(uint32_t); + uint32_t keyLength = sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t); uint32_t dataLength = getVariableInfoSize(data); endian::Writer writer(out); writer.write(keyLength); @@ -454,8 +456,9 @@ namespace { void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { endian::Writer writer(out); - writer.write(key.first); - writer.write(key.second); + writer.write(std::get<0>(key)); + writer.write(std::get<1>(key)); + writer.write(std::get<2>(key)); } void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, @@ -1060,10 +1063,11 @@ ContextID APINotesWriter::addObjCProtocol(StringRef name, return ContextID(known->second.first); } void APINotesWriter::addObjCProperty(ContextID contextID, StringRef name, + bool isInstance, const ObjCPropertyInfo &info) { IdentifierID nameID = Impl.getIdentifier(name); - assert(!Impl.ObjCProperties.count({contextID.Value, nameID})); - Impl.ObjCProperties[{contextID.Value, nameID}] = info; + assert(!Impl.ObjCProperties.count({contextID.Value, nameID, isInstance})); + Impl.ObjCProperties[{contextID.Value, nameID, isInstance}] = info; } void APINotesWriter::addObjCMethod(ContextID contextID, diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index a218859077739..b9640954f58fa 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -193,6 +193,7 @@ namespace { struct Property { StringRef Name; + llvm::Optional Kind; llvm::Optional Nullability; AvailabilityItem Availability; bool SwiftPrivate = false; @@ -348,6 +349,7 @@ namespace llvm { struct MappingTraits { static void mapping(IO &io, Property& p) { io.mapRequired("Name", p.Name); + io.mapOptional("PropertyKind", p.Kind); io.mapOptional("Nullability", p.Nullability, AbsentNullability); io.mapOptional("Availability", p.Availability.Mode); @@ -693,11 +695,20 @@ namespace { } // Write all properties. - llvm::StringSet<> knownProperties; + llvm::StringSet<> knownInstanceProperties; + llvm::StringSet<> knownClassProperties; for (const auto &prop : cl.Properties) { // Check for duplicate property definitions. - if (!knownProperties.insert(prop.Name).second) { - emitError("duplicate definition of property '" + cl.Name + "." + + if ((!prop.Kind || *prop.Kind == MethodKind::Instance) && + !knownInstanceProperties.insert(prop.Name).second) { + emitError("duplicate definition of instance property '" + cl.Name + + "." + prop.Name + "'"); + continue; + } + + if ((!prop.Kind || *prop.Kind == MethodKind::Class) && + !knownClassProperties.insert(prop.Name).second) { + emitError("duplicate definition of class property '" + cl.Name + "." + prop.Name + "'"); continue; } @@ -711,7 +722,14 @@ namespace { pInfo.SwiftName = prop.SwiftName; if (prop.Nullability) pInfo.setNullabilityAudited(*prop.Nullability); - Writer->addObjCProperty(clID, prop.Name, pInfo); + if (prop.Kind) { + Writer->addObjCProperty(clID, prop.Name, + *prop.Kind == MethodKind::Instance, pInfo); + } else { + // Add both instance and class properties with this name. + Writer->addObjCProperty(clID, prop.Name, true, pInfo); + Writer->addObjCProperty(clID, prop.Name, false, pInfo); + } } } @@ -1037,9 +1055,11 @@ namespace { } virtual void visitObjCProperty(ContextID contextID, StringRef name, + bool isInstance, const ObjCPropertyInfo &info) { Property property; property.Name = name; + property.Kind = isInstance ? MethodKind::Instance : MethodKind::Class; handleCommon(property, info); // FIXME: No way to represent "not audited for nullability". @@ -1109,6 +1129,11 @@ namespace { }; } +/// Produce a flattened, numeric value for optional method/property kinds. +static unsigned flattenPropertyKind(llvm::Optional kind) { + return kind ? (*kind == MethodKind::Instance ? 2 : 1) : 0; +} + bool api_notes::decompileAPINotes(std::unique_ptr input, llvm::raw_ostream &os) { // Try to read the file. @@ -1150,7 +1175,10 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, // Sort properties. std::sort(record.Properties.begin(), record.Properties.end(), [](const Property &lhs, const Property &rhs) -> bool { - return lhs.Name < rhs.Name; + return lhs.Name < rhs.Name || + (lhs.Name == rhs.Name && + flattenPropertyKind(lhs.Kind) < + flattenPropertyKind(rhs.Kind)); }); // Sort methods. diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 445a52c8ed1de..a0c5ca88236ca 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -491,8 +491,12 @@ void Sema::ProcessAPINotes(Decl *D) { if (api_notes::APINotesReader *Reader = APINotes.findAPINotes(D->getLocation())) { if (auto Context = GetContext(Reader)) { + bool isInstanceProperty = + (Property->getPropertyAttributesAsWritten() & + ObjCPropertyDecl::OBJC_PR_class) == 0; if (auto Info = Reader->lookupObjCProperty(*Context, - Property->getName())) { + Property->getName(), + isInstanceProperty)) { ::ProcessAPINotes(*this, Property, *Info); } } diff --git a/clang/lib/Sema/SemaObjCProperty.cpp b/clang/lib/Sema/SemaObjCProperty.cpp index 5e38751f44a50..a03b320a3d3db 100644 --- a/clang/lib/Sema/SemaObjCProperty.cpp +++ b/clang/lib/Sema/SemaObjCProperty.cpp @@ -636,8 +636,6 @@ ObjCPropertyDecl *Sema::CreatePropertyDecl(Scope *S, PDecl->setInvalidDecl(); } - ProcessDeclAttributes(S, PDecl, FD.D); - // Regardless of setter/getter attribute, we save the default getter/setter // selector names in anticipation of declaration of setter/getter methods. PDecl->setGetterName(GetterSel); @@ -645,6 +643,8 @@ ObjCPropertyDecl *Sema::CreatePropertyDecl(Scope *S, PDecl->setPropertyAttributesAsWritten( makePropertyAttributesAsWritten(AttributesAsWritten)); + ProcessDeclAttributes(S, PDecl, FD.D); + if (Attributes & ObjCDeclSpec::DQ_PR_readonly) PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_readonly); diff --git a/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes index d251491ed43db..52336df400b41 100644 --- a/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes @@ -20,6 +20,14 @@ Classes: AvailabilityMsg: "wouldn't work anyway" - Name: internalProperty Nullability: N + - Name: nonnullAInstance + PropertyKind: Instance + Nullability: N + - Name: nonnullAClass + PropertyKind: Class + Nullability: N + - Name: nonnullABoth + Nullability: N - Name: B Availability: none AvailabilityMsg: "just don't" diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes index a585ca5f4df3b..ade66a10a932e 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes @@ -12,8 +12,17 @@ Classes: Nullability: [ N, S ] Properties: - Name: intValue + PropertyKind: Instance Availability: none AvailabilityMsg: "wouldn't work anyway" + - Name: nonnullAInstance + PropertyKind: Instance + Nullability: N + - Name: nonnullAClass + PropertyKind: Class + Nullability: N + - Name: nonnullABoth + Nullability: N - Name: B Availability: none AvailabilityMsg: "just don't" diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h index 5e0d7e0b7ab26..eb25cc0c7fcc8 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h @@ -24,4 +24,15 @@ __attribute__((objc_root_class)) +(instancetype)processInfo; @end +@interface A(NonNullProperties) +@property (nonatomic, readwrite, retain) A *nonnullAInstance; +@property (class, nonatomic, readwrite, retain) A *nonnullAInstance; + +@property (nonatomic, readwrite, retain) A *nonnullAClass; +@property (class, nonatomic, readwrite, retain) A *nonnullAClass; + +@property (nonatomic, readwrite, retain) A *nonnullABoth; +@property (class, nonatomic, readwrite, retain) A *nonnullABoth; +@end + #endif diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 090273479018b..10ac249817d73 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -90,12 +90,14 @@ Classes: SwiftName: 'beginDragginSession(_:event:source:)' Properties: - Name: enclosingScrollView + PropertyKind: Instance Nullability: O Availability: available AvailabilityMsg: '' SwiftPrivate: false SwiftName: enclosing - Name: makeBackingLayer + PropertyKind: Class Nullability: N Availability: available AvailabilityMsg: '' diff --git a/clang/test/APINotes/nullability.m b/clang/test/APINotes/nullability.m index ce4beabb597ff..486b2c5f366cc 100644 --- a/clang/test/APINotes/nullability.m +++ b/clang/test/APINotes/nullability.m @@ -3,12 +3,20 @@ #import - int main() { A *a; [a transform: 0 integer: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + [a setNonnullAInstance: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + [A setNonnullAInstance: 0]; // no warning + + [a setNonnullAClass: 0]; // no warning + [A setNonnullAClass: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + + [a setNonnullABoth: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + [A setNonnullABoth: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + return 0; } diff --git a/clang/test/APINotes/yaml-reader-errors.c b/clang/test/APINotes/yaml-reader-errors.c index 51dfe3aed841f..3e01eaaef03cb 100644 --- a/clang/test/APINotes/yaml-reader-errors.c +++ b/clang/test/APINotes/yaml-reader-errors.c @@ -34,7 +34,7 @@ AvailabilityMsg: iOSOnly Nullability: N Availability: iOS AvailabilityMsg: iOSOnly -# CHECK: duplicate definition of property 'UIFont.familyName' +# CHECK: duplicate definition of instance property 'UIFont.familyName' - Name: familyName Nullability: N Availability: iOS From 16ca907a8533fa32d33899e639140a722e2aa8fd Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 27 Sep 2016 14:42:17 -0700 Subject: [PATCH 086/585] Fix bad code-motion apple-llvm-split-commit: f0171c8ba46246343439cafd8f315549f8b80845 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesFormat.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 8e5210cc319d2..1db2380462f49 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -291,6 +291,4 @@ namespace llvm { }; } -} - #endif // LLVM_CLANG_API_NOTES_FORMAT_H From 22fe14c242f82f06fc367a57a61e6e0a47ea280f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 27 Sep 2016 20:51:36 -0700 Subject: [PATCH 087/585] [API Notes] Use std::make_tuple to unbreak libstdc++ build. apple-llvm-split-commit: b4f0490f10b156be5e5c1cfc5b08c8153a1698b9 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesWriter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 0e0bfaf1bf4ac..b0a46504e58ac 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -1066,8 +1066,8 @@ void APINotesWriter::addObjCProperty(ContextID contextID, StringRef name, bool isInstance, const ObjCPropertyInfo &info) { IdentifierID nameID = Impl.getIdentifier(name); - assert(!Impl.ObjCProperties.count({contextID.Value, nameID, isInstance})); - Impl.ObjCProperties[{contextID.Value, nameID, isInstance}] = info; + assert(!Impl.ObjCProperties.count(std::make_tuple(contextID.Value, nameID, isInstance))); + Impl.ObjCProperties[std::make_tuple(contextID.Value, nameID, isInstance)] = info; } void APINotesWriter::addObjCMethod(ContextID contextID, From b772be2c2a9e29b8c081e197bf0167cbb66ed92c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 27 Sep 2016 20:14:55 -0700 Subject: [PATCH 088/585] [API Notes] Load API notes from framework/header search paths. Introduce a new command-line flag, -fapinotes-modules, that loads module-specific API notes based on where the module map itself was provided, e.g., within the framework (Foo.framework/APINotes/Foo.apinotes) or alongside the headers (ModuleName.apinotes). If found here, these take precedence over the module-specific API notes in the API notes search paths (-iapinotes-modules XXX). Fixes rdar://problem/28511719. apple-llvm-split-commit: 1452c9a3b67ad9bedc9c027e2075b0116e92a979 apple-llvm-split-dir: clang/ --- .../include/clang/APINotes/APINotesManager.h | 15 +++- clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Driver/Options.td | 4 + clang/lib/APINotes/APINotesManager.cpp | 80 +++++++++++++------ clang/lib/Driver/Tools.cpp | 5 ++ clang/lib/Frontend/CompilerInstance.cpp | 14 ++-- clang/lib/Frontend/CompilerInvocation.cpp | 1 + .../APINotes/Inputs/APINotes/SomeKit.apinotes | 48 ----------- .../Inputs/APINotes/SomeOtherKit.apinotes | 8 ++ .../APINotes/SomeKit.apinotes | 6 +- .../APINotes/SomeOtherKit.apinotes | 8 ++ .../Headers/SomeOtherKit.h | 9 +++ .../Modules/module.modulemap | 5 ++ .../{APINotes => Headers}/HeaderLib.apinotes | 5 +- clang/test/APINotes/availability.m | 2 +- clang/test/APINotes/cache.m | 8 +- clang/test/APINotes/cache_pruning.m | 10 +-- clang/test/APINotes/nullability.c | 2 +- clang/test/APINotes/nullability.m | 2 +- clang/test/APINotes/objc_designated_inits.m | 2 +- clang/test/APINotes/search-order.m | 25 ++++++ 21 files changed, 159 insertions(+), 101 deletions(-) delete mode 100644 clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes create mode 100644 clang/test/APINotes/Inputs/APINotes/SomeOtherKit.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/APINotes/SomeOtherKit.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Headers/SomeOtherKit.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Modules/module.modulemap rename clang/test/APINotes/Inputs/{APINotes => Headers}/HeaderLib.apinotes (92%) create mode 100644 clang/test/APINotes/search-order.m diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h index 7f540b9c17dee..e3cc4dff3a1b8 100644 --- a/clang/include/clang/APINotes/APINotesManager.h +++ b/clang/include/clang/APINotes/APINotesManager.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_APINOTES_APINOTESMANAGER_H #include "clang/Basic/SourceLocation.h" +#include "clang/Basic/Module.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/PointerUnion.h" @@ -81,6 +82,14 @@ class APINotesManager { bool loadAPINotes(const DirectoryEntry *HeaderDir, const FileEntry *APINotesFile); + /// Look for API notes relative to the given directory, adjusting + /// for whether it is a framework directory, and with the base + /// filename. + /// + /// This might find either a binary or source API notes. + const FileEntry *findAPINotes(const DirectoryEntry *directory, + bool isFramework, StringRef filename); + /// Attempt to load API notes for the given framework. /// /// \param FrameworkPath The path to the framework. @@ -100,13 +109,15 @@ class APINotesManager { /// Load the API notes for the current module. /// - /// \param moduleName The name of the current module. + /// \param module The current module. + /// \param lookInModule Whether to look inside the module itself. /// \param searchPaths The paths in which we should search for API notes /// for the current module. /// /// \returns the file entry for the API notes file loaded, or nullptr if /// no API notes were found. - const FileEntry *loadCurrentModuleAPINotes(StringRef moduleName, + const FileEntry *loadCurrentModuleAPINotes(const Module *module, + bool lookInModule, ArrayRef searchPaths); /// Find the API notes reader that corresponds to the given source location. diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index eb79111483e9f..c184321c6c038 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -253,6 +253,7 @@ LANGOPT(ApplePragmaPack, 1, 0, "Apple gcc-compatible #pragma pack handling") LANGOPT(RetainCommentsFromSystemHeaders, 1, 0, "retain documentation comments from system headers in the AST") LANGOPT(APINotes, 1, 0, "use external API notes") +LANGOPT(APINotesModules, 1, 0, "use external API notes") LANGOPT(SanitizeAddressFieldPadding, 2, 0, "controls how aggressive is ASan " "field padding (0: none, 1:least " diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 98230d9b9c310..b4dba4b98c37e 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -542,8 +542,12 @@ def fno_profile_use : Flag<["-"], "fno-profile-use">, def fapinotes : Flag<["-"], "fapinotes">, Group, Flags<[CC1Option]>, HelpText<"Enable external API notes support">; +def fapinotes_modules : Flag<["-"], "fapinotes-modules">, Group, + Flags<[CC1Option]>, HelpText<"Enable module-based external API notes support">; def fno_apinotes : Flag<["-"], "fno-apinotes">, Group, Flags<[CC1Option]>, HelpText<"Disable external API notes support">; +def fno_apinotes_modules : Flag<["-"], "fno-apinotes-modules">, Group, + Flags<[CC1Option]>, HelpText<"Disable module-based external API notes support">; def fapinotes_cache_path : Joined<["-"], "fapinotes-cache-path=">, Group, Flags<[DriverOption, CC1Option]>, MetaVarName<"">, HelpText<"Specify the API notes cache path">; diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index 3c3d5fdfc4101..be259663ed031 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -1,4 +1,4 @@ -//===--- APINotesMAnager.cpp - Manage API Notes Files ---------------------===// +//===--- APINotesManager.cpp - Manage API Notes Files ---------------------===// // // The LLVM Compiler Infrastructure // @@ -275,6 +275,39 @@ bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir, return true; } +const FileEntry *APINotesManager::findAPINotes(const DirectoryEntry *directory, + bool isFramework, + StringRef basename) { + FileManager &fileMgr = SourceMgr.getFileManager(); + + llvm::SmallString<128> path; + path += directory->getName(); + + if (isFramework) { + llvm::sys::path::append(path, "APINotes"); + auto apinotesDir = fileMgr.getDirectory(path); + if (!apinotesDir) return nullptr; + + // Find the API notes within this directory. + return findAPINotes(apinotesDir, /*isFramework=*/false, basename); + } + + unsigned pathLen = path.size(); + + // Look for a binary API notes file. + llvm::sys::path::append(path, + llvm::Twine(basename) + "." + BINARY_APINOTES_EXTENSION); + if (const FileEntry *binaryFile = fileMgr.getFile(path)) + return binaryFile; + + path.resize(pathLen); + + // Look for the source API notes file. + llvm::sys::path::append(path, + llvm::Twine(basename) + "." + SOURCE_APINOTES_EXTENSION); + return fileMgr.getFile(path); +} + const DirectoryEntry *APINotesManager::loadFrameworkAPINotes( llvm::StringRef FrameworkPath, llvm::StringRef FrameworkName, @@ -326,38 +359,33 @@ const DirectoryEntry *APINotesManager::loadFrameworkAPINotes( } const FileEntry *APINotesManager::loadCurrentModuleAPINotes( - StringRef moduleName, + const Module *module, + bool lookInModule, ArrayRef searchPaths) { assert(!CurrentModuleReader && "Already loaded API notes for the current module?"); FileManager &fileMgr = SourceMgr.getFileManager(); - - // Look for API notes for this module in the module search paths. - for (const auto &searchPath : searchPaths) { - // First, look for a binary API notes file. - llvm::SmallString<128> apiNotesFilePath; - apiNotesFilePath += searchPath; - llvm::sys::path::append( - apiNotesFilePath, - llvm::Twine(moduleName) + "." + BINARY_APINOTES_EXTENSION); - - // Try to open the binary API Notes file. - if (const FileEntry *binaryAPINotesFile - = fileMgr.getFile(apiNotesFilePath)) { - CurrentModuleReader = loadAPINotes(binaryAPINotesFile); - return CurrentModuleReader ? binaryAPINotesFile : nullptr; + auto moduleName = module->getTopLevelModuleName(); + + // First, look relative to the module itself. + if (lookInModule) { + if (auto file = findAPINotes(module->Directory, module->IsFramework, + moduleName)) { + CurrentModuleReader = loadAPINotes(file); + return CurrentModuleReader ? file : nullptr; } + } - // Try to open the source API Notes file. - apiNotesFilePath = searchPath; - llvm::sys::path::append( - apiNotesFilePath, - llvm::Twine(moduleName) + "." + SOURCE_APINOTES_EXTENSION); - if (const FileEntry *sourceAPINotesFile - = fileMgr.getFile(apiNotesFilePath)) { - CurrentModuleReader = loadAPINotes(sourceAPINotesFile); - return CurrentModuleReader ? sourceAPINotesFile : nullptr; + // Second, look for API notes for this module in the module API + // notes search paths. + for (const auto &searchPath : searchPaths) { + if (auto searchDir = fileMgr.getDirectory(searchPath)) { + if (auto file = findAPINotes(searchDir, /*isFramework=*/false, + moduleName)) { + CurrentModuleReader = loadAPINotes(file); + return CurrentModuleReader ? file : nullptr; + } } } diff --git a/clang/lib/Driver/Tools.cpp b/clang/lib/Driver/Tools.cpp index a6fdec54c4027..47644b12a11a1 100644 --- a/clang/lib/Driver/Tools.cpp +++ b/clang/lib/Driver/Tools.cpp @@ -5412,9 +5412,14 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, if (Args.hasFlag(options::OPT_fapinotes, options::OPT_fno_apinotes, false) || + Args.hasFlag(options::OPT_fapinotes_modules, + options::OPT_fno_apinotes_modules, false) || Args.hasArg(options::OPT_iapinotes_modules)) { if (Args.hasFlag(options::OPT_fapinotes, options::OPT_fno_apinotes, false)) CmdArgs.push_back("-fapinotes"); + if (Args.hasFlag(options::OPT_fapinotes_modules, + options::OPT_fno_apinotes_modules, false)) + CmdArgs.push_back("-fapinotes-modules"); SmallString<128> APINotesCachePath; if (Arg *A = Args.getLastArg(options::OPT_fapinotes_cache_path)) { diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index de8630c15dcf2..9daec8baa6d8b 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -540,17 +540,15 @@ void CompilerInstance::createSema(TranslationUnitKind TUKind, TheSema.reset(new Sema(getPreprocessor(), getASTContext(), getASTConsumer(), TUKind, CompletionConsumer)); - // If we're building a module, notify the API notes manager. - StringRef currentModuleName = getLangOpts().CurrentModule; - if (!currentModuleName.empty()) { + // If we're building a module and are supposed to load API notes, + // notify the API notes manager. + if (auto currentModule = getPreprocessor().getCurrentModule()) { (void)TheSema->APINotes.loadCurrentModuleAPINotes( - currentModuleName, + currentModule, + getLangOpts().APINotesModules, getAPINotesOpts().ModuleSearchPaths); // Check for any attributes we should add to the module if (auto curReader = TheSema->APINotes.getCurrentModuleReader()) { - auto currentModule = getPreprocessor().getCurrentModule(); - assert(currentModule && "how can we have a reader for it?"); - // swift_infer_import_as_member if (curReader->getModuleOptions().SwiftInferImportAsMember) { currentModule->IsSwiftInferImportAsMember = true; @@ -953,7 +951,7 @@ static bool compileModuleImpl(CompilerInstance &ImportingInstance, SourceLocation ImportLoc, Module *Module, StringRef ModuleFileName) { - ModuleMap &ModMap + ModuleMap &ModMap = ImportingInstance.getPreprocessor().getHeaderSearchInfo().getModuleMap(); // Construct a compiler invocation for creating this module. diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 6abf4638c4e3c..63f7af79aed58 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -2001,6 +2001,7 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK, Opts.HalfArgsAndReturns = Args.hasArg(OPT_fallow_half_arguments_and_returns) | Opts.NativeHalfArgsAndReturns; Opts.APINotes = Args.hasArg(OPT_fapinotes); + Opts.APINotesModules = Args.hasArg(OPT_fapinotes_modules); Opts.GNUAsm = !Args.hasArg(OPT_fno_gnu_inline_asm); // __declspec is enabled by default for the PS4 by the driver, and also diff --git a/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes deleted file mode 100644 index 52336df400b41..0000000000000 --- a/clang/test/APINotes/Inputs/APINotes/SomeKit.apinotes +++ /dev/null @@ -1,48 +0,0 @@ -Name: SomeKit -Classes: - - Name: A - Methods: - - Selector: "transform:" - MethodKind: Instance - Availability: none - AvailabilityMsg: "anything but this" - - Selector: "transform:integer:" - MethodKind: Instance - NullabilityOfRet: N - Nullability: [ N, S ] - - Selector: "privateTransform:input:" - MethodKind: Instance - NullabilityOfRet: N - Nullability: [ N, S ] - Properties: - - Name: intValue - Availability: none - AvailabilityMsg: "wouldn't work anyway" - - Name: internalProperty - Nullability: N - - Name: nonnullAInstance - PropertyKind: Instance - Nullability: N - - Name: nonnullAClass - PropertyKind: Class - Nullability: N - - Name: nonnullABoth - Nullability: N - - Name: B - Availability: none - AvailabilityMsg: "just don't" - - Name: C - Methods: - - Selector: "initWithA:" - MethodKind: Instance - DesignatedInit: true - - Name: ProcessInfo - Methods: - - Selector: "processInfo" - MethodKind: Class - FactoryAsInit: C - -Protocols: - - Name: InternalProtocol - Availability: none - AvailabilityMsg: "not for you" diff --git a/clang/test/APINotes/Inputs/APINotes/SomeOtherKit.apinotes b/clang/test/APINotes/Inputs/APINotes/SomeOtherKit.apinotes new file mode 100644 index 0000000000000..ccdc4e15d34d1 --- /dev/null +++ b/clang/test/APINotes/Inputs/APINotes/SomeOtherKit.apinotes @@ -0,0 +1,8 @@ +Name: SomeOtherKit +Classes: + - Name: A + Methods: + - Selector: "methodB" + MethodKind: Instance + Availability: none + AvailabilityMsg: "anything but this" diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes index ade66a10a932e..51b571f67032d 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes @@ -1,7 +1,7 @@ Name: SomeKit Classes: - Name: A - Methods: + Methods: - Selector: "transform:" MethodKind: Instance Availability: none @@ -31,3 +31,7 @@ Classes: - Selector: "initWithA:" MethodKind: Instance DesignatedInit: true +Protocols: + - Name: InternalProtocol + Availability: none + AvailabilityMsg: "not for you" diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/APINotes/SomeOtherKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/APINotes/SomeOtherKit.apinotes new file mode 100644 index 0000000000000..2ad546b8f8bcc --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/APINotes/SomeOtherKit.apinotes @@ -0,0 +1,8 @@ +Name: SomeOtherKit +Classes: + - Name: A + Methods: + - Selector: "methodA" + MethodKind: Instance + Availability: none + AvailabilityMsg: "anything but this" diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Headers/SomeOtherKit.h b/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Headers/SomeOtherKit.h new file mode 100644 index 0000000000000..3911d765230c6 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Headers/SomeOtherKit.h @@ -0,0 +1,9 @@ +#ifndef SOME_OTHER_KIT_H + +__attribute__((objc_root_class)) +@interface A +-(void)methodA; +-(void)methodB; +@end + +#endif diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Modules/module.modulemap b/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Modules/module.modulemap new file mode 100644 index 0000000000000..0aaad92e041ce --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module SomeOtherKit { + umbrella header "SomeOtherKit.h" + export * + module * { export * } +} diff --git a/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes b/clang/test/APINotes/Inputs/Headers/HeaderLib.apinotes similarity index 92% rename from clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes rename to clang/test/APINotes/Inputs/Headers/HeaderLib.apinotes index 9df8c3d5e464c..f1cd086126862 100644 --- a/clang/test/APINotes/Inputs/APINotes/HeaderLib.apinotes +++ b/clang/test/APINotes/Inputs/Headers/HeaderLib.apinotes @@ -1,4 +1,5 @@ Name: HeaderLib +SwiftInferImportAsMember: true Functions: - Name: custom_realloc NullabilityOfRet: N @@ -16,17 +17,15 @@ Functions: NoEscape: true - Position: 1 NoEscape: true - Globals: - Name: global_int Nullability: N - Name: unavailable_global_int Availability: none - Tags: - Name: unavailable_struct Availability: none Typedefs: - Name: unavailable_typedef - Availability: none + Availability: none \ No newline at end of file diff --git a/clang/test/APINotes/availability.m b/clang/test/APINotes/availability.m index 1cfc65862f46a..5537316dcaa01 100644 --- a/clang/test/APINotes/availability.m +++ b/clang/test/APINotes/availability.m @@ -1,5 +1,5 @@ // RUN: rm -rf %t -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #include "HeaderLib.h" #import diff --git a/clang/test/APINotes/cache.m b/clang/test/APINotes/cache.m index 6a2c2f5d17a4d..05fb81c72dcf3 100644 --- a/clang/test/APINotes/cache.m +++ b/clang/test/APINotes/cache.m @@ -1,19 +1,19 @@ // RUN: rm -rf %t/APINotesCache -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fapinotes -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify // Check for the presence of the cached compiled form. // RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" // Run test again to ensure that caching doesn't cause problems. -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fapinotes -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify // Check that the driver provides a default -fapinotes-cache-path= -// RUN: %clang -fsyntax-only -fapinotes -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -### 2>&1 | FileCheck --check-prefix=CHECK-DEFAULT-PATH %s +// RUN: %clang -fsyntax-only -fapinotes -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -### 2>&1 | FileCheck --check-prefix=CHECK-DEFAULT-PATH %s // CHECK-DEFAULT-PATH: -fapinotes-cache-path={{.*}}org.llvm.clang/APINotesCache // Check that the driver passes through a provided -fapinotes-cache-path= -// RUN: %clang -fsyntax-only -fapinotes -fapinotes-cache-path=/wobble -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -### 2>&1 | FileCheck --check-prefix=CHECK-PATH %s +// RUN: %clang -fsyntax-only -fapinotes -fapinotes-modules -fapinotes-cache-path=/wobble -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -### 2>&1 | FileCheck --check-prefix=CHECK-PATH %s // CHECK-PATH: -fapinotes-cache-path=/wobble #include "HeaderLib.h" diff --git a/clang/test/APINotes/cache_pruning.m b/clang/test/APINotes/cache_pruning.m index 54b0c647aef3e..1a36570bb43f0 100644 --- a/clang/test/APINotes/cache_pruning.m +++ b/clang/test/APINotes/cache_pruning.m @@ -4,7 +4,7 @@ // RUN: rm -rf %t/APINotesCache // Run Clang. This should generated the cached versions of both and a timestamp. -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DINCLUDE_HEADERLIB +// RUN: %clang_cc1 -fapinotes -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DINCLUDE_HEADERLIB // RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "APINotes.timestamp" @@ -12,7 +12,7 @@ // Set the timestamp back a very long time. We should try to prune, // but nothing gets pruned because the API Notes files are new enough. // RUN: touch -m -a -t 201101010000 %t/APINotes.timestamp -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -fapinotes -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "APINotes.timestamp" @@ -21,7 +21,7 @@ // This shouldn't prune anything, because the timestamp has been updated, so // the pruning mechanism won't fire. // RUN: find %t/APINotesCache -name APINotes-*.apinotesc | xargs touch -a -t 201101010000 -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -fapinotes -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "APINotes.timestamp" @@ -30,13 +30,13 @@ // HeaderLib file, because the pruning mechanism should fire and // HeaderLib is both old and not used. // RUN: touch -m -a -t 201101010000 %t/APINotesCache/APINotes.timestamp -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -fapinotes -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: ls %t/APINotesCache | not grep "APINotes-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "APINotes.timestamp" // Run Clang. This should generated the cached versions of both and a timestamp. -// RUN: %clang_cc1 -fapinotes -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DINCLUDE_HEADERLIB +// RUN: %clang_cc1 -fapinotes -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DINCLUDE_HEADERLIB // RUN: ls %t/APINotesCache | grep "APINotes-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "SomeKit-.*.apinotesc" // RUN: ls %t/APINotesCache | grep "APINotes.timestamp" diff --git a/clang/test/APINotes/nullability.c b/clang/test/APINotes/nullability.c index 1d5939bf92e2c..36507f1b9724b 100644 --- a/clang/test/APINotes/nullability.c +++ b/clang/test/APINotes/nullability.c @@ -1,5 +1,5 @@ // RUN: rm -rf %t && mkdir -p %t -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #include "HeaderLib.h" diff --git a/clang/test/APINotes/nullability.m b/clang/test/APINotes/nullability.m index 486b2c5f366cc..377256f0bec96 100644 --- a/clang/test/APINotes/nullability.m +++ b/clang/test/APINotes/nullability.m @@ -1,5 +1,5 @@ // RUN: rm -rf %t && mkdir -p %t -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #import diff --git a/clang/test/APINotes/objc_designated_inits.m b/clang/test/APINotes/objc_designated_inits.m index bbb50ba1a74bc..1df8cf80edd0e 100644 --- a/clang/test/APINotes/objc_designated_inits.m +++ b/clang/test/APINotes/objc_designated_inits.m @@ -1,5 +1,5 @@ // RUN: rm -rf %t && mkdir -p %t -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify #include "HeaderLib.h" #import diff --git a/clang/test/APINotes/search-order.m b/clang/test/APINotes/search-order.m new file mode 100644 index 0000000000000..2c667be38d29b --- /dev/null +++ b/clang/test/APINotes/search-order.m @@ -0,0 +1,25 @@ +// RUN: rm -rf %t && mkdir -p %t + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DFROM_FRAMEWORK=1 -verify + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DFROM_SEARCH_PATH=1 -verify + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -iapinotes-modules %S/Inputs/APINotes -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -DFROM_FRAMEWORK=1 -verify + +@import SomeOtherKit; + +void test(A *a) { +#if FROM_FRAMEWORK + [a methodA]; // expected-error{{unavailable}} + [a methodB]; + + // expected-note@SomeOtherKit/SomeOtherKit.h:5{{'methodA' has been explicitly marked unavailable here}} +#elif FROM_SEARCH_PATH + [a methodA]; + [a methodB]; // expected-error{{unavailable}} + + // expected-note@SomeOtherKit/SomeOtherKit.h:6{{'methodB' has been explicitly marked unavailable here}} +#else +# error Not something we need to test +#endif +} From 10630737443b3bc2c8fe4ad9ebee9ebfa5000dfe Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 27 Sep 2016 21:48:15 -0700 Subject: [PATCH 089/585] [API Notes] Load both public and private API notes when they are present. Finishes rdar://problem/28511719. apple-llvm-split-commit: f3843ce220dc99d4ec723812950774e80b06c4e8 apple-llvm-split-dir: clang/ --- .../include/clang/APINotes/APINotesManager.h | 40 ++++--- clang/lib/APINotes/APINotesManager.cpp | 106 +++++++++++------- clang/lib/Frontend/CompilerInstance.cpp | 5 +- clang/lib/Sema/SemaAPINotes.cpp | 27 ++--- .../APINotes/SomeKit.apinotes | 4 - clang/test/APINotes/cache.m | 1 - clang/test/APINotes/nullability.m | 2 + 7 files changed, 103 insertions(+), 82 deletions(-) diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h index e3cc4dff3a1b8..a300c14ee9dd3 100644 --- a/clang/include/clang/APINotes/APINotesManager.h +++ b/clang/include/clang/APINotes/APINotesManager.h @@ -55,8 +55,11 @@ class APINotesManager { /// source file from which an entity was declared. bool ImplicitAPINotes; - /// The API notes reader for the current module. - std::unique_ptr CurrentModuleReader; + /// API notes readers for the current module. + /// + /// There can be up to two of these, one for public headers and one + /// for private headers. + APINotesReader *CurrentModuleReaders[2] = { nullptr, nullptr }; /// Whether we have already pruned the API notes cache. bool PrunedCache; @@ -82,13 +85,12 @@ class APINotesManager { bool loadAPINotes(const DirectoryEntry *HeaderDir, const FileEntry *APINotesFile); - /// Look for API notes relative to the given directory, adjusting - /// for whether it is a framework directory, and with the base - /// filename. + /// Look for API notes in the given directory. /// /// This might find either a binary or source API notes. - const FileEntry *findAPINotes(const DirectoryEntry *directory, - bool isFramework, StringRef filename); + const FileEntry *findAPINotesFile(const DirectoryEntry *directory, + StringRef filename, + bool wantPublic = true); /// Attempt to load API notes for the given framework. /// @@ -114,18 +116,20 @@ class APINotesManager { /// \param searchPaths The paths in which we should search for API notes /// for the current module. /// - /// \returns the file entry for the API notes file loaded, or nullptr if - /// no API notes were found. - const FileEntry *loadCurrentModuleAPINotes(const Module *module, - bool lookInModule, - ArrayRef searchPaths); - - /// Find the API notes reader that corresponds to the given source location. - APINotesReader *findAPINotes(SourceLocation Loc); - - APINotesReader *getCurrentModuleReader() { - return CurrentModuleReader.get(); + /// \returns true if API notes were successfully loaded, \c false otherwise. + bool loadCurrentModuleAPINotes(const Module *module, + bool lookInModule, + ArrayRef searchPaths); + + /// Retrieve the set of API notes readers for the current module. + ArrayRef getCurrentModuleReaders() const { + unsigned numReaders = static_cast(CurrentModuleReaders[0] != nullptr) + + static_cast(CurrentModuleReaders[1] != nullptr); + return llvm::makeArrayRef(CurrentModuleReaders).slice(0, numReaders); } + + /// Find the API notes readers that correspond to the given source location. + llvm::SmallVector findAPINotes(SourceLocation Loc); }; } // end namespace api_notes diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index be259663ed031..78ee755655a4d 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -63,6 +63,9 @@ APINotesManager::~APINotesManager() { delete reader; } } + + delete CurrentModuleReaders[0]; + delete CurrentModuleReaders[1]; } /// \brief Write a new timestamp file with the given path. @@ -275,36 +278,31 @@ bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir, return true; } -const FileEntry *APINotesManager::findAPINotes(const DirectoryEntry *directory, - bool isFramework, - StringRef basename) { +const FileEntry *APINotesManager::findAPINotesFile(const DirectoryEntry *directory, + StringRef basename, + bool wantPublic) { FileManager &fileMgr = SourceMgr.getFileManager(); llvm::SmallString<128> path; path += directory->getName(); - if (isFramework) { - llvm::sys::path::append(path, "APINotes"); - auto apinotesDir = fileMgr.getDirectory(path); - if (!apinotesDir) return nullptr; - - // Find the API notes within this directory. - return findAPINotes(apinotesDir, /*isFramework=*/false, basename); - } - unsigned pathLen = path.size(); + StringRef basenameSuffix = ""; + if (!wantPublic) basenameSuffix = "_private"; + // Look for a binary API notes file. llvm::sys::path::append(path, - llvm::Twine(basename) + "." + BINARY_APINOTES_EXTENSION); + llvm::Twine(basename) + basenameSuffix + "." + BINARY_APINOTES_EXTENSION); if (const FileEntry *binaryFile = fileMgr.getFile(path)) return binaryFile; + // Go back to the original path. path.resize(pathLen); // Look for the source API notes file. llvm::sys::path::append(path, - llvm::Twine(basename) + "." + SOURCE_APINOTES_EXTENSION); + llvm::Twine(basename) + basenameSuffix + "." + SOURCE_APINOTES_EXTENSION); return fileMgr.getFile(path); } @@ -358,11 +356,11 @@ const DirectoryEntry *APINotesManager::loadFrameworkAPINotes( return HeaderDir; } -const FileEntry *APINotesManager::loadCurrentModuleAPINotes( +bool APINotesManager::loadCurrentModuleAPINotes( const Module *module, bool lookInModule, ArrayRef searchPaths) { - assert(!CurrentModuleReader && + assert(!CurrentModuleReaders[0] && "Already loaded API notes for the current module?"); FileManager &fileMgr = SourceMgr.getFileManager(); @@ -370,48 +368,76 @@ const FileEntry *APINotesManager::loadCurrentModuleAPINotes( // First, look relative to the module itself. if (lookInModule) { - if (auto file = findAPINotes(module->Directory, module->IsFramework, - moduleName)) { - CurrentModuleReader = loadAPINotes(file); - return CurrentModuleReader ? file : nullptr; + bool foundAny = false; + unsigned numReaders = 0; + + // Local function to try loading an API notes file in the given directory. + auto tryAPINotes = [&](const DirectoryEntry *dir, bool wantPublic) { + if (auto file = findAPINotesFile(dir, moduleName, wantPublic)) { + foundAny = true; + + // Try to load the API notes file. + CurrentModuleReaders[numReaders] = loadAPINotes(file).release(); + if (CurrentModuleReaders[numReaders]) + ++numReaders; + } + }; + + if (module->IsFramework) { + // For frameworks, we search in the "APINotes" subdirectory. + llvm::SmallString<128> path; + path += module->Directory->getName(); + llvm::sys::path::append(path, "APINotes"); + if (auto apinotesDir = fileMgr.getDirectory(path)) { + tryAPINotes(apinotesDir, /*wantPublic=*/true); + tryAPINotes(apinotesDir, /*wantPublic=*/false); + } + } else { + tryAPINotes(module->Directory, /*wantPublic=*/true); + tryAPINotes(module->Directory, /*wantPublic=*/false); } + + if (foundAny) + return numReaders > 0; } // Second, look for API notes for this module in the module API // notes search paths. for (const auto &searchPath : searchPaths) { if (auto searchDir = fileMgr.getDirectory(searchPath)) { - if (auto file = findAPINotes(searchDir, /*isFramework=*/false, - moduleName)) { - CurrentModuleReader = loadAPINotes(file); - return CurrentModuleReader ? file : nullptr; + if (auto file = findAPINotesFile(searchDir, moduleName)) { + CurrentModuleReaders[0] = loadAPINotes(file).release(); + return !getCurrentModuleReaders().empty(); } } } // Didn't find any API notes. - return nullptr; + return false; } -APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) { - // If there is a reader for the current module, return it. - if (CurrentModuleReader) return CurrentModuleReader.get(); +llvm::SmallVector APINotesManager::findAPINotes(SourceLocation Loc) { + llvm::SmallVector Results; + + // If there are readers for the current module, return them. + if (!getCurrentModuleReaders().empty()) { + Results.append(getCurrentModuleReaders().begin(), getCurrentModuleReaders().end()); + return Results; + } // If we're not allowed to implicitly load API notes files, we're done. - if (!ImplicitAPINotes) return nullptr; + if (!ImplicitAPINotes) return Results; // If we don't have source location information, we're done. - if (Loc.isInvalid()) return nullptr; + if (Loc.isInvalid()) return Results; // API notes are associated with the expansion location. Retrieve the // file for this location. SourceLocation ExpansionLoc = SourceMgr.getExpansionLoc(Loc); FileID ID = SourceMgr.getFileID(ExpansionLoc); - if (ID.isInvalid()) - return nullptr; + if (ID.isInvalid()) return Results; const FileEntry *File = SourceMgr.getFileEntryForID(ID); - if (!File) - return nullptr; + if (!File) return Results; // Look for API notes in the directory corresponding to this file, or one of // its its parent directories. @@ -420,7 +446,6 @@ APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) { llvm::SetVector, llvm::SmallPtrSet> DirsVisited; - APINotesReader *Result = nullptr; do { // Look for an API notes reader for this header search directory. auto Known = Readers.find(Dir); @@ -437,7 +462,8 @@ APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) { } // We have the answer. - Result = Known->second.dyn_cast(); + if (auto Reader = Known->second.dyn_cast()) + Results.push_back(Reader); break; } @@ -473,7 +499,8 @@ APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) { } // Grab the result. - Result = Readers[Dir].dyn_cast();; + if (auto Reader = Readers[Dir].dyn_cast()) + Results.push_back(Reader); break; } } else { @@ -489,7 +516,8 @@ APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) { if (const FileEntry *APINotesFile = FileMgr.getFile(APINotesPath)) { if (!loadAPINotes(Dir, APINotesFile)) { ++NumHeaderAPINotes; - Result = Readers[Dir].dyn_cast(); + if (auto Reader = Readers[Dir].dyn_cast()) + Results.push_back(Reader); break; } } @@ -519,5 +547,5 @@ APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) { Readers[Visited] = Dir; } - return Result; + return Results; } diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 9daec8baa6d8b..43d89485d8ab5 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -548,10 +548,11 @@ void CompilerInstance::createSema(TranslationUnitKind TUKind, getLangOpts().APINotesModules, getAPINotesOpts().ModuleSearchPaths); // Check for any attributes we should add to the module - if (auto curReader = TheSema->APINotes.getCurrentModuleReader()) { + for (auto reader : TheSema->APINotes.getCurrentModuleReaders()) { // swift_infer_import_as_member - if (curReader->getModuleOptions().SwiftInferImportAsMember) { + if (reader->getModuleOptions().SwiftInferImportAsMember) { currentModule->IsSwiftInferImportAsMember = true; + break; } } } diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index a0c5ca88236ca..29861d140f242 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -327,8 +327,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (D->getDeclContext()->isFileContext()) { // Global variables. if (auto VD = dyn_cast(D)) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupGlobalVariable(VD->getName())) { ::ProcessAPINotes(*this, VD, *Info); } @@ -340,8 +339,7 @@ void Sema::ProcessAPINotes(Decl *D) { // Global functions. if (auto FD = dyn_cast(D)) { if (FD->getDeclName().isIdentifier()) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupGlobalFunction(FD->getName())) { ::ProcessAPINotes(*this, FD, *Info); } @@ -353,8 +351,7 @@ void Sema::ProcessAPINotes(Decl *D) { // Objective-C classes. if (auto Class = dyn_cast(D)) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupObjCClass(Class->getName())) { ::ProcessAPINotes(*this, Class, Info->second); } @@ -365,8 +362,7 @@ void Sema::ProcessAPINotes(Decl *D) { // Objective-C protocols. if (auto Protocol = dyn_cast(D)) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupObjCProtocol(Protocol->getName())) { ::ProcessAPINotes(*this, Protocol, Info->second); } @@ -377,8 +373,7 @@ void Sema::ProcessAPINotes(Decl *D) { // Tags if (auto Tag = dyn_cast(D)) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupTag(Tag->getName())) { ::ProcessAPINotes(*this, Tag, *Info); } @@ -389,8 +384,7 @@ void Sema::ProcessAPINotes(Decl *D) { // Typedefs if (auto Typedef = dyn_cast(D)) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupTypedef(Typedef->getName())) { ::ProcessAPINotes(*this, Typedef, *Info); } @@ -405,8 +399,7 @@ void Sema::ProcessAPINotes(Decl *D) { // Enumerators. if (D->getDeclContext()->getRedeclContext()->isFileContext()) { if (auto EnumConstant = dyn_cast(D)) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupEnumConstant(EnumConstant->getName())) { ::ProcessAPINotes(*this, EnumConstant, *Info); } @@ -461,8 +454,7 @@ void Sema::ProcessAPINotes(Decl *D) { // Objective-C methods. if (auto Method = dyn_cast(D)) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Context = GetContext(Reader)) { // Map the selector. Selector Sel = Method->getSelector(); @@ -488,8 +480,7 @@ void Sema::ProcessAPINotes(Decl *D) { // Objective-C properties. if (auto Property = dyn_cast(D)) { - if (api_notes::APINotesReader *Reader - = APINotes.findAPINotes(D->getLocation())) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Context = GetContext(Reader)) { bool isInstanceProperty = (Property->getPropertyAttributesAsWritten() & diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes index 51b571f67032d..e79a210e39aaf 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes @@ -31,7 +31,3 @@ Classes: - Selector: "initWithA:" MethodKind: Instance DesignatedInit: true -Protocols: - - Name: InternalProtocol - Availability: none - AvailabilityMsg: "not for you" diff --git a/clang/test/APINotes/cache.m b/clang/test/APINotes/cache.m index 05fb81c72dcf3..b87bdf14fecc8 100644 --- a/clang/test/APINotes/cache.m +++ b/clang/test/APINotes/cache.m @@ -30,4 +30,3 @@ int main() { return 0; } - diff --git a/clang/test/APINotes/nullability.m b/clang/test/APINotes/nullability.m index 377256f0bec96..fc149b465ea35 100644 --- a/clang/test/APINotes/nullability.m +++ b/clang/test/APINotes/nullability.m @@ -17,6 +17,8 @@ int main() { [a setNonnullABoth: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} [A setNonnullABoth: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + [a setInternalProperty: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + return 0; } From 16a0f3116a9620308ae602e31af23429bea282f3 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 27 Sep 2016 23:15:55 -0700 Subject: [PATCH 090/585] [API Notes] Ensure that modules get rebuilt when API notes change. The approach here is fairly simple: treat API notes source files as "sources" in the source manager, so that module files automatically check whether they have changed and rebuild when they do. Also, have API notes files compiled into the cache keep track of the size and modification time of the API notes source files from which they were generated. Fixes rdar://problem/25639554. apple-llvm-split-commit: b07d2eed96ce5bba8cba80f236e87db776a48c63 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 4 ++ clang/include/clang/APINotes/APINotesWriter.h | 8 ++- .../clang/APINotes/APINotesYAMLCompiler.h | 4 ++ clang/lib/APINotes/APINotesFormat.h | 11 +++- clang/lib/APINotes/APINotesManager.cpp | 31 +++++++---- clang/lib/APINotes/APINotesReader.cpp | 13 +++++ clang/lib/APINotes/APINotesWriter.cpp | 14 ++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 20 ++++--- clang/test/APINotes/module-cache.m | 55 +++++++++++++++++++ clang/tools/driver/apinotes_main.cpp | 2 +- 10 files changed, 137 insertions(+), 25 deletions(-) create mode 100644 clang/test/APINotes/module-cache.m diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index cc6dafb863f65..c64d6c31624bb 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -50,6 +50,10 @@ class APINotesReader { /// notes. StringRef getModuleName() const; + /// Retrieve the size and modification time of the source file from + /// which this API notes file was created, if known. + Optional> getSourceFileSizeAndModTime() const; + /// Retrieve the module options ModuleOptions getModuleOptions() const; diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h index cc93a5f5899b6..4bf3ce1371641 100644 --- a/clang/include/clang/APINotes/APINotesWriter.h +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -23,6 +23,9 @@ namespace llvm { } namespace clang { + +class FileEntry; + namespace api_notes { /// A class that writes API notes data to a binary representation that can be @@ -32,8 +35,9 @@ class APINotesWriter { Implementation &Impl; public: - /// Create a new API notes writer with the given module name. - APINotesWriter(StringRef moduleName); + /// Create a new API notes writer with the given module name and + /// (optional) source file. + APINotesWriter(StringRef moduleName, const FileEntry *sourceFile); ~APINotesWriter(); APINotesWriter(const APINotesWriter &) = delete; diff --git a/clang/include/clang/APINotes/APINotesYAMLCompiler.h b/clang/include/clang/APINotes/APINotesYAMLCompiler.h index f459e079d25bd..508da65993e7d 100644 --- a/clang/include/clang/APINotes/APINotesYAMLCompiler.h +++ b/clang/include/clang/APINotes/APINotesYAMLCompiler.h @@ -23,6 +23,9 @@ namespace llvm { } namespace clang { + +class FileEntry; + namespace api_notes { enum class ActionType { @@ -42,6 +45,7 @@ namespace api_notes { /// Converts API notes from YAML format to binary format. bool compileAPINotes(llvm::StringRef yamlInput, + const FileEntry *sourceFile, llvm::raw_ostream &os, OSType targetOS, llvm::SourceMgr::DiagHandlerTy diagHandler = nullptr, diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 1db2380462f49..542f90831a580 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 14; // Objective-C class properties +const uint16_t VERSION_MINOR = 15; // source file info using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; @@ -106,7 +106,8 @@ namespace control_block { enum { METADATA = 1, MODULE_NAME = 2, - MODULE_OPTIONS = 3 + MODULE_OPTIONS = 3, + SOURCE_FILE = 4, }; using MetadataLayout = BCRecordLayout< @@ -124,6 +125,12 @@ namespace control_block { MODULE_OPTIONS, BCFixed<1> // SwiftInferImportAsMember >; + + using SourceFileLayout = BCRecordLayout< + SOURCE_FILE, + BCVBR<16>, // file size + BCVBR<16> // creation time + >; } namespace identifier_block { diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index 78ee755655a4d..8dc337827e6c9 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -181,11 +181,16 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { /*cacheFailure=*/false)) { // Load the file contents. if (auto buffer = fileMgr.getBufferForFile(compiledFile)) { - // Make sure the file is up-to-date. - if (compiledFile->getModificationTime() - >= apiNotesFile->getModificationTime()) { - // Load the file. - if (auto reader = APINotesReader::get(std::move(buffer.get()))) { + // Load the file. + if (auto reader = APINotesReader::get(std::move(buffer.get()))) { + bool outOfDate = false; + if (auto sizeAndModTime = reader->getSourceFileSizeAndModTime()) { + if (sizeAndModTime->first != apiNotesFile->getSize() || + sizeAndModTime->second != apiNotesFile->getModificationTime()) + outOfDate = true; + } + + if (!outOfDate) { // Success. ++NumBinaryCacheHits; return reader; @@ -202,13 +207,15 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { } // Open the source file. - auto buffer = fileMgr.getBufferForFile(apiNotesFile); - if (!buffer) return nullptr; + auto sourceFileID = SourceMgr.createFileID(apiNotesFile, SourceLocation(), SrcMgr::C_User); + auto sourceBuffer = SourceMgr.getBuffer(sourceFileID, SourceLocation()); + if (!sourceBuffer) return nullptr; // Compile the API notes source into a buffer. // FIXME: Either propagate OSType through or, better yet, improve the binary // APINotes format to maintain complete availability information. llvm::SmallVector apiNotesBuffer; + std::unique_ptr compiledBuffer; { SourceMgrAdapter srcMgrAdapter(SourceMgr, SourceMgr.getDiagnostics(), diag::err_apinotes_message, @@ -216,7 +223,8 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { diag::note_apinotes_message, apiNotesFile); llvm::raw_svector_ostream OS(apiNotesBuffer); - if (api_notes::compileAPINotes(buffer.get()->getBuffer(), + if (api_notes::compileAPINotes(sourceBuffer->getBuffer(), + SourceMgr.getFileEntryForID(sourceFileID), OS, api_notes::OSType::Absent, srcMgrAdapter.getDiagHandler(), @@ -224,7 +232,7 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { return nullptr; // Make a copy of the compiled form into the buffer. - buffer = llvm::MemoryBuffer::getMemBufferCopy( + compiledBuffer = llvm::MemoryBuffer::getMemBufferCopy( StringRef(apiNotesBuffer.data(), apiNotesBuffer.size())); } @@ -247,7 +255,8 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { bool hadError; { llvm::raw_fd_ostream out(temporaryFD, /*shouldClose=*/true); - out.write(buffer.get()->getBufferStart(), buffer.get()->getBufferSize()); + out.write(compiledBuffer.get()->getBufferStart(), + compiledBuffer.get()->getBufferSize()); out.flush(); hadError = out.has_error(); @@ -261,7 +270,7 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { } // Load the binary form we just compiled. - auto reader = APINotesReader::get(std::move(*buffer)); + auto reader = APINotesReader::get(std::move(compiledBuffer)); assert(reader && "Could not load the API notes we just generated?"); return reader; } diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 2b49125fffc01..9618e70d5f84a 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -586,6 +586,10 @@ class APINotesReader::Implementation { /// The name of the module that we read from the control block. std::string ModuleName; + // The size and modification time of the source file from + // which this API notes file was created, if known. + Optional> SourceFileSizeAndModTime; + /// Various options and attributes for the module ModuleOptions ModuleOpts; @@ -766,6 +770,10 @@ bool APINotesReader::Implementation::readControlBlock( ModuleOpts.SwiftInferImportAsMember = (scratch.front() & 1) != 0; break; + case control_block::SOURCE_FILE: + SourceFileSizeAndModTime = { scratch[0], scratch[1] }; + break; + default: // Unknown metadata record, possibly for use by a future version of the // module format. @@ -1471,6 +1479,11 @@ StringRef APINotesReader::getModuleName() const { return Impl.ModuleName; } +Optional> +APINotesReader::getSourceFileSizeAndModTime() const { + return Impl.SourceFileSizeAndModTime; +} + ModuleOptions APINotesReader::getModuleOptions() const { return Impl.ModuleOpts; } diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index b0a46504e58ac..2df5e76f7c8d4 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -15,6 +15,7 @@ //===----------------------------------------------------------------------===// #include "clang/APINotes/APINotesWriter.h" #include "APINotesFormat.h" +#include "clang/Basic/FileManager.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/SmallString.h" @@ -43,6 +44,10 @@ class APINotesWriter::Implementation { /// The name of the module std::string ModuleName; + /// The source file from which this binary representation was + /// created, if known. + const FileEntry *SourceFile; + bool SwiftInferImportAsMember = false; /// Information about Objective-C contexts (classes or protocols). @@ -222,6 +227,12 @@ void APINotesWriter::Implementation::writeControlBlock( control_block::ModuleOptionsLayout moduleOptions(writer); moduleOptions.emit(ScratchRecord, SwiftInferImportAsMember); } + + if (SourceFile) { + control_block::SourceFileLayout sourceFile(writer); + sourceFile.emit(ScratchRecord, SourceFile->getSize(), + SourceFile->getModificationTime()); + } } namespace { @@ -1006,10 +1017,11 @@ void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &os) { os.flush(); } -APINotesWriter::APINotesWriter(StringRef moduleName) +APINotesWriter::APINotesWriter(StringRef moduleName, const FileEntry *sourceFile) : Impl(*new Implementation) { Impl.ModuleName = moduleName; + Impl.SourceFile = sourceFile; } APINotesWriter::~APINotesWriter() { diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index b9640954f58fa..0d30731194c41 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -501,6 +501,7 @@ namespace { class YAMLConverter { const Module &TheModule; + const FileEntry *SourceFile; APINotesWriter *Writer; OSType TargetOS; llvm::raw_ostream &OS; @@ -519,11 +520,12 @@ namespace { public: YAMLConverter(const Module &module, - OSType targetOS, - llvm::raw_ostream &os, - llvm::SourceMgr::DiagHandlerTy diagHandler, - void *diagHandlerCtxt) : - TheModule(module), Writer(0), TargetOS(targetOS), OS(os), + const FileEntry *sourceFile, + OSType targetOS, + llvm::raw_ostream &os, + llvm::SourceMgr::DiagHandlerTy diagHandler, + void *diagHandlerCtxt) : + TheModule(module), SourceFile(sourceFile), Writer(0), TargetOS(targetOS), OS(os), DiagHandler(diagHandler), DiagHandlerCtxt(diagHandlerCtxt), ErrorOccured(false) {} @@ -739,7 +741,7 @@ namespace { // Set up the writer. // FIXME: This is kindof ugly. - APINotesWriter writer(TheModule.Name); + APINotesWriter writer(TheModule.Name, SourceFile); Writer = &writer; // Write all classes. @@ -877,13 +879,14 @@ namespace { } static bool compile(const Module &module, + const FileEntry *sourceFile, llvm::raw_ostream &os, api_notes::OSType targetOS, llvm::SourceMgr::DiagHandlerTy diagHandler, void *diagHandlerCtxt){ using namespace api_notes; - YAMLConverter c(module, targetOS, os, diagHandler, diagHandlerCtxt); + YAMLConverter c(module, sourceFile, targetOS, os, diagHandler, diagHandlerCtxt); return c.convertModule(); } @@ -905,6 +908,7 @@ static void printDiagnostic(const llvm::SMDiagnostic &diag, void *context) { } bool api_notes::compileAPINotes(StringRef yamlInput, + const FileEntry *sourceFile, llvm::raw_ostream &os, OSType targetOS, llvm::SourceMgr::DiagHandlerTy diagHandler, @@ -918,7 +922,7 @@ bool api_notes::compileAPINotes(StringRef yamlInput, if (parseAPINotes(yamlInput, module, diagHandler, diagHandlerCtxt)) return true; - return compile(module, os, targetOS, diagHandler, diagHandlerCtxt); + return compile(module, sourceFile, os, targetOS, diagHandler, diagHandlerCtxt); } namespace { diff --git a/clang/test/APINotes/module-cache.m b/clang/test/APINotes/module-cache.m new file mode 100644 index 0000000000000..dce003b3c7f86 --- /dev/null +++ b/clang/test/APINotes/module-cache.m @@ -0,0 +1,55 @@ +// RUN: rm -rf %t + +// Set up a directory with API notes +// RUN: mkdir -p %t/APINotes +// RUN: cp %S/Inputs/APINotes/SomeOtherKit.apinotes %t/APINotes/SomeOtherKit.apinotes + +// First build: check that 'methodB' is unavailable but 'methodA' is available. +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -Rmodule-build -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %t/APINotes -fapinotes-cache-path=%t/APINotesCache -F %S/Inputs/Frameworks %s > %t/before.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-METHODB %s < %t/before.log +// RUN: FileCheck -check-prefix=CHECK-REBUILD %s < %t/before.log +// RUN: FileCheck -check-prefix=CHECK-ONE-ERROR %s < %t/before.log + +// Do it again; now we're using caches. +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -Rmodule-build -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %t/APINotes -fapinotes-cache-path=%t/APINotesCache -F %S/Inputs/Frameworks %s > %t/before.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-METHODB %s < %t/before.log +// RUN: FileCheck -check-prefix=CHECK-WITHOUT-REBUILD %s < %t/before.log +// RUN: FileCheck -check-prefix=CHECK-ONE-ERROR %s < %t/before.log + +// Change the API notes file. +// RUN: echo ' - Selector: "methodA"' >> %t/APINotes/SomeOtherKit.apinotes +// RUN: echo ' MethodKind: Instance' >> %t/APINotes/SomeOtherKit.apinotes +// RUN: echo ' Availability: none' >> %t/APINotes/SomeOtherKit.apinotes +// RUN: echo ' AvailabilityMsg: "not here either"' >> %t/APINotes/SomeOtherKit.apinotes + +// Build again: check that both methods are now unavailable and that the module rebuilt. +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -Rmodule-build -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %t/APINotes -fapinotes-cache-path=%t/APINotesCache -F %S/Inputs/Frameworks %s > %t/after.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-METHODA %s < %t/after.log +// RUN: FileCheck -check-prefix=CHECK-METHODB %s < %t/after.log +// RUN: FileCheck -check-prefix=CHECK-REBUILD %s < %t/after.log +// RUN: FileCheck -check-prefix=CHECK-TWO-ERRORS %s < %t/after.log + +// Run the build again: check that both methods are now unavailable +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -Rmodule-build -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %t/APINotes -fapinotes-cache-path=%t/APINotesCache -F %S/Inputs/Frameworks %s > %t/after.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-METHODA %s < %t/after.log +// RUN: FileCheck -check-prefix=CHECK-METHODB %s < %t/after.log +// RUN: FileCheck -check-prefix=CHECK-WITHOUT-REBUILD %s < %t/after.log +// RUN: FileCheck -check-prefix=CHECK-TWO-ERRORS %s < %t/after.log + +@import SomeOtherKit; + +void test(A *a) { + // CHECK-METHODA: error: 'methodA' is unavailable: not here either + [a methodA]; + + // CHECK-METHODB: error: 'methodB' is unavailable: anything but this + [a methodB]; +} + +// CHECK-REBUILD: remark: building module{{.*}}SomeOtherKit + +// CHECK-WITHOUT-REBUILD-NOT: remark: building module{{.*}}SomeOtherKit + +// CHECK-ONE-ERROR: 1 error generated. +// CHECK-TWO-ERRORS: 2 errors generated. + diff --git a/clang/tools/driver/apinotes_main.cpp b/clang/tools/driver/apinotes_main.cpp index cbd8045922d2b..e4ccc2e7f3244 100644 --- a/clang/tools/driver/apinotes_main.cpp +++ b/clang/tools/driver/apinotes_main.cpp @@ -119,7 +119,7 @@ int cc1apinotes_main(ArrayRef Argv, const char *Argv0, llvm::raw_fd_ostream os(OutputFilename, EC, llvm::sys::fs::OpenFlags::F_None); - if (api_notes::compileAPINotes(input, os, targetOS)) + if (api_notes::compileAPINotes(input, /*sourceFile=*/nullptr, os, targetOS)) return 1; os.flush(); From 682653710c0291238b21ced3cd2eb1bc4ab4f88c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 27 Sep 2016 23:28:52 -0700 Subject: [PATCH 091/585] [API Notes] Ensure that modules get rebuilt when binary API notes change. Extend the solution for ensuring that modules get rebuilt to cases where the inputs are precompiled binary API notes, rather than just source API notes that get precompiled into the cache. Fixes the rest of rdar://problem/25639554. apple-llvm-split-commit: 35d7c61210eed20096bbd9488f89a8eb25b4ff51 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 10 +++++- clang/lib/APINotes/APINotesManager.cpp | 6 ++-- clang/lib/APINotes/APINotesReader.cpp | 31 +++++++++++++--- clang/lib/Frontend/CompilerInvocation.cpp | 4 +-- clang/test/APINotes/module-cache.m | 35 +++++++++++++++++++ 5 files changed, 76 insertions(+), 10 deletions(-) diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index c64d6c31624bb..aa88bac062662 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -31,7 +31,8 @@ class APINotesReader { Implementation &Impl; - APINotesReader(std::unique_ptr inputBuffer, bool &failed); + APINotesReader(llvm::MemoryBuffer *inputBuffer, bool ownsInputBuffer, + bool &failed); public: /// Create a new API notes reader from the given member buffer, which @@ -41,6 +42,13 @@ class APINotesReader { static std::unique_ptr get(std::unique_ptr inputBuffer); + /// Create a new API notes reader from the given member buffer, which + /// contains the contents of a binary API notes file. + /// + /// \returns the new API notes reader, or null if an error occurred. + static std::unique_ptr + getUnmanaged(llvm::MemoryBuffer *inputBuffer); + ~APINotesReader(); APINotesReader(const APINotesReader &) = delete; diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index 8dc337827e6c9..834d7e1f72f41 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -146,12 +146,14 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { StringRef apiNotesFileExt = llvm::sys::path::extension(apiNotesFileName); if (!apiNotesFileExt.empty() && apiNotesFileExt.substr(1) == BINARY_APINOTES_EXTENSION) { + auto compiledFileID = SourceMgr.createFileID(apiNotesFile, SourceLocation(), SrcMgr::C_User); + // Load the file. - auto buffer = fileMgr.getBufferForFile(apiNotesFile); + auto buffer = SourceMgr.getBuffer(compiledFileID, SourceLocation()); if (!buffer) return nullptr; // Load the binary form. - return APINotesReader::get(std::move(buffer.get())); + return APINotesReader::getUnmanaged(buffer); } // If we haven't pruned the API notes cache yet during this execution, do diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 9618e70d5f84a..0c93756a0053e 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -578,7 +578,10 @@ namespace { class APINotesReader::Implementation { public: /// The input buffer for the API notes data. - std::unique_ptr InputBuffer; + llvm::MemoryBuffer *InputBuffer; + + /// Whether we own the input buffer. + bool OwnsInputBuffer; /// The reader attached to \c InputBuffer. llvm::BitstreamReader InputReader; @@ -1321,14 +1324,16 @@ bool APINotesReader::Implementation::readTypedefBlock( return false; } -APINotesReader::APINotesReader(std::unique_ptr inputBuffer, - bool &failed) +APINotesReader::APINotesReader(llvm::MemoryBuffer *inputBuffer, + bool ownsInputBuffer, + bool &failed) : Impl(*new Implementation) { failed = false; // Initialize the input buffer. - Impl.InputBuffer = std::move(inputBuffer); + Impl.InputBuffer = inputBuffer; + Impl.OwnsInputBuffer = ownsInputBuffer; Impl.InputReader.init( reinterpret_cast(Impl.InputBuffer->getBufferStart()), reinterpret_cast(Impl.InputBuffer->getBufferEnd())); @@ -1461,6 +1466,9 @@ APINotesReader::APINotesReader(std::unique_ptr inputBuffer, } APINotesReader::~APINotesReader() { + if (Impl.OwnsInputBuffer) + delete Impl.InputBuffer; + delete &Impl; } @@ -1468,7 +1476,20 @@ std::unique_ptr APINotesReader::get(std::unique_ptr inputBuffer) { bool failed = false; std::unique_ptr - reader(new APINotesReader(std::move(inputBuffer), failed)); + reader(new APINotesReader(inputBuffer.release(), /*ownsInputBuffer=*/true, + failed)); + if (failed) + return nullptr; + + return reader; +} + +std::unique_ptr +APINotesReader::getUnmanaged(llvm::MemoryBuffer *inputBuffer) { + bool failed = false; + std::unique_ptr + reader(new APINotesReader(inputBuffer, /*ownsInputBuffer=*/false, + failed)); if (failed) return nullptr; diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 63f7af79aed58..0fc9c7fa1d5b6 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -2423,8 +2423,8 @@ bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Res, if (Res.getFrontendOpts().ProgramAction == frontend::RewriteObjC) LangOpts.ObjCExceptions = 1; - // -fapinotes requires -fapinotes-cache-path=. - if (LangOpts.APINotes && + // -fapinotes and -fapinotes-modules requires -fapinotes-cache-path=. + if ((LangOpts.APINotes || LangOpts.APINotesModules) && Res.getFileSystemOpts().APINotesCachePath.empty()) { Diags.Report(diag::err_no_apinotes_cache_path); Success = false; diff --git a/clang/test/APINotes/module-cache.m b/clang/test/APINotes/module-cache.m index dce003b3c7f86..2324697a0f701 100644 --- a/clang/test/APINotes/module-cache.m +++ b/clang/test/APINotes/module-cache.m @@ -36,6 +36,41 @@ // RUN: FileCheck -check-prefix=CHECK-WITHOUT-REBUILD %s < %t/after.log // RUN: FileCheck -check-prefix=CHECK-TWO-ERRORS %s < %t/after.log +// Set up a directory with pre-compiled API notes. +// RUN: mkdir -p %t/CompiledAPINotes +// RUN: rm -rf %t/ModulesCache +// RUN: rm -rf %t/APINotesCache +// RUN: %clang -cc1apinotes -yaml-to-binary -o %t/CompiledAPINotes/SomeOtherKit.apinotesc %S/Inputs/APINotes/SomeOtherKit.apinotes + +// First build: check that 'methodB' is unavailable but 'methodA' is available. +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -Rmodule-build -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %t/CompiledAPINotes -fapinotes-cache-path=%t/APINotesCache -F %S/Inputs/Frameworks %s > %t/compiled-before.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-METHODB %s < %t/compiled-before.log +// RUN: FileCheck -check-prefix=CHECK-REBUILD %s < %t/compiled-before.log +// RUN: FileCheck -check-prefix=CHECK-ONE-ERROR %s < %t/compiled-before.log + +// Do it again; now we're using caches. +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -Rmodule-build -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %t/CompiledAPINotes -fapinotes-cache-path=%t/APINotesCache -F %S/Inputs/Frameworks %s > %t/compiled-before.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-METHODB %s < %t/compiled-before.log +// RUN: FileCheck -check-prefix=CHECK-WITHOUT-REBUILD %s < %t/compiled-before.log +// RUN: FileCheck -check-prefix=CHECK-ONE-ERROR %s < %t/compiled-before.log + +// Compile a new API notes file to replace the old one. +// RUN: %clang -cc1apinotes -yaml-to-binary -o %t/CompiledAPINotes/SomeOtherKit.apinotesc %t/APINotes/SomeOtherKit.apinotes + +// Build again: check that both methods are now unavailable and that the module rebuilt. +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -Rmodule-build -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %t/CompiledAPINotes -fapinotes-cache-path=%t/APINotesCache -F %S/Inputs/Frameworks %s > %t/compiled-after.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-METHODA %s < %t/compiled-after.log +// RUN: FileCheck -check-prefix=CHECK-METHODB %s < %t/compiled-after.log +// RUN: FileCheck -check-prefix=CHECK-REBUILD %s < %t/compiled-after.log +// RUN: FileCheck -check-prefix=CHECK-TWO-ERRORS %s < %t/compiled-after.log + +// Run the build again: check that both methods are now unavailable +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -Rmodule-build -fmodules-cache-path=%t/ModulesCache -iapinotes-modules %t/CompiledAPINotes -fapinotes-cache-path=%t/APINotesCache -F %S/Inputs/Frameworks %s > %t/compiled-after.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-METHODA %s < %t/compiled-after.log +// RUN: FileCheck -check-prefix=CHECK-METHODB %s < %t/compiled-after.log +// RUN: FileCheck -check-prefix=CHECK-WITHOUT-REBUILD %s < %t/compiled-after.log +// RUN: FileCheck -check-prefix=CHECK-TWO-ERRORS %s < %t/compiled-after.log + @import SomeOtherKit; void test(A *a) { From 14fb7a6aef93e82919e842d07684964bf5cd9a75 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 2 Oct 2016 21:01:55 -0700 Subject: [PATCH 092/585] [API Notes] Add Swift versioning to API notes files Extend the API notes format with support for specifying API notes at a particular Swift version. A single API notes file has "unversioned" information, followed optionally by more information about the various classes/functions/etc. at a specific Swift version. The intent is to allow API notes to provide backward-compatibility information Parse Swift-versioned API notes, store version API notes in the binary format, and ensure that it round-trips. For now, Clang still only adds attributes based on the unversioned information. This is the first step of rdar://problem/28455809. apple-llvm-split-commit: 16d41c34b0feb962d6974b311597944e74b7027b apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 53 +- clang/include/clang/APINotes/APINotesWriter.h | 46 +- clang/include/clang/Basic/VersionTuple.h | 35 + clang/lib/APINotes/APINotesFormat.h | 21 +- clang/lib/APINotes/APINotesReader.cpp | 635 +++++++++-------- clang/lib/APINotes/APINotesWriter.cpp | 636 ++++++++++-------- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 355 ++++++---- clang/lib/Sema/SemaAPINotes.cpp | 16 +- clang/test/APINotes/Inputs/roundtrip.apinotes | 19 + 9 files changed, 1049 insertions(+), 767 deletions(-) diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index aa88bac062662..593e8174d4e08 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -17,6 +17,7 @@ #define LLVM_CLANG_API_NOTES_READER_H #include "clang/APINotes/Types.h" +#include "clang/Basic/VersionTuple.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/MemoryBuffer.h" #include @@ -65,21 +66,33 @@ class APINotesReader { /// Retrieve the module options ModuleOptions getModuleOptions() const; + /// Look for the context ID of the given Objective-C class. + /// + /// \param name The name of the class we're looking for. + /// + /// \returns The ID, if known. + Optional lookupObjCClassID(StringRef name); + /// Look for information regarding the given Objective-C class. /// /// \param name The name of the class we're looking for. /// - /// \returns The ID and information about the class, if known. - Optional> - lookupObjCClass(StringRef name); + /// \returns The information about the class, if known. + Optional lookupObjCClassInfo(StringRef name); + + /// Look for the context ID of the given Objective-C protocol. + /// + /// \param name The name of the protocol we're looking for. + /// + /// \returns The ID of the protocol, if known. + Optional lookupObjCProtocolID(StringRef name); /// Look for information regarding the given Objective-C protocol. /// /// \param name The name of the protocol we're looking for. /// - /// \returns The ID and information about the protocol, if known. - Optional> - lookupObjCProtocol(StringRef name); + /// \returns The information about the protocol, if known. + Optional lookupObjCProtocolInfo(StringRef name); /// Look for information regarding the given Objective-C property in /// the given context. @@ -88,6 +101,7 @@ class APINotesReader { /// \param name The name of the property we're looking for. /// \param isInstance Whether we are looking for an instance property (vs. /// a class property). + /// \param swiftVersion The Swift version to filter for, if any. /// /// \returns Information about the property, if known. Optional lookupObjCProperty(ContextID contextID, @@ -149,39 +163,48 @@ class APINotesReader { /// Visit an Objective-C class. virtual void visitObjCClass(ContextID contextID, StringRef name, - const ObjCContextInfo &info); + const ObjCContextInfo &info, + VersionTuple swiftVersion); /// Visit an Objective-C protocol. virtual void visitObjCProtocol(ContextID contextID, StringRef name, - const ObjCContextInfo &info); + const ObjCContextInfo &info, + VersionTuple swiftVersion); /// Visit an Objective-C method. virtual void visitObjCMethod(ContextID contextID, StringRef selector, bool isInstanceMethod, - const ObjCMethodInfo &info); + const ObjCMethodInfo &info, + VersionTuple swiftVersion); /// Visit an Objective-C property. virtual void visitObjCProperty(ContextID contextID, StringRef name, bool isInstance, - const ObjCPropertyInfo &info); + const ObjCPropertyInfo &info, + VersionTuple swiftVersion); /// Visit a global variable. virtual void visitGlobalVariable(StringRef name, - const GlobalVariableInfo &info); + const GlobalVariableInfo &info, + VersionTuple swiftVersion); /// Visit a global function. virtual void visitGlobalFunction(StringRef name, - const GlobalFunctionInfo &info); + const GlobalFunctionInfo &info, + VersionTuple swiftVersion); /// Visit an enumerator. virtual void visitEnumConstant(StringRef name, - const EnumConstantInfo &info); + const EnumConstantInfo &info, + VersionTuple swiftVersion); /// Visit a tag. - virtual void visitTag(StringRef name, const TagInfo &info); + virtual void visitTag(StringRef name, const TagInfo &info, + VersionTuple swiftVersion); /// Visit a typedef. - virtual void visitTypedef(StringRef name, const TypedefInfo &info); + virtual void visitTypedef(StringRef name, const TypedefInfo &info, + VersionTuple swiftVersion); }; /// Visit the contents of the API notes file, passing each entity to the diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h index 4bf3ce1371641..62defc1f944f7 100644 --- a/clang/include/clang/APINotes/APINotesWriter.h +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -16,6 +16,7 @@ #ifndef LLVM_CLANG_API_NOTES_WRITER_H #define LLVM_CLANG_API_NOTES_WRITER_H +#include "clang/Basic/VersionTuple.h" #include "clang/APINotes/Types.h" namespace llvm { @@ -46,23 +47,17 @@ class APINotesWriter { /// Write the API notes data to the given stream. void writeToStream(llvm::raw_ostream &os); - /// Add information about a specific Objective-C class. + /// Add information about a specific Objective-C class or protocol. /// - /// \param name The name of this class. - /// \param info Information about this class. + /// \param name The name of this class/protocol. + /// \param isClass Whether this is a class (vs. a protocol). + /// \param info Information about this class/protocol. /// - /// \returns the ID of the class, which can be used to add properties and - /// methods to the class. - ContextID addObjCClass(StringRef name, const ObjCContextInfo &info); - - /// Add information about a specific Objective-C protocol. - /// - /// \param name The name of this protocol. - /// \param info Information about this protocol. - /// - /// \returns the ID of the protocol, which can be used to add properties and - /// methods to the protocol. - ContextID addObjCProtocol(StringRef name, const ObjCContextInfo &info); + /// \returns the ID of the class or protocol, which can be used to add + /// properties and methods to the class/protocol. + ContextID addObjCContext(StringRef name, bool isClass, + const ObjCContextInfo &info, + VersionTuple swiftVersion); /// Add information about a specific Objective-C property. /// @@ -71,7 +66,8 @@ class APINotesWriter { /// \param info Information about this property. void addObjCProperty(ContextID contextID, StringRef name, bool isInstanceProperty, - const ObjCPropertyInfo &info); + const ObjCPropertyInfo &info, + VersionTuple swiftVersion); /// Add information about a specific Objective-C method. /// @@ -81,37 +77,43 @@ class APINotesWriter { /// (vs. a class method). /// \param info Information about this method. void addObjCMethod(ContextID contextID, ObjCSelectorRef selector, - bool isInstanceMethod, const ObjCMethodInfo &info); + bool isInstanceMethod, const ObjCMethodInfo &info, + VersionTuple swiftVersion); /// Add information about a global variable. /// /// \param name The name of this global variable. /// \param info Information about this global variable. - void addGlobalVariable(StringRef name, const GlobalVariableInfo &info); + void addGlobalVariable(StringRef name, const GlobalVariableInfo &info, + VersionTuple swiftVersion); /// Add information about a global function. /// /// \param name The name of this global function. /// \param info Information about this global function. - void addGlobalFunction(StringRef name, const GlobalFunctionInfo &info); + void addGlobalFunction(StringRef name, const GlobalFunctionInfo &info, + VersionTuple swiftVersion); /// Add information about an enumerator. /// /// \param name The name of this enumerator. /// \param info Information about this enumerator. - void addEnumConstant(StringRef name, const EnumConstantInfo &info); + void addEnumConstant(StringRef name, const EnumConstantInfo &info, + VersionTuple swiftVersion); /// Add information about a tag (struct/union/enum/C++ class). /// /// \param name The name of this tag. /// \param info Information about this tag. - void addTag(StringRef name, const TagInfo &info); + void addTag(StringRef name, const TagInfo &info, + VersionTuple swiftVersion); /// Add information about a typedef. /// /// \param name The name of this typedef. /// \param info Information about this typedef. - void addTypedef(StringRef name, const TypedefInfo &info); + void addTypedef(StringRef name, const TypedefInfo &info, + VersionTuple swiftVersion); /// Add module options void addModuleOptions(ModuleOptions opts); diff --git a/clang/include/clang/Basic/VersionTuple.h b/clang/include/clang/Basic/VersionTuple.h index da3b01903ed93..07315f008cdde 100644 --- a/clang/include/clang/Basic/VersionTuple.h +++ b/clang/include/clang/Basic/VersionTuple.h @@ -17,6 +17,7 @@ #include "clang/Basic/LLVM.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/DenseMapInfo.h" #include #include @@ -70,6 +71,9 @@ class VersionTuple { return Major == 0 && Minor == 0 && Subminor == 0 && Build == 0; } + /// Whether this is a non-empty version tuple. + explicit operator bool () const { return !empty(); } + /// \brief Retrieve the major version number. unsigned getMajor() const { return Major; } @@ -165,4 +169,35 @@ class VersionTuple { raw_ostream& operator<<(raw_ostream &Out, const VersionTuple &V); } // end namespace clang + +namespace llvm { + // Provide DenseMapInfo for version tuples. + template<> + struct DenseMapInfo { + static inline clang::VersionTuple getEmptyKey() { + return clang::VersionTuple(0x7FFFFFFF); + } + static inline clang::VersionTuple getTombstoneKey() { + return clang::VersionTuple(0x7FFFFFFE); + } + static unsigned getHashValue(const clang::VersionTuple& value) { + unsigned result = value.getMajor(); + if (auto minor = value.getMinor()) + result = combineHashValue(result, *minor); + if (auto subminor = value.getSubminor()) + result = combineHashValue(result, *subminor); + if (auto build = value.getBuild()) + result = combineHashValue(result, *build); + + return result; + } + + static bool isEqual(const clang::VersionTuple &lhs, + const clang::VersionTuple &rhs) { + return lhs == rhs; + } + }; + +} // end namespace llvm + #endif // LLVM_CLANG_BASIC_VERSIONTUPLE_H diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 542f90831a580..9b68cf292386d 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 15; // source file info +const uint16_t VERSION_MINOR = 16; // versioned API notes. using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; @@ -60,8 +60,8 @@ enum BlockID { /// The identifier data block, which maps identifier strings to IDs. IDENTIFIER_BLOCK_ID, - /// The Objective-C class data block, which maps Objective-C class - /// names to information about the class. + /// The Objective-C context data block, which contains information about + /// Objective-C classes and protocols. OBJC_CONTEXT_BLOCK_ID, /// The Objective-C property data block, which maps Objective-C @@ -147,13 +147,20 @@ namespace identifier_block { namespace objc_context_block { enum { - OBJC_CONTEXT_DATA = 1, + OBJC_CONTEXT_ID_DATA = 1, + OBJC_CONTEXT_INFO_DATA = 2, }; - using ObjCContextDataLayout = BCRecordLayout< - OBJC_CONTEXT_DATA, // record ID + using ObjCContextIDLayout = BCRecordLayout< + OBJC_CONTEXT_ID_DATA, // record ID BCVBR<16>, // table offset within the blob (see below) - BCBlob // map from ObjC class names (as IDs) to ObjC class information + BCBlob // map from ObjC class names/protocol (as IDs) to context IDs + >; + + using ObjCContextInfoLayout = BCRecordLayout< + OBJC_CONTEXT_INFO_DATA, // record ID + BCVBR<16>, // table offset within the blob (see below) + BCBlob // map from ObjC context IDs to context information. >; } diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 0c93756a0053e..71e1e411602b2 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -28,6 +28,77 @@ using namespace llvm::support; using namespace llvm; namespace { + /// Deserialize a version tuple. + VersionTuple readVersionTuple(const uint8_t *&data) { + uint8_t numVersions = (*data++) & 0x03; + + unsigned major = endian::readNext(data); + if (numVersions == 0) + return VersionTuple(major); + + unsigned minor = endian::readNext(data); + if (numVersions == 1) + return VersionTuple(major, minor); + + unsigned subminor = endian::readNext(data); + if (numVersions == 2) + return VersionTuple(major, minor, subminor); + + unsigned build = endian::readNext(data); + return VersionTuple(major, minor, subminor, build); + } + + /// An on-disk hash table whose data is versioned based on the Swift version. + template + class VersionedTableInfo { + public: + using internal_key_type = KeyType; + using external_key_type = KeyType; + using data_type = SmallVector, 1>; + using hash_value_type = size_t; + using offset_type = unsigned; + + internal_key_type GetInternalKey(external_key_type key) { + return key; + } + + external_key_type GetExternalKey(internal_key_type key) { + return key; + } + + hash_value_type ComputeHash(internal_key_type key) { + return static_cast(llvm::hash_value(key)); + } + + static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { + return lhs == rhs; + } + + static std::pair + ReadKeyDataLength(const uint8_t *&data) { + unsigned keyLength = endian::readNext(data); + unsigned dataLength = endian::readNext(data); + return { keyLength, dataLength }; + } + + static data_type ReadData(internal_key_type key, const uint8_t *data, + unsigned length) { + unsigned numElements = endian::readNext(data); + data_type result; + result.reserve(numElements); + for (unsigned i = 0; i != numElements; ++i) { + auto version = readVersionTuple(data); + auto dataBefore = data; (void)data; + auto unversionedData = Derived::readUnversioned(key, data); + assert(data != dataBefore + && "Unversioned data reader didn't move pointer"); + result.push_back({version, unversionedData}); + } + return result; + } + }; + + /// Read serialized CommonEntityInfo. void readCommonEntityInfo(const uint8_t *&data, CommonEntityInfo &info) { uint8_t unavailableBits = *data++; @@ -109,12 +180,12 @@ namespace { }; /// Used to deserialize the on-disk Objective-C class table. - class ObjCContextTableInfo { + class ObjCContextIDTableInfo { public: // identifier ID, is-protocol using internal_key_type = std::pair; using external_key_type = internal_key_type; - using data_type = std::pair; + using data_type = unsigned; using hash_value_type = size_t; using offset_type = unsigned; @@ -150,16 +221,35 @@ namespace { static data_type ReadData(internal_key_type key, const uint8_t *data, unsigned length) { - data_type result; - result.first = endian::readNext(data); - readCommonTypeInfo(data, result.second); - if (*data++) { - result.second.setDefaultNullability(static_cast(*data)); - } - ++data; - result.second.setHasDesignatedInits(*data++); - - return result; + return endian::readNext(data); + } + }; + + /// Used to deserialize the on-disk Objective-C property table. + class ObjCContextInfoTableInfo + : public VersionedTableInfo + { + public: + static internal_key_type ReadKey(const uint8_t *data, unsigned length) { + return endian::readNext(data); + } + + static ObjCContextInfo readUnversioned(internal_key_type key, + const uint8_t *&data) { + ObjCContextInfo info; + readCommonTypeInfo(data, info); + uint8_t payload = *data++; + + if (payload & 0x01) + info.setHasDesignatedInits(true); + payload = payload >> 1; + + if (payload & 0x4) + info.setDefaultNullability(static_cast(payload&0x03)); + + return info; } }; @@ -173,38 +263,12 @@ namespace { } /// Used to deserialize the on-disk Objective-C property table. - class ObjCPropertyTableInfo { + class ObjCPropertyTableInfo + : public VersionedTableInfo, + ObjCPropertyInfo> + { public: - // (context ID, name ID, isInstance) - using internal_key_type = std::tuple; - using external_key_type = internal_key_type; - using data_type = ObjCPropertyInfo; - using hash_value_type = size_t; - using offset_type = unsigned; - - internal_key_type GetInternalKey(external_key_type key) { - return key; - } - - external_key_type GetExternalKey(internal_key_type key) { - return key; - } - - hash_value_type ComputeHash(internal_key_type key) { - return static_cast(llvm::hash_value(key)); - } - - static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { - return lhs == rhs; - } - - static std::pair - ReadKeyDataLength(const uint8_t *&data) { - unsigned keyLength = endian::readNext(data); - unsigned dataLength = endian::readNext(data); - return { keyLength, dataLength }; - } - static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto classID = endian::readNext(data); auto nameID = endian::readNext(data); @@ -212,8 +276,8 @@ namespace { return std::make_tuple(classID, nameID, isInstance); } - static data_type ReadData(internal_key_type key, const uint8_t *data, - unsigned length) { + static ObjCPropertyInfo readUnversioned(internal_key_type key, + const uint8_t *&data) { ObjCPropertyInfo info; readVariableInfo(data, info); return info; @@ -248,40 +312,11 @@ namespace { } /// Used to deserialize the on-disk Objective-C method table. - class ObjCMethodTableInfo { + class ObjCMethodTableInfo + : public VersionedTableInfo, + ObjCMethodInfo> { public: - // (class ID, selector ID, is-instance) - using internal_key_type = std::tuple; - using external_key_type = internal_key_type; - using data_type = ObjCMethodInfo; - using hash_value_type = size_t; - using offset_type = unsigned; - - internal_key_type GetInternalKey(external_key_type key) { - return key; - } - - external_key_type GetExternalKey(internal_key_type key) { - return key; - } - - hash_value_type ComputeHash(internal_key_type key) { - return llvm::hash_combine(std::get<0>(key), - std::get<1>(key), - std::get<2>(key)); - } - - static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { - return lhs == rhs; - } - - static std::pair - ReadKeyDataLength(const uint8_t *&data) { - unsigned keyLength = endian::readNext(data); - unsigned dataLength = endian::readNext(data); - return { keyLength, dataLength }; - } - static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto classID = endian::readNext(data); auto selectorID = endian::readNext(data); @@ -289,13 +324,18 @@ namespace { return internal_key_type{ classID, selectorID, isInstance }; } - static data_type ReadData(internal_key_type key, const uint8_t *data, - unsigned length) { + static ObjCMethodInfo readUnversioned(internal_key_type key, + const uint8_t *&data) { ObjCMethodInfo info; + uint8_t payload = *data++; + info.Required = payload & 0x01; + payload >>= 1; + info.DesignatedInit = payload & 0x01; + payload >>= 1; + info.FactoryAsInit = payload & 0x03; + payload >>= 2; + readFunctionInfo(data, info); - info.DesignatedInit = endian::readNext(data); - info.FactoryAsInit = endian::readNext(data); - info.Required = endian::readNext(data); return info; } }; @@ -350,44 +390,17 @@ namespace { }; /// Used to deserialize the on-disk global variable table. - class GlobalVariableTableInfo { + class GlobalVariableTableInfo + : public VersionedTableInfo { public: - using internal_key_type = unsigned; // name ID - using external_key_type = internal_key_type; - using data_type = GlobalVariableInfo; - using hash_value_type = size_t; - using offset_type = unsigned; - - internal_key_type GetInternalKey(external_key_type key) { - return key; - } - - external_key_type GetExternalKey(internal_key_type key) { - return key; - } - - hash_value_type ComputeHash(internal_key_type key) { - return static_cast(llvm::hash_value(key)); - } - - static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { - return lhs == rhs; - } - - static std::pair - ReadKeyDataLength(const uint8_t *&data) { - unsigned keyLength = endian::readNext(data); - unsigned dataLength = endian::readNext(data); - return { keyLength, dataLength }; - } - static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto nameID = endian::readNext(data); return nameID; } - - static data_type ReadData(internal_key_type key, const uint8_t *data, - unsigned length) { + + static GlobalVariableInfo readUnversioned(internal_key_type key, + const uint8_t *&data) { GlobalVariableInfo info; readVariableInfo(data, info); return info; @@ -395,44 +408,17 @@ namespace { }; /// Used to deserialize the on-disk global function table. - class GlobalFunctionTableInfo { + class GlobalFunctionTableInfo + : public VersionedTableInfo { public: - using internal_key_type = unsigned; // name ID - using external_key_type = internal_key_type; - using data_type = GlobalFunctionInfo; - using hash_value_type = size_t; - using offset_type = unsigned; - - internal_key_type GetInternalKey(external_key_type key) { - return key; - } - - external_key_type GetExternalKey(internal_key_type key) { - return key; - } - - hash_value_type ComputeHash(internal_key_type key) { - return static_cast(llvm::hash_value(key)); - } - - static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { - return lhs == rhs; - } - - static std::pair - ReadKeyDataLength(const uint8_t *&data) { - unsigned keyLength = endian::readNext(data); - unsigned dataLength = endian::readNext(data); - return { keyLength, dataLength }; - } - static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto nameID = endian::readNext(data); return nameID; } - static data_type ReadData(internal_key_type key, const uint8_t *data, - unsigned length) { + static GlobalFunctionInfo readUnversioned(internal_key_type key, + const uint8_t *&data) { GlobalFunctionInfo info; readFunctionInfo(data, info); return info; @@ -440,44 +426,17 @@ namespace { }; /// Used to deserialize the on-disk enumerator table. - class EnumConstantTableInfo { + class EnumConstantTableInfo + : public VersionedTableInfo { public: - using internal_key_type = unsigned; // name ID - using external_key_type = internal_key_type; - using data_type = EnumConstantInfo; - using hash_value_type = size_t; - using offset_type = unsigned; - - internal_key_type GetInternalKey(external_key_type key) { - return key; - } - - external_key_type GetExternalKey(internal_key_type key) { - return key; - } - - hash_value_type ComputeHash(internal_key_type key) { - return static_cast(llvm::hash_value(key)); - } - - static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { - return lhs == rhs; - } - - static std::pair - ReadKeyDataLength(const uint8_t *&data) { - unsigned keyLength = endian::readNext(data); - unsigned dataLength = endian::readNext(data); - return { keyLength, dataLength }; - } - static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto nameID = endian::readNext(data); return nameID; } - static data_type ReadData(internal_key_type key, const uint8_t *data, - unsigned length) { + static EnumConstantInfo readUnversioned(internal_key_type key, + const uint8_t *&data) { EnumConstantInfo info; readCommonEntityInfo(data, info); return info; @@ -485,44 +444,16 @@ namespace { }; /// Used to deserialize the on-disk tag table. - class TagTableInfo { + class TagTableInfo + : public VersionedTableInfo { public: - using internal_key_type = unsigned; // name ID - using external_key_type = internal_key_type; - using data_type = TagInfo; - using hash_value_type = size_t; - using offset_type = unsigned; - - internal_key_type GetInternalKey(external_key_type key) { - return key; - } - - external_key_type GetExternalKey(internal_key_type key) { - return key; - } - - hash_value_type ComputeHash(internal_key_type key) { - return static_cast(llvm::hash_value(key)); - } - - static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { - return lhs == rhs; - } - - static std::pair - ReadKeyDataLength(const uint8_t *&data) { - unsigned keyLength = endian::readNext(data); - unsigned dataLength = endian::readNext(data); - return { keyLength, dataLength }; - } - static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto nameID = endian::readNext(data); return nameID; } - static data_type ReadData(internal_key_type key, const uint8_t *data, - unsigned length) { + static TagInfo readUnversioned(internal_key_type key, + const uint8_t *&data) { TagInfo info; readCommonTypeInfo(data, info); return info; @@ -530,44 +461,16 @@ namespace { }; /// Used to deserialize the on-disk typedef table. - class TypedefTableInfo { + class TypedefTableInfo + : public VersionedTableInfo { public: - using internal_key_type = unsigned; // name ID - using external_key_type = internal_key_type; - using data_type = TypedefInfo; - using hash_value_type = size_t; - using offset_type = unsigned; - - internal_key_type GetInternalKey(external_key_type key) { - return key; - } - - external_key_type GetExternalKey(internal_key_type key) { - return key; - } - - hash_value_type ComputeHash(internal_key_type key) { - return static_cast(llvm::hash_value(key)); - } - - static bool EqualKey(internal_key_type lhs, internal_key_type rhs) { - return lhs == rhs; - } - - static std::pair - ReadKeyDataLength(const uint8_t *&data) { - unsigned keyLength = endian::readNext(data); - unsigned dataLength = endian::readNext(data); - return { keyLength, dataLength }; - } - static internal_key_type ReadKey(const uint8_t *data, unsigned length) { auto nameID = endian::readNext(data); return nameID; } - - static data_type ReadData(internal_key_type key, const uint8_t *data, - unsigned length) { + + static TypedefInfo readUnversioned(internal_key_type key, + const uint8_t *&data) { TypedefInfo info; readCommonTypeInfo(data, info); return info; @@ -602,11 +505,17 @@ class APINotesReader::Implementation { /// The identifier table. std::unique_ptr IdentifierTable; - using SerializedObjCContextTable = - llvm::OnDiskIterableChainedHashTable; + using SerializedObjCContextIDTable = + llvm::OnDiskIterableChainedHashTable; - /// The Objective-C context table. - std::unique_ptr ObjCContextTable; + /// The Objective-C context ID table. + std::unique_ptr ObjCContextIDTable; + + using SerializedObjCContextInfoTable = + llvm::OnDiskIterableChainedHashTable; + + /// The Objective-C context info table. + std::unique_ptr ObjCContextInfoTable; using SerializedObjCPropertyTable = llvm::OnDiskIterableChainedHashTable; @@ -867,19 +776,36 @@ bool APINotesReader::Implementation::readObjCContextBlock( StringRef blobData; unsigned kind = cursor.readRecord(next.ID, scratch, &blobData); switch (kind) { - case objc_context_block::OBJC_CONTEXT_DATA: { - // Already saw Objective-C class table. - if (ObjCContextTable) + case objc_context_block::OBJC_CONTEXT_ID_DATA: { + // Already saw Objective-C context ID table. + if (ObjCContextIDTable) return true; uint32_t tableOffset; - objc_context_block::ObjCContextDataLayout::readRecord(scratch, tableOffset); + objc_context_block::ObjCContextIDLayout::readRecord(scratch, tableOffset); auto base = reinterpret_cast(blobData.data()); - ObjCContextTable.reset( - SerializedObjCContextTable::Create(base + tableOffset, - base + sizeof(uint32_t), - base)); + ObjCContextIDTable.reset( + SerializedObjCContextIDTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); + break; + } + + case objc_context_block::OBJC_CONTEXT_INFO_DATA: { + // Already saw Objective-C context info table. + if (ObjCContextInfoTable) + return true; + + uint32_t tableOffset; + objc_context_block::ObjCContextInfoLayout::readRecord(scratch, + tableOffset); + auto base = reinterpret_cast(blobData.data()); + + ObjCContextInfoTable.reset( + SerializedObjCContextInfoTable::Create(base + tableOffset, + base + sizeof(uint32_t), + base)); break; } @@ -1509,38 +1435,78 @@ ModuleOptions APINotesReader::getModuleOptions() const { return Impl.ModuleOpts; } -auto APINotesReader::lookupObjCClass(StringRef name) - -> Optional> { - if (!Impl.ObjCContextTable) +namespace { + template + Optional getUnversioned( + const SmallVectorImpl>& array) { + for (const auto &versioned : array) { + if (!versioned.first) return versioned.second; + } + return None; + } +} + +auto APINotesReader::lookupObjCClassID(StringRef name) -> Optional { + if (!Impl.ObjCContextIDTable) return None; Optional classID = Impl.getIdentifier(name); if (!classID) return None; - auto known = Impl.ObjCContextTable->find({*classID, '\0'}); - if (known == Impl.ObjCContextTable->end()) + auto knownID = Impl.ObjCContextIDTable->find({*classID, '\0'}); + if (knownID == Impl.ObjCContextIDTable->end()) return None; - auto result = *known; - return std::make_pair(ContextID(result.first), result.second); + return ContextID(*knownID); } -auto APINotesReader::lookupObjCProtocol(StringRef name) - -> Optional> { - if (!Impl.ObjCContextTable) +auto APINotesReader::lookupObjCClassInfo(StringRef name) + -> Optional { + if (!Impl.ObjCContextInfoTable) return None; - Optional classID = Impl.getIdentifier(name); - if (!classID) + Optional contextID = lookupObjCClassID(name); + if (!contextID) return None; - auto known = Impl.ObjCContextTable->find({*classID, '\1'}); - if (known == Impl.ObjCContextTable->end()) + auto knownInfo = Impl.ObjCContextInfoTable->find(contextID->Value); + if (knownInfo == Impl.ObjCContextInfoTable->end()) return None; - auto result = *known; - return std::make_pair(ContextID(result.first), result.second); + return getUnversioned(*knownInfo); +} + +auto APINotesReader::lookupObjCProtocolID(StringRef name) + -> Optional { + if (!Impl.ObjCContextIDTable) + return None; + + Optional classID = Impl.getIdentifier(name); + if (!classID) + return None; + + auto knownID = Impl.ObjCContextIDTable->find({*classID, '\1'}); + if (knownID == Impl.ObjCContextIDTable->end()) + return None; + + return ContextID(*knownID); +} + +auto APINotesReader::lookupObjCProtocolInfo(StringRef name) + -> Optional { + if (!Impl.ObjCContextInfoTable) + return None; + + Optional contextID = lookupObjCProtocolID(name); + if (!contextID) + return None; + + auto knownInfo = Impl.ObjCContextInfoTable->find(contextID->Value); + if (knownInfo == Impl.ObjCContextInfoTable->end()) + return None; + + return getUnversioned(*knownInfo); } Optional APINotesReader::lookupObjCProperty( @@ -1560,7 +1526,7 @@ Optional APINotesReader::lookupObjCProperty( if (known == Impl.ObjCPropertyTable->end()) return None; - return *known; + return getUnversioned(*known); } Optional APINotesReader::lookupObjCMethod( @@ -1580,7 +1546,7 @@ Optional APINotesReader::lookupObjCMethod( if (known == Impl.ObjCMethodTable->end()) return None; - return *known; + return getUnversioned(*known); } Optional APINotesReader::lookupGlobalVariable( @@ -1596,7 +1562,7 @@ Optional APINotesReader::lookupGlobalVariable( if (known == Impl.GlobalVariableTable->end()) return None; - return *known; + return getUnversioned(*known); } Optional APINotesReader::lookupGlobalFunction( @@ -1612,7 +1578,7 @@ Optional APINotesReader::lookupGlobalFunction( if (known == Impl.GlobalFunctionTable->end()) return None; - return *known; + return getUnversioned(*known); } Optional APINotesReader::lookupEnumConstant(StringRef name) { @@ -1627,7 +1593,7 @@ Optional APINotesReader::lookupEnumConstant(StringRef name) { if (known == Impl.EnumConstantTable->end()) return None; - return *known; + return getUnversioned(*known); } Optional APINotesReader::lookupTag(StringRef name) { @@ -1642,7 +1608,7 @@ Optional APINotesReader::lookupTag(StringRef name) { if (known == Impl.TagTable->end()) return None; - return *known; + return getUnversioned(*known); } Optional APINotesReader::lookupTypedef(StringRef name) { @@ -1657,48 +1623,61 @@ Optional APINotesReader::lookupTypedef(StringRef name) { if (known == Impl.TypedefTable->end()) return None; - return *known; + return getUnversioned(*known); } APINotesReader::Visitor::~Visitor() { } -void APINotesReader::Visitor::visitObjCClass(ContextID contextID, - StringRef name, - const ObjCContextInfo &info) { } - -void APINotesReader::Visitor::visitObjCProtocol(ContextID contextID, - StringRef name, - const ObjCContextInfo &info) { } - -void APINotesReader::Visitor::visitObjCMethod(ContextID contextID, - StringRef selector, - bool isInstanceMethod, - const ObjCMethodInfo &info) { } +void APINotesReader::Visitor::visitObjCClass( + ContextID contextID, + StringRef name, + const ObjCContextInfo &info, + VersionTuple swiftVersion) { } -void APINotesReader::Visitor::visitObjCProperty(ContextID contextID, - StringRef name, - bool isInstance, - const ObjCPropertyInfo &info) { } +void APINotesReader::Visitor::visitObjCProtocol( + ContextID contextID, + StringRef name, + const ObjCContextInfo &info, + VersionTuple swiftVersion) { } + +void APINotesReader::Visitor::visitObjCMethod( + ContextID contextID, + StringRef selector, + bool isInstanceMethod, + const ObjCMethodInfo &info, + VersionTuple swiftVersion) { } + +void APINotesReader::Visitor::visitObjCProperty( + ContextID contextID, + StringRef name, + bool isInstance, + const ObjCPropertyInfo &info, + VersionTuple swiftVersion) { } void APINotesReader::Visitor::visitGlobalVariable( StringRef name, - const GlobalVariableInfo &info) { } + const GlobalVariableInfo &info, + VersionTuple swiftVersion) { } void APINotesReader::Visitor::visitGlobalFunction( StringRef name, - const GlobalFunctionInfo &info) { } + const GlobalFunctionInfo &info, + VersionTuple swiftVersion) { } void APINotesReader::Visitor::visitEnumConstant( StringRef name, - const EnumConstantInfo &info) { } + const EnumConstantInfo &info, + VersionTuple swiftVersion) { } void APINotesReader::Visitor::visitTag( StringRef name, - const TagInfo &info) { } + const TagInfo &info, + VersionTuple swiftVersion) { } void APINotesReader::Visitor::visitTypedef( StringRef name, - const TypedefInfo &info) { } + const TypedefInfo &info, + VersionTuple swiftVersion) { } void APINotesReader::visit(Visitor &visitor) { // FIXME: All of these iterations would be significantly more efficient if we @@ -1717,15 +1696,22 @@ void APINotesReader::visit(Visitor &visitor) { } // Visit classes and protocols. - if (Impl.ObjCContextTable) { - for (auto key : Impl.ObjCContextTable->keys()) { + if (Impl.ObjCContextIDTable && Impl.ObjCContextInfoTable) { + for (auto key : Impl.ObjCContextIDTable->keys()) { auto name = identifiers[key.first]; - auto info = *Impl.ObjCContextTable->find(key); - - if (key.second) - visitor.visitObjCProtocol(ContextID(info.first), name, info.second); - else - visitor.visitObjCClass(ContextID(info.first), name, info.second); + auto contextID = *Impl.ObjCContextIDTable->find(key); + + auto knownInfo = Impl.ObjCContextInfoTable->find(contextID); + if (knownInfo == Impl.ObjCContextInfoTable->end()) continue; + + for (const auto &versioned : *knownInfo) { + if (key.second) + visitor.visitObjCProtocol(ContextID(contextID), name, + versioned.second, versioned.first); + else + visitor.visitObjCClass(ContextID(contextID), name, versioned.second, + versioned.first); + } } } @@ -1754,8 +1740,9 @@ void APINotesReader::visit(Visitor &visitor) { for (auto key : Impl.ObjCMethodTable->keys()) { ContextID contextID(std::get<0>(key)); const auto &selector = selectors[std::get<1>(key)]; - auto info = *Impl.ObjCMethodTable->find(key); - visitor.visitObjCMethod(contextID, selector, std::get<2>(key), info); + for (const auto &versioned : *Impl.ObjCMethodTable->find(key)) + visitor.visitObjCMethod(contextID, selector, std::get<2>(key), + versioned.second, versioned.first); } } @@ -1765,8 +1752,10 @@ void APINotesReader::visit(Visitor &visitor) { ContextID contextID(std::get<0>(key)); auto name = identifiers[std::get<1>(key)]; char isInstance = std::get<2>(key); - auto info = *Impl.ObjCPropertyTable->find(key); - visitor.visitObjCProperty(contextID, name, isInstance, info); + for (const auto &versioned : *Impl.ObjCPropertyTable->find(key)) { + visitor.visitObjCProperty(contextID, name, isInstance, versioned.second, + versioned.first); + } } } @@ -1774,8 +1763,8 @@ void APINotesReader::visit(Visitor &visitor) { if (Impl.GlobalFunctionTable) { for (auto key : Impl.GlobalFunctionTable->keys()) { auto name = identifiers[key]; - auto info = *Impl.GlobalFunctionTable->find(key); - visitor.visitGlobalFunction(name, info); + for (const auto &versioned : *Impl.GlobalFunctionTable->find(key)) + visitor.visitGlobalFunction(name, versioned.second, versioned.first); } } @@ -1783,8 +1772,8 @@ void APINotesReader::visit(Visitor &visitor) { if (Impl.GlobalVariableTable) { for (auto key : Impl.GlobalVariableTable->keys()) { auto name = identifiers[key]; - auto info = *Impl.GlobalVariableTable->find(key); - visitor.visitGlobalVariable(name, info); + for (const auto &versioned : *Impl.GlobalVariableTable->find(key)) + visitor.visitGlobalVariable(name, versioned.second, versioned.first); } } @@ -1792,8 +1781,8 @@ void APINotesReader::visit(Visitor &visitor) { if (Impl.EnumConstantTable) { for (auto key : Impl.EnumConstantTable->keys()) { auto name = identifiers[key]; - auto info = *Impl.EnumConstantTable->find(key); - visitor.visitEnumConstant(name, info); + for (const auto &versioned : *Impl.EnumConstantTable->find(key)) + visitor.visitEnumConstant(name, versioned.second, versioned.first); } } @@ -1801,8 +1790,8 @@ void APINotesReader::visit(Visitor &visitor) { if (Impl.TagTable) { for (auto key : Impl.TagTable->keys()) { auto name = identifiers[key]; - auto info = *Impl.TagTable->find(key); - visitor.visitTag(name, info); + for (const auto &versioned : *Impl.TagTable->find(key)) + visitor.visitTag(name, versioned.second, versioned.first); } } @@ -1810,8 +1799,8 @@ void APINotesReader::visit(Visitor &visitor) { if (Impl.TypedefTable) { for (auto key : Impl.TypedefTable->keys()) { auto name = identifiers[key]; - auto info = *Impl.TypedefTable->find(key); - visitor.visitTypedef(name, info); + for (const auto &versioned : *Impl.TypedefTable->find(key)) + visitor.visitTypedef(name, versioned.second, versioned.first); } } } diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 2df5e76f7c8d4..36c504afc34cd 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -24,12 +24,18 @@ #include "llvm/Support/EndianStream.h" #include "llvm/Support/OnDiskHashTable.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/DataTypes.h" #include #include using namespace clang; using namespace api_notes; using namespace llvm::support; +namespace { + template using VersionedSmallVector = + SmallVector, 1>; +} + class APINotesWriter::Implementation { /// Mapping from strings to identifier IDs. llvm::StringMap IdentifierIDs; @@ -56,7 +62,8 @@ class APINotesWriter::Implementation { /// for a class (0) or protocol (1) and provides both the context ID and /// information describing the context within that module. llvm::DenseMap, - std::pair> ObjCContexts; + std::pair>> + ObjCContexts; /// Mapping from context IDs to the identifier ID holding the name. llvm::DenseMap ObjCContextNames; @@ -65,40 +72,56 @@ class APINotesWriter::Implementation { /// /// Indexed by the context ID, property name, and whether this is an /// instance property. - llvm::DenseMap, ObjCPropertyInfo> + llvm::DenseMap, + llvm::SmallVector, + 1>> ObjCProperties; /// Information about Objective-C methods. /// /// Indexed by the context ID, selector ID, and Boolean (stored as a /// char) indicating whether this is a class or instance method. - llvm::DenseMap, ObjCMethodInfo> + llvm::DenseMap, + llvm::SmallVector, 1>> ObjCMethods; /// Information about global variables. /// /// Indexed by the identifier ID. - llvm::DenseMap GlobalVariables; + llvm::DenseMap, + 1>> + GlobalVariables; /// Information about global functions. /// /// Indexed by the identifier ID. - llvm::DenseMap GlobalFunctions; + llvm::DenseMap, + 1>> + GlobalFunctions; /// Information about enumerators. /// /// Indexed by the identifier ID. - llvm::DenseMap EnumConstants; + llvm::DenseMap, + 1>> + EnumConstants; /// Information about tags. /// /// Indexed by the identifier ID. - llvm::DenseMap Tags; + llvm::DenseMap, 1>> + Tags; /// Information about typedefs. /// /// Indexed by the identifier ID. - llvm::DenseMap Typedefs; + llvm::DenseMap, 1>> + Typedefs; /// Retrieve the ID for the given identifier. IdentifierID getIdentifier(StringRef identifier) { @@ -194,7 +217,7 @@ void APINotesWriter::Implementation::writeBlockInfoBlock( BLOCK_RECORD(identifier_block, IDENTIFIER_DATA); BLOCK(OBJC_CONTEXT_BLOCK); - BLOCK_RECORD(objc_context_block, OBJC_CONTEXT_DATA); + BLOCK_RECORD(objc_context_block, OBJC_CONTEXT_ID_DATA); BLOCK(OBJC_PROPERTY_BLOCK); BLOCK_RECORD(objc_property_block, OBJC_PROPERTY_DATA); @@ -336,18 +359,15 @@ namespace { } /// Used to serialize the on-disk Objective-C context table. - class ObjCContextTableInfo { + class ObjCContextIDTableInfo { public: using key_type = std::pair; // identifier ID, is-protocol using key_type_ref = key_type; - using data_type = std::pair; + using data_type = unsigned; using data_type_ref = const data_type &; using hash_value_type = size_t; using offset_type = unsigned; - /// The number of bytes in a data entry. - static const unsigned dataBytes = 3; - hash_value_type ComputeHash(key_type_ref key) { return static_cast(llvm::hash_value(key)); } @@ -356,9 +376,7 @@ namespace { key_type_ref key, data_type_ref data) { uint32_t keyLength = sizeof(uint32_t) + 1; - uint32_t dataLength = sizeof(uint32_t) - + getCommonTypeInfoSize(data.second) - + dataBytes; + uint32_t dataLength = sizeof(uint32_t); endian::Writer writer(out); writer.write(keyLength); writer.write(dataLength); @@ -374,58 +392,92 @@ namespace { void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, unsigned len) { endian::Writer writer(out); - writer.write(data.first); - - emitCommonTypeInfo(out, data.second); - - // FIXME: Inefficient representation. - uint8_t bytes[dataBytes] = { 0, 0, 0 }; - if (auto nullable = data.second.getDefaultNullability()) { - bytes[0] = 1; - bytes[1] = static_cast(*nullable); - } else { - // Nothing to do. - } - bytes[2] = data.second.hasDesignatedInits(); - - out.write(reinterpret_cast(bytes), dataBytes); + writer.write(data); } }; } // end anonymous namespace -void APINotesWriter::Implementation::writeObjCContextBlock( - llvm::BitstreamWriter &writer) { - BCBlockRAII restoreBlock(writer, OBJC_CONTEXT_BLOCK_ID, 3); +namespace { + /// Retrieve the serialized size of the given VersionTuple, for use in + /// on-disk hash tables. + unsigned getVersionTupleSize(const VersionTuple &version) { + unsigned size = sizeof(uint8_t) + /*major*/sizeof(uint32_t); + if (version.getMinor()) size += sizeof(uint32_t); + if (version.getSubminor()) size += sizeof(uint32_t); + if (version.getBuild()) size += sizeof(uint32_t); + return size; + } - if (ObjCContexts.empty()) - return; + /// Emit a serialized representation of a version tuple. + void emitVersionTuple(raw_ostream &out, const VersionTuple &version) { + endian::Writer writer(out); - llvm::SmallString<4096> hashTableBlob; - uint32_t tableOffset; - { - llvm::OnDiskChainedHashTableGenerator generator; - for (auto &entry : ObjCContexts) - generator.insert(entry.first, entry.second); + // First byte contains the number of components beyond the 'major' + // component. + uint8_t descriptor; + if (version.getBuild()) descriptor = 3; + else if (version.getSubminor()) descriptor = 2; + else if (version.getMinor()) descriptor = 1; + else descriptor = 0; + assert(!version.usesUnderscores() && "Not a serializable version"); + writer.write(descriptor); + + // Write the components. + writer.write(version.getMajor()); + if (auto minor = version.getMinor()) + writer.write(*minor); + if (auto subminor = version.getSubminor()) + writer.write(*subminor); + if (auto build = version.getBuild()) + writer.write(*build); + } - llvm::raw_svector_ostream blobStream(hashTableBlob); - // Make sure that no bucket is at offset 0 - endian::Writer(blobStream).write(0); - tableOffset = generator.Emit(blobStream); + /// Localized helper to make a type dependent, thwarting template argument + /// deduction. + template + struct MakeDependent { + typedef T Type; + }; + + /// Determine the size of an array of versioned information, + template + unsigned getVersionedInfoSize( + const SmallVectorImpl> &infoArray, + llvm::function_ref::Type&)> + getInfoSize) { + unsigned result = sizeof(uint16_t); // # of elements + for (const auto &element : infoArray) { + result += getVersionTupleSize(element.first); + result += getInfoSize(element.second); + } + + return result; } - objc_context_block::ObjCContextDataLayout layout(writer); - layout.emit(ScratchRecord, tableOffset, hashTableBlob); -} + /// Emit versioned information. + template + void emitVersionedInfo( + raw_ostream &out, + const SmallVectorImpl> &infoArray, + llvm::function_ref::Type& info)> + emitInfo) { + endian::Writer writer(out); + writer.write(infoArray.size()); + for (const auto &element : infoArray) { + emitVersionTuple(out, element.first); + emitInfo(out, element.second); + } + } -namespace { /// Retrieve the serialized size of the given VariableInfo, for use in /// on-disk hash tables. - static unsigned getVariableInfoSize(const VariableInfo &info) { + unsigned getVariableInfoSize(const VariableInfo &info) { return 2 + getCommonEntityInfoSize(info); } /// Emit a serialized representation of the variable information. - static void emitVariableInfo(raw_ostream &out, const VariableInfo &info) { + void emitVariableInfo(raw_ostream &out, const VariableInfo &info) { emitCommonEntityInfo(out, info); uint8_t bytes[2] = { 0, 0 }; @@ -439,32 +491,96 @@ namespace { out.write(reinterpret_cast(bytes), 2); } - /// Used to serialize the on-disk Objective-C property table. - class ObjCPropertyTableInfo { + /// On-dish hash table info key base for handling versioned data. + template + class VersionedTableInfo { + Derived &asDerived() { + return *static_cast(this); + } + + const Derived &asDerived() const { + return *static_cast(this); + } + public: - // (class ID, name ID, isInstance) - using key_type = std::tuple; + using key_type = KeyType; using key_type_ref = key_type; - using data_type = ObjCPropertyInfo; + using data_type = + SmallVector, 1>; using data_type_ref = const data_type &; using hash_value_type = size_t; using offset_type = unsigned; hash_value_type ComputeHash(key_type_ref key) { - return static_cast(llvm::hash_value(key)); + return llvm::hash_value(key); } std::pair EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { - uint32_t keyLength = sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t); - uint32_t dataLength = getVariableInfoSize(data); + uint32_t keyLength = asDerived().getKeyLength(key); + uint32_t dataLength = getVersionedInfoSize(data, + [this](const UnversionedDataType &unversionedInfo) { + return asDerived().getUnversionedInfoSize(unversionedInfo); + }); + endian::Writer writer(out); writer.write(keyLength); writer.write(dataLength); return { keyLength, dataLength }; } + void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, + unsigned len) { + emitVersionedInfo(out, data, + [this](llvm::raw_ostream &out, + const UnversionedDataType &unversionedInfo) { + asDerived().emitUnversionedInfo(out, unversionedInfo); + }); + } + }; + + /// Used to serialize the on-disk Objective-C property table. + class ObjCContextInfoTableInfo + : public VersionedTableInfo { + public: + unsigned getKeyLength(key_type_ref) { + return sizeof(uint32_t); + } + + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { + endian::Writer writer(out); + writer.write(key); + } + + unsigned getUnversionedInfoSize(const ObjCContextInfo &info) { + return getCommonTypeInfoSize(info) + 1; + } + + void emitUnversionedInfo(raw_ostream &out, const ObjCContextInfo &info) { + emitCommonTypeInfo(out, info); + + uint8_t payload = 0; + if (auto nullable = info.getDefaultNullability()) { + payload = (0x01 << 2) | static_cast(*nullable); + } + payload = (payload << 1) | (info.hasDesignatedInits() ? 1 : 0); + out << payload; + } + }; + + /// Used to serialize the on-disk Objective-C property table. + class ObjCPropertyTableInfo + : public VersionedTableInfo, + ObjCPropertyInfo> { + public: + unsigned getKeyLength(key_type_ref) { + return sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t); + } + void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { endian::Writer writer(out); writer.write(std::get<0>(key)); @@ -472,13 +588,61 @@ namespace { writer.write(std::get<2>(key)); } - void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, - unsigned len) { - emitVariableInfo(out, data); + unsigned getUnversionedInfoSize(const ObjCPropertyInfo &info) { + return getVariableInfoSize(info); + } + + void emitUnversionedInfo(raw_ostream &out, const ObjCPropertyInfo &info) { + emitVariableInfo(out, info); } }; } // end anonymous namespace +void APINotesWriter::Implementation::writeObjCContextBlock( + llvm::BitstreamWriter &writer) { + BCBlockRAII restoreBlock(writer, OBJC_CONTEXT_BLOCK_ID, 3); + + if (ObjCContexts.empty()) + return; + + { + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator generator; + for (auto &entry : ObjCContexts) + generator.insert(entry.first, entry.second.first); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + objc_context_block::ObjCContextIDLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); + } + + { + llvm::SmallString<4096> hashTableBlob; + uint32_t tableOffset; + { + llvm::OnDiskChainedHashTableGenerator + generator; + for (auto &entry : ObjCContexts) + generator.insert(entry.second.first, entry.second.second); + + llvm::raw_svector_ostream blobStream(hashTableBlob); + // Make sure that no bucket is at offset 0 + endian::Writer(blobStream).write(0); + tableOffset = generator.Emit(blobStream); + } + + objc_context_block::ObjCContextInfoLayout layout(writer); + layout.emit(ScratchRecord, tableOffset, hashTableBlob); + } +} + void APINotesWriter::Implementation::writeObjCPropertyBlock( llvm::BitstreamWriter &writer) { BCBlockRAII restoreBlock(writer, OBJC_PROPERTY_BLOCK_ID, 3); @@ -536,31 +700,13 @@ namespace { } /// Used to serialize the on-disk Objective-C method table. - class ObjCMethodTableInfo { + class ObjCMethodTableInfo + : public VersionedTableInfo, + ObjCMethodInfo> { public: - // (class ID, selector ID, is-instance) - using key_type = std::tuple; - using key_type_ref = key_type; - using data_type = ObjCMethodInfo; - using data_type_ref = const data_type &; - using hash_value_type = size_t; - using offset_type = unsigned; - - hash_value_type ComputeHash(key_type_ref key) { - return llvm::hash_combine(std::get<0>(key), - std::get<1>(key), - std::get<2>(key)); - } - - std::pair EmitKeyDataLength(raw_ostream &out, - key_type_ref key, - data_type_ref data) { - uint32_t keyLength = sizeof(uint32_t) + sizeof(uint32_t) + 1; - uint32_t dataLength = getFunctionInfoSize(data) + 3; - endian::Writer writer(out); - writer.write(keyLength); - writer.write(dataLength); - return { keyLength, dataLength }; + unsigned getKeyLength(key_type_ref) { + return sizeof(uint32_t) + sizeof(uint32_t) + 1; } void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { @@ -570,16 +716,18 @@ namespace { writer.write(std::get<2>(key)); } - void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, - unsigned len) { - emitFunctionInfo(out, data); + unsigned getUnversionedInfoSize(const ObjCMethodInfo &info) { + return 1 + getFunctionInfoSize(info); + } + void emitUnversionedInfo(raw_ostream &out, const ObjCMethodInfo &info) { + uint8_t payload = info.FactoryAsInit << 2; + payload = (payload | info.DesignatedInit) << 1; + payload = (payload | info.Required); endian::Writer writer(out); + writer.write(payload); - // FIXME: Inefficient representation - writer.write(data.DesignatedInit); - writer.write(data.FactoryAsInit); - writer.write(data.Required); + emitFunctionInfo(out, info); } }; } // end anonymous namespace @@ -678,28 +826,13 @@ void APINotesWriter::Implementation::writeObjCSelectorBlock( namespace { /// Used to serialize the on-disk global variable table. - class GlobalVariableTableInfo { + class GlobalVariableTableInfo + : public VersionedTableInfo { public: - using key_type = unsigned; // name ID - using key_type_ref = key_type; - using data_type = GlobalVariableInfo; - using data_type_ref = const data_type &; - using hash_value_type = size_t; - using offset_type = unsigned; - - hash_value_type ComputeHash(key_type_ref key) { - return static_cast(llvm::hash_value(key)); - } - - std::pair EmitKeyDataLength(raw_ostream &out, - key_type_ref key, - data_type_ref data) { - uint32_t keyLength = sizeof(uint32_t); - uint32_t dataLength = getVariableInfoSize(data); - endian::Writer writer(out); - writer.write(keyLength); - writer.write(dataLength); - return { keyLength, dataLength }; + unsigned getKeyLength(key_type_ref key) { + return sizeof(uint32_t); } void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { @@ -707,9 +840,13 @@ namespace { writer.write(key); } - void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, - unsigned len) { - emitVariableInfo(out, data); + unsigned getUnversionedInfoSize(const GlobalVariableInfo &info) { + return getVariableInfoSize(info); + } + + void emitUnversionedInfo(raw_ostream &out, + const GlobalVariableInfo &info) { + emitVariableInfo(out, info); } }; } // end anonymous namespace @@ -740,28 +877,13 @@ void APINotesWriter::Implementation::writeGlobalVariableBlock( namespace { /// Used to serialize the on-disk global function table. - class GlobalFunctionTableInfo { + class GlobalFunctionTableInfo + : public VersionedTableInfo { public: - using key_type = unsigned; // name ID - using key_type_ref = key_type; - using data_type = GlobalFunctionInfo; - using data_type_ref = const data_type &; - using hash_value_type = size_t; - using offset_type = unsigned; - - hash_value_type ComputeHash(key_type_ref key) { - return llvm::hash_value(key); - } - - std::pair EmitKeyDataLength(raw_ostream &out, - key_type_ref key, - data_type_ref data) { - uint32_t keyLength = sizeof(uint32_t); - uint32_t dataLength = getFunctionInfoSize(data); - endian::Writer writer(out); - writer.write(keyLength); - writer.write(dataLength); - return { keyLength, dataLength }; + unsigned getKeyLength(key_type_ref) { + return sizeof(uint32_t); } void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { @@ -769,9 +891,13 @@ namespace { writer.write(key); } - void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, - unsigned len) { - emitFunctionInfo(out, data); + unsigned getUnversionedInfoSize(const GlobalFunctionInfo &info) { + return getFunctionInfoSize(info); + } + + void emitUnversionedInfo(raw_ostream &out, + const GlobalFunctionInfo &info) { + emitFunctionInfo(out, info); } }; } // end anonymous namespace @@ -803,28 +929,13 @@ void APINotesWriter::Implementation::writeGlobalFunctionBlock( namespace { /// Used to serialize the on-disk global enum constant. - class EnumConstantTableInfo { + class EnumConstantTableInfo + : public VersionedTableInfo { public: - using key_type = unsigned; // name ID - using key_type_ref = key_type; - using data_type = EnumConstantInfo; - using data_type_ref = const data_type &; - using hash_value_type = size_t; - using offset_type = unsigned; - - hash_value_type ComputeHash(key_type_ref key) { - return static_cast(llvm::hash_value(key)); - } - - std::pair EmitKeyDataLength(raw_ostream &out, - key_type_ref key, - data_type_ref data) { - uint32_t keyLength = sizeof(uint32_t); - uint32_t dataLength = getCommonEntityInfoSize(data); - endian::Writer writer(out); - writer.write(keyLength); - writer.write(dataLength); - return { keyLength, dataLength }; + unsigned getKeyLength(key_type_ref) { + return sizeof(uint32_t); } void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { @@ -832,9 +943,12 @@ namespace { writer.write(key); } - void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, - unsigned len) { - emitCommonEntityInfo(out, data); + unsigned getUnversionedInfoSize(const EnumConstantInfo &info) { + return getCommonEntityInfoSize(info); + } + + void emitUnversionedInfo(raw_ostream &out, const EnumConstantInfo &info) { + emitCommonEntityInfo(out, info); } }; } // end anonymous namespace @@ -864,41 +978,32 @@ void APINotesWriter::Implementation::writeEnumConstantBlock( } namespace { - /// Used to serialize the on-disk tag table. - class TagTableInfo { + template + class CommonTypeTableInfo + : public VersionedTableInfo { public: - using key_type = unsigned; // name ID - using key_type_ref = key_type; - using data_type = TagInfo; - using data_type_ref = const data_type &; - using hash_value_type = size_t; - using offset_type = unsigned; - - hash_value_type ComputeHash(key_type_ref key) { - return static_cast(llvm::hash_value(key)); - } + using key_type_ref = typename CommonTypeTableInfo::key_type_ref; - std::pair EmitKeyDataLength(raw_ostream &out, - key_type_ref key, - data_type_ref data) { - uint32_t keyLength = sizeof(IdentifierID); - uint32_t dataLength = getCommonTypeInfoSize(data); - endian::Writer writer(out); - writer.write(keyLength); - writer.write(dataLength); - return { keyLength, dataLength }; + unsigned getKeyLength(key_type_ref) { + return sizeof(IdentifierID); } - void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { endian::Writer writer(out); writer.write(key); } - void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, - unsigned len) { - emitCommonTypeInfo(out, data); + unsigned getUnversionedInfoSize(const UnversionedDataType &info) { + return getCommonTypeInfoSize(info); + } + + void emitUnversionedInfo(raw_ostream &out, + const UnversionedDataType &info) { + emitCommonTypeInfo(out, info); } }; + + /// Used to serialize the on-disk tag table. + class TagTableInfo : public CommonTypeTableInfo { }; } // end anonymous namespace void APINotesWriter::Implementation::writeTagBlock( @@ -927,40 +1032,8 @@ void APINotesWriter::Implementation::writeTagBlock( namespace { /// Used to serialize the on-disk typedef table. - class TypedefTableInfo { - public: - using key_type = unsigned; // name ID - using key_type_ref = key_type; - using data_type = TypedefInfo; - using data_type_ref = const data_type &; - using hash_value_type = size_t; - using offset_type = unsigned; - - hash_value_type ComputeHash(key_type_ref key) { - return static_cast(llvm::hash_value(key)); - } - - std::pair EmitKeyDataLength(raw_ostream &out, - key_type_ref key, - data_type_ref data) { - uint32_t keyLength = sizeof(IdentifierID); - uint32_t dataLength = getCommonTypeInfoSize(data); - endian::Writer writer(out); - writer.write(keyLength); - writer.write(dataLength); - return { keyLength, dataLength }; - } - - void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { - endian::Writer writer(out); - writer.write(key); - } - - void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, - unsigned len) { - emitCommonTypeInfo(out, data); - } - }; + class TypedefTableInfo + : public CommonTypeTableInfo { }; } // end anonymous namespace void APINotesWriter::Implementation::writeTypedefBlock( @@ -1033,106 +1106,115 @@ void APINotesWriter::writeToStream(raw_ostream &os) { Impl.writeToStream(os); } -ContextID APINotesWriter::addObjCClass(StringRef name, - const ObjCContextInfo &info) { - IdentifierID classID = Impl.getIdentifier(name); +ContextID APINotesWriter::addObjCContext(StringRef name, bool isClass, + const ObjCContextInfo &info, + VersionTuple swiftVersion) { + IdentifierID nameID = Impl.getIdentifier(name); - std::pair key(classID, 0); + std::pair key(nameID, isClass ? 0 : 1); auto known = Impl.ObjCContexts.find(key); - if (known != Impl.ObjCContexts.end()) { - known->second.second |= info; - } else { + if (known == Impl.ObjCContexts.end()) { unsigned nextID = Impl.ObjCContexts.size() + 1; + VersionedSmallVector emptyVersionedInfo; known = Impl.ObjCContexts.insert( - std::make_pair(key, std::make_pair(nextID, info))) + std::make_pair(key, std::make_pair(nextID, emptyVersionedInfo))) .first; - Impl.ObjCContextNames[nextID] = classID; + Impl.ObjCContextNames[nextID] = nameID; } - return ContextID(known->second.first); -} - -ContextID APINotesWriter::addObjCProtocol(StringRef name, - const ObjCContextInfo &info) { - IdentifierID protocolID = Impl.getIdentifier(name); - - std::pair key(protocolID, 1); - auto known = Impl.ObjCContexts.find(key); - if (known != Impl.ObjCContexts.end()) { - known->second.second |= info; - } else { - unsigned nextID = Impl.ObjCContexts.size() + 1; - - known = Impl.ObjCContexts.insert( - std::make_pair(key, std::make_pair(nextID, info))) - .first; - - Impl.ObjCContextNames[nextID] = protocolID; + // Add this version information. + auto &versionedVec = known->second.second; + bool found = false; + for (auto &versioned : versionedVec){ + if (versioned.first == swiftVersion) { + versioned.second |= info; + found = true; + break; + } } + if (!found) + versionedVec.push_back({swiftVersion, info}); + return ContextID(known->second.first); } + void APINotesWriter::addObjCProperty(ContextID contextID, StringRef name, bool isInstance, - const ObjCPropertyInfo &info) { + const ObjCPropertyInfo &info, + VersionTuple swiftVersion) { IdentifierID nameID = Impl.getIdentifier(name); - assert(!Impl.ObjCProperties.count(std::make_tuple(contextID.Value, nameID, isInstance))); - Impl.ObjCProperties[std::make_tuple(contextID.Value, nameID, isInstance)] = info; + Impl.ObjCProperties[std::make_tuple(contextID.Value, nameID, isInstance)] + .push_back({swiftVersion, info}); } void APINotesWriter::addObjCMethod(ContextID contextID, ObjCSelectorRef selector, bool isInstanceMethod, - const ObjCMethodInfo &info) { + const ObjCMethodInfo &info, + VersionTuple swiftVersion) { SelectorID selectorID = Impl.getSelector(selector); auto key = std::tuple{ contextID.Value, selectorID, isInstanceMethod}; - assert(!Impl.ObjCMethods.count(key)); - Impl.ObjCMethods[key] = info; + Impl.ObjCMethods[key].push_back({swiftVersion, info}); // If this method is a designated initializer, update the class to note that // it has designated initializers. if (info.DesignatedInit) { assert(Impl.ObjCContexts.count({Impl.ObjCContextNames[contextID.Value], (char)0})); - Impl.ObjCContexts[{Impl.ObjCContextNames[contextID.Value], (char)0}] - .second.setHasDesignatedInits(true); + auto &versionedVec = + Impl.ObjCContexts[{Impl.ObjCContextNames[contextID.Value], (char)0}] + .second; + bool found = false; + for (auto &versioned : versionedVec) { + if (versioned.first == swiftVersion) { + versioned.second.setHasDesignatedInits(true); + found = true; + break; + } + } + + if (!found) { + versionedVec.push_back({swiftVersion, ObjCContextInfo()}); + versionedVec.back().second.setHasDesignatedInits(true); + } } } void APINotesWriter::addGlobalVariable(llvm::StringRef name, - const GlobalVariableInfo &info) { + const GlobalVariableInfo &info, + VersionTuple swiftVersion) { IdentifierID variableID = Impl.getIdentifier(name); - assert(!Impl.GlobalVariables.count(variableID)); - Impl.GlobalVariables[variableID] = info; + Impl.GlobalVariables[variableID].push_back({swiftVersion, info}); } void APINotesWriter::addGlobalFunction(llvm::StringRef name, - const GlobalFunctionInfo &info) { + const GlobalFunctionInfo &info, + VersionTuple swiftVersion) { IdentifierID nameID = Impl.getIdentifier(name); - assert(!Impl.GlobalFunctions.count(nameID)); - Impl.GlobalFunctions[nameID] = info; + Impl.GlobalFunctions[nameID].push_back({swiftVersion, info}); } void APINotesWriter::addEnumConstant(llvm::StringRef name, - const EnumConstantInfo &info) { + const EnumConstantInfo &info, + VersionTuple swiftVersion) { IdentifierID enumConstantID = Impl.getIdentifier(name); - assert(!Impl.EnumConstants.count(enumConstantID)); - Impl.EnumConstants[enumConstantID] = info; + Impl.EnumConstants[enumConstantID].push_back({swiftVersion, info}); } -void APINotesWriter::addTag(llvm::StringRef name, const TagInfo &info) { +void APINotesWriter::addTag(llvm::StringRef name, const TagInfo &info, + VersionTuple swiftVersion) { IdentifierID tagID = Impl.getIdentifier(name); - assert(!Impl.Tags.count(tagID)); - Impl.Tags[tagID] = info; + Impl.Tags[tagID].push_back({swiftVersion, info}); } -void APINotesWriter::addTypedef(llvm::StringRef name, const TypedefInfo &info) { +void APINotesWriter::addTypedef(llvm::StringRef name, const TypedefInfo &info, + VersionTuple swiftVersion) { IdentifierID typedefID = Impl.getIdentifier(name); - assert(!Impl.Typedefs.count(typedefID)); - Impl.Typedefs[typedefID] = info; + Impl.Typedefs[typedefID].push_back({swiftVersion, info}); } void APINotesWriter::addModuleOptions(ModuleOptions opts) { diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 0d30731194c41..f15099e4677ca 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -14,6 +14,7 @@ #include "clang/APINotes/APINotesReader.h" #include "clang/APINotes/Types.h" #include "clang/APINotes/APINotesWriter.h" +#include "clang/Basic/VersionTuple.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/SourceMgr.h" @@ -262,9 +263,7 @@ namespace { }; typedef std::vector TypedefsSeq; - struct Module { - StringRef Name; - AvailabilityItem Availability; + struct TopLevelItems { ClassesSeq Classes; ClassesSeq Protocols; FunctionsSeq Functions; @@ -272,6 +271,20 @@ namespace { EnumConstantsSeq EnumConstants; TagsSeq Tags; TypedefsSeq Typedefs; + }; + + struct Versioned { + VersionTuple Version; + TopLevelItems Items; + }; + + typedef std::vector VersionedSeq; + + struct Module { + StringRef Name; + AvailabilityItem Availability; + TopLevelItems TopLevel; + VersionedSeq SwiftVersions; llvm::Optional SwiftInferImportAsMember = {llvm::None}; @@ -291,6 +304,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(GlobalVariable) LLVM_YAML_IS_SEQUENCE_VECTOR(EnumConstant) LLVM_YAML_IS_SEQUENCE_VECTOR(Tag) LLVM_YAML_IS_SEQUENCE_VECTOR(Typedef) +LLVM_YAML_IS_SEQUENCE_VECTOR(Versioned) namespace llvm { namespace yaml { @@ -335,6 +349,24 @@ namespace llvm { } }; + template <> + struct ScalarTraits { + static void output(const VersionTuple &value, void*, + llvm::raw_ostream &out) { + out << value; + } + static StringRef input(StringRef scalar, void*, VersionTuple &value) { + if (value.tryParse(scalar)) + return "not a version number in the form XX.YY"; + + // Canonicalize on '.' as a separator. + value.UseDotAsSeparator(); + return StringRef(); + } + + static bool mustQuote(StringRef) { return false; } + }; + template <> struct MappingTraits { static void mapping(IO &io, Param& p) { @@ -460,6 +492,24 @@ namespace llvm { } }; + static void mapTopLevelItems(IO &io, TopLevelItems &i) { + io.mapOptional("Classes", i.Classes); + io.mapOptional("Protocols", i.Protocols); + io.mapOptional("Functions", i.Functions); + io.mapOptional("Globals", i.Globals); + io.mapOptional("Enumerators", i.EnumConstants); + io.mapOptional("Tags", i.Tags); + io.mapOptional("Typedefs", i.Typedefs); + } + + template <> + struct MappingTraits { + static void mapping(IO &io, Versioned& v) { + io.mapRequired("Version", v.Version); + mapTopLevelItems(io, v.Items); + } + }; + template <> struct MappingTraits { static void mapping(IO &io, Module& m) { @@ -467,13 +517,10 @@ namespace llvm { io.mapOptional("Availability", m.Availability.Mode); io.mapOptional("AvailabilityMsg", m.Availability.Msg); io.mapOptional("SwiftInferImportAsMember", m.SwiftInferImportAsMember); - io.mapOptional("Classes", m.Classes); - io.mapOptional("Protocols", m.Protocols); - io.mapOptional("Functions", m.Functions); - io.mapOptional("Globals", m.Globals); - io.mapOptional("Enumerators", m.EnumConstants); - io.mapOptional("Tags", m.Tags); - io.mapOptional("Typedefs", m.Typedefs); + + mapTopLevelItems(io, m.TopLevel); + + io.mapOptional("SwiftVersions", m.SwiftVersions); } }; } @@ -624,7 +671,8 @@ namespace { // Translate from Method into ObjCMethodInfo and write it out. void convertMethod(const Method &meth, - ContextID classID, StringRef className) { + ContextID classID, StringRef className, + VersionTuple swiftVersion) { ObjCMethodInfo mInfo; if (convertCommon(meth, mInfo, meth.Selector)) @@ -662,10 +710,11 @@ namespace { // Write it. Writer->addObjCMethod(classID, selectorRef, meth.Kind == MethodKind::Instance, - mInfo); + mInfo, swiftVersion); } - void convertContext(const Class &cl, bool isClass) { + void convertContext(const Class &cl, bool isClass, + VersionTuple swiftVersion) { // Write the class. ObjCContextInfo cInfo; @@ -675,8 +724,8 @@ namespace { if (cl.AuditedForNullability) cInfo.setDefaultNullability(*DefaultNullability); - ContextID clID = isClass ? Writer->addObjCClass(cl.Name, cInfo) : - Writer->addObjCProtocol(cl.Name, cInfo); + ContextID clID = Writer->addObjCContext(cl.Name, isClass, cInfo, + swiftVersion); // Write all methods. llvm::StringMap> knownMethods; @@ -693,7 +742,7 @@ namespace { } known = true; - convertMethod(method, clID, cl.Name); + convertMethod(method, clID, cl.Name, swiftVersion); } // Write all properties. @@ -726,51 +775,45 @@ namespace { pInfo.setNullabilityAudited(*prop.Nullability); if (prop.Kind) { Writer->addObjCProperty(clID, prop.Name, - *prop.Kind == MethodKind::Instance, pInfo); + *prop.Kind == MethodKind::Instance, pInfo, + swiftVersion); } else { // Add both instance and class properties with this name. - Writer->addObjCProperty(clID, prop.Name, true, pInfo); - Writer->addObjCProperty(clID, prop.Name, false, pInfo); + Writer->addObjCProperty(clID, prop.Name, true, pInfo, swiftVersion); + Writer->addObjCProperty(clID, prop.Name, false, pInfo, swiftVersion); } } } - bool convertModule() { - if (!isAvailable(TheModule.Availability)) - return false; - - // Set up the writer. - // FIXME: This is kindof ugly. - APINotesWriter writer(TheModule.Name, SourceFile); - Writer = &writer; - + void convertTopLevelItems(const TopLevelItems &items, + VersionTuple swiftVersion) { // Write all classes. llvm::StringSet<> knownClasses; - for (const auto &cl : TheModule.Classes) { + for (const auto &cl : items.Classes) { // Check for duplicate class definitions. if (!knownClasses.insert(cl.Name).second) { emitError("multiple definitions of class '" + cl.Name + "'"); continue; } - convertContext(cl, /*isClass*/ true); + convertContext(cl, /*isClass*/ true, swiftVersion); } // Write all protocols. llvm::StringSet<> knownProtocols; - for (const auto &pr : TheModule.Protocols) { + for (const auto &pr : items.Protocols) { // Check for duplicate protocol definitions. if (!knownProtocols.insert(pr.Name).second) { emitError("multiple definitions of protocol '" + pr.Name + "'"); continue; } - convertContext(pr, /*isClass*/ false); + convertContext(pr, /*isClass*/ false, swiftVersion); } // Write all global variables. llvm::StringSet<> knownGlobals; - for (const auto &global : TheModule.Globals) { + for (const auto &global : items.Globals) { // Check for duplicate global variables. if (!knownGlobals.insert(global.Name).second) { emitError("multiple definitions of global variable '" + @@ -786,12 +829,12 @@ namespace { info.SwiftName = global.SwiftName; if (global.Nullability) info.setNullabilityAudited(*global.Nullability); - Writer->addGlobalVariable(global.Name, info); + Writer->addGlobalVariable(global.Name, info, swiftVersion); } // Write all global functions. llvm::StringSet<> knownFunctions; - for (const auto &function : TheModule.Functions) { + for (const auto &function : items.Functions) { // Check for duplicate global functions. if (!knownFunctions.insert(function.Name).second) { emitError("multiple definitions of global function '" + @@ -810,12 +853,12 @@ namespace { function.NullabilityOfRet, info, function.Name); - Writer->addGlobalFunction(function.Name, info); + Writer->addGlobalFunction(function.Name, info, swiftVersion); } // Write all enumerators. llvm::StringSet<> knownEnumConstants; - for (const auto &enumConstant : TheModule.EnumConstants) { + for (const auto &enumConstant : items.EnumConstants) { // Check for duplicate enumerators if (!knownEnumConstants.insert(enumConstant.Name).second) { emitError("multiple definitions of enumerator '" + @@ -829,12 +872,12 @@ namespace { convertAvailability(enumConstant.Availability, info, enumConstant.Name); info.SwiftPrivate = enumConstant.SwiftPrivate; info.SwiftName = enumConstant.SwiftName; - Writer->addEnumConstant(enumConstant.Name, info); + Writer->addEnumConstant(enumConstant.Name, info, swiftVersion); } // Write all tags. llvm::StringSet<> knownTags; - for (const auto &t : TheModule.Tags) { + for (const auto &t : items.Tags) { // Check for duplicate tag definitions. if (!knownTags.insert(t.Name).second) { emitError("multiple definitions Of tag '" + t.Name + "'"); @@ -845,12 +888,12 @@ namespace { if (convertCommonType(t, tagInfo, t.Name)) continue; - Writer->addTag(t.Name, tagInfo); + Writer->addTag(t.Name, tagInfo, swiftVersion); } // Write all typedefs. llvm::StringSet<> knownTypedefs; - for (const auto &t : TheModule.Typedefs) { + for (const auto &t : items.Typedefs) { // Check for duplicate typedef definitions. if (!knownTypedefs.insert(t.Name).second) { emitError("multiple definitions of typedef '" + t.Name + "'"); @@ -860,9 +903,22 @@ namespace { TypedefInfo typedefInfo; if (convertCommonType(t, typedefInfo, t.Name)) continue; - - Writer->addTypedef(t.Name, typedefInfo); + + Writer->addTypedef(t.Name, typedefInfo, swiftVersion); } + } + + bool convertModule() { + if (!isAvailable(TheModule.Availability)) + return false; + + // Set up the writer. + // FIXME: This is kindof ugly. + APINotesWriter writer(TheModule.Name, SourceFile); + Writer = &writer; + + // Write the top-level items. + convertTopLevelItems(TheModule.TopLevel, VersionTuple()); if (TheModule.SwiftInferImportAsMember) { ModuleOptions opts; @@ -870,6 +926,12 @@ namespace { Writer->addModuleOptions(opts); } + // Convert the versioned information. + for (const auto &versioned : TheModule.SwiftVersions) { + convertTopLevelItems(versioned.Items, versioned.Version); + + } + if (!ErrorOccured) Writer->writeToStream(OS); @@ -934,10 +996,34 @@ namespace { /// The module we're building. Module TheModule; + /// A known context, which tracks what we know about a context ID. + struct KnownContext { + /// Whether this is a protocol (vs. a class). + bool isProtocol; + + /// The indices into the top-level items for this context at each + /// Swift version. + SmallVector, 1> indices; + + Class &getContext(const VersionTuple &swiftVersion, + TopLevelItems &items) { + ClassesSeq &seq = isProtocol ? items.Protocols : items.Classes; + + for (auto &index : indices) { + if (index.first == swiftVersion) + return seq[index.second]; + } + + indices.push_back({swiftVersion, seq.size()}); + seq.push_back(Class()); + return seq.back(); + } + }; + /// A mapping from context ID to a pair (index, is-protocol) that indicates /// the index of that class or protocol in the global "classes" or /// "protocols" list. - llvm::DenseMap> knownContexts; + llvm::DenseMap knownContexts; /// Copy a string into allocated memory so it does disappear on us. StringRef copyString(StringRef string) { @@ -1015,30 +1101,46 @@ namespace { } } + TopLevelItems &getTopLevelItems(VersionTuple swiftVersion) { + if (!swiftVersion) return TheModule.TopLevel; + + for (auto &versioned : TheModule.SwiftVersions) { + if (versioned.Version == swiftVersion) + return versioned.Items; + } + + TheModule.SwiftVersions.push_back(Versioned()); + TheModule.SwiftVersions.back().Version = swiftVersion; + return TheModule.SwiftVersions.back().Items; + } + public: virtual void visitObjCClass(ContextID contextID, StringRef name, - const ObjCContextInfo &info) { + const ObjCContextInfo &info, + VersionTuple swiftVersion) { // Record this known context. - knownContexts[contextID.Value] = { TheModule.Classes.size(), false }; + auto &items = getTopLevelItems(swiftVersion); + auto &known = knownContexts[contextID.Value]; + known.isProtocol = false; - // Add the class. - TheModule.Classes.push_back(Class()); - handleObjCContext(TheModule.Classes.back(), name, info); + handleObjCContext(known.getContext(swiftVersion, items), name, info); } virtual void visitObjCProtocol(ContextID contextID, StringRef name, - const ObjCContextInfo &info) { + const ObjCContextInfo &info, + VersionTuple swiftVersion) { // Record this known context. - knownContexts[contextID.Value] = { TheModule.Protocols.size(), true }; + auto &items = getTopLevelItems(swiftVersion); + auto &known = knownContexts[contextID.Value]; + known.isProtocol = true; - // Add the protocol. - TheModule.Protocols.push_back(Class()); - handleObjCContext(TheModule.Protocols.back(), name, info); + handleObjCContext(known.getContext(swiftVersion, items), name, info); } virtual void visitObjCMethod(ContextID contextID, StringRef selector, bool isInstanceMethod, - const ObjCMethodInfo &info) { + const ObjCMethodInfo &info, + VersionTuple swiftVersion) { Method method; method.Selector = copyString(selector); method.Kind = isInstanceMethod ? MethodKind::Instance : MethodKind::Class; @@ -1051,16 +1153,15 @@ namespace { method.DesignatedInit = info.DesignatedInit; method.Required = info.Required; - auto known = knownContexts[contextID.Value]; - if (known.second) - TheModule.Protocols[known.first].Methods.push_back(method); - else - TheModule.Classes[known.first].Methods.push_back(method); + auto &items = getTopLevelItems(swiftVersion); + knownContexts[contextID.Value].getContext(swiftVersion, items) + .Methods.push_back(method); } virtual void visitObjCProperty(ContextID contextID, StringRef name, bool isInstance, - const ObjCPropertyInfo &info) { + const ObjCPropertyInfo &info, + VersionTuple swiftVersion) { Property property; property.Name = name; property.Kind = isInstance ? MethodKind::Instance : MethodKind::Class; @@ -1071,15 +1172,14 @@ namespace { property.Nullability = *nullability; } - auto known = knownContexts[contextID.Value]; - if (known.second) - TheModule.Protocols[known.first].Properties.push_back(property); - else - TheModule.Classes[known.first].Properties.push_back(property); + auto &items = getTopLevelItems(swiftVersion); + knownContexts[contextID.Value].getContext(swiftVersion, items) + .Properties.push_back(property); } virtual void visitGlobalFunction(StringRef name, - const GlobalFunctionInfo &info) { + const GlobalFunctionInfo &info, + VersionTuple swiftVersion) { Function function; function.Name = name; handleCommon(function, info); @@ -1088,11 +1188,13 @@ namespace { handleNullability(function.Nullability, function.NullabilityOfRet, info, info.NumAdjustedNullable-1); - TheModule.Functions.push_back(function); + auto &items = getTopLevelItems(swiftVersion); + items.Functions.push_back(function); } virtual void visitGlobalVariable(StringRef name, - const GlobalVariableInfo &info) { + const GlobalVariableInfo &info, + VersionTuple swiftVersion) { GlobalVariable global; global.Name = name; handleCommon(global, info); @@ -1102,30 +1204,37 @@ namespace { global.Nullability = *nullability; } - TheModule.Globals.push_back(global); + auto &items = getTopLevelItems(swiftVersion); + items.Globals.push_back(global); } virtual void visitEnumConstant(StringRef name, - const EnumConstantInfo &info) { + const EnumConstantInfo &info, + VersionTuple swiftVersion) { EnumConstant enumConstant; enumConstant.Name = name; handleCommon(enumConstant, info); - TheModule.EnumConstants.push_back(enumConstant); + auto &items = getTopLevelItems(swiftVersion); + items.EnumConstants.push_back(enumConstant); } - virtual void visitTag(StringRef name, const TagInfo &info) { + virtual void visitTag(StringRef name, const TagInfo &info, + VersionTuple swiftVersion) { Tag tag; tag.Name = name; handleCommonType(tag, info); - TheModule.Tags.push_back(tag); + auto &items = getTopLevelItems(swiftVersion); + items.Tags.push_back(tag); } - virtual void visitTypedef(StringRef name, const TypedefInfo &info) { + virtual void visitTypedef(StringRef name, const TypedefInfo &info, + VersionTuple swiftVersion) { Typedef td; td.Name = name; handleCommonType(td, info); - TheModule.Typedefs.push_back(td); + auto &items = getTopLevelItems(swiftVersion); + items.Typedefs.push_back(td); } /// Retrieve the module. @@ -1138,38 +1247,16 @@ static unsigned flattenPropertyKind(llvm::Optional kind) { return kind ? (*kind == MethodKind::Instance ? 2 : 1) : 0; } -bool api_notes::decompileAPINotes(std::unique_ptr input, - llvm::raw_ostream &os) { - // Try to read the file. - auto reader = APINotesReader::get(std::move(input)); - if (!reader) { - llvm::errs() << "not a well-formed API notes binary file\n"; - return true; - } - - DecompileVisitor decompileVisitor; - reader->visit(decompileVisitor); - - // Sort the data in the module, because the API notes reader doesn't preserve - // order. - auto &module = decompileVisitor.getModule(); - - // Set module name. - module.Name = reader->getModuleName(); - - // Set module options - auto opts = reader->getModuleOptions(); - if (opts.SwiftInferImportAsMember) - module.SwiftInferImportAsMember = true; - +/// Sort the items in the given block of "top-level" items. +static void sortTopLevelItems(TopLevelItems &items) { // Sort classes. - std::sort(module.Classes.begin(), module.Classes.end(), + std::sort(items.Classes.begin(), items.Classes.end(), [](const Class &lhs, const Class &rhs) -> bool { return lhs.Name < rhs.Name; }); // Sort protocols. - std::sort(module.Protocols.begin(), module.Protocols.end(), + std::sort(items.Protocols.begin(), items.Protocols.end(), [](const Class &lhs, const Class &rhs) -> bool { return lhs.Name < rhs.Name; }); @@ -1180,52 +1267,90 @@ bool api_notes::decompileAPINotes(std::unique_ptr input, std::sort(record.Properties.begin(), record.Properties.end(), [](const Property &lhs, const Property &rhs) -> bool { return lhs.Name < rhs.Name || - (lhs.Name == rhs.Name && - flattenPropertyKind(lhs.Kind) < - flattenPropertyKind(rhs.Kind)); + (lhs.Name == rhs.Name && + flattenPropertyKind(lhs.Kind) < + flattenPropertyKind(rhs.Kind)); }); // Sort methods. std::sort(record.Methods.begin(), record.Methods.end(), [](const Method &lhs, const Method &rhs) -> bool { return lhs.Selector < rhs.Selector || - (lhs.Selector == rhs.Selector && - static_cast(lhs.Kind) - < static_cast(rhs.Kind)); + (lhs.Selector == rhs.Selector && + static_cast(lhs.Kind) + < static_cast(rhs.Kind)); }); }; - std::for_each(module.Classes.begin(), module.Classes.end(), sortMembers); - std::for_each(module.Protocols.begin(), module.Protocols.end(), sortMembers); + std::for_each(items.Classes.begin(), items.Classes.end(), sortMembers); + std::for_each(items.Protocols.begin(), items.Protocols.end(), sortMembers); // Sort functions. - std::sort(module.Functions.begin(), module.Functions.end(), + std::sort(items.Functions.begin(), items.Functions.end(), [](const Function &lhs, const Function &rhs) -> bool { return lhs.Name < rhs.Name; }); // Sort global variables. - std::sort(module.Globals.begin(), module.Globals.end(), + std::sort(items.Globals.begin(), items.Globals.end(), [](const GlobalVariable &lhs, const GlobalVariable &rhs) -> bool { return lhs.Name < rhs.Name; }); // Sort enum constants. - std::sort(module.EnumConstants.begin(), module.EnumConstants.end(), + std::sort(items.EnumConstants.begin(), items.EnumConstants.end(), [](const EnumConstant &lhs, const EnumConstant &rhs) -> bool { return lhs.Name < rhs.Name; }); // Sort tags. - std::sort(module.Tags.begin(), module.Tags.end(), + std::sort(items.Tags.begin(), items.Tags.end(), [](const Tag &lhs, const Tag &rhs) -> bool { return lhs.Name < rhs.Name; }); // Sort typedefs. - std::sort(module.Typedefs.begin(), module.Typedefs.end(), + std::sort(items.Typedefs.begin(), items.Typedefs.end(), [](const Typedef &lhs, const Typedef &rhs) -> bool { return lhs.Name < rhs.Name; }); +} + +bool api_notes::decompileAPINotes(std::unique_ptr input, + llvm::raw_ostream &os) { + // Try to read the file. + auto reader = APINotesReader::get(std::move(input)); + if (!reader) { + llvm::errs() << "not a well-formed API notes binary file\n"; + return true; + } + + DecompileVisitor decompileVisitor; + reader->visit(decompileVisitor); + + // Sort the data in the module, because the API notes reader doesn't preserve + // order. + auto &module = decompileVisitor.getModule(); + + // Set module name. + module.Name = reader->getModuleName(); + + // Set module options + auto opts = reader->getModuleOptions(); + if (opts.SwiftInferImportAsMember) + module.SwiftInferImportAsMember = true; + + // Sort the top-level items. + sortTopLevelItems(module.TopLevel); + + // Sort the Swift versions. + std::sort(module.SwiftVersions.begin(), module.SwiftVersions.end(), + [](const Versioned &lhs, const Versioned &rhs) -> bool { + return lhs.Version < rhs.Version; + }); + + // Sort the top-level items within each Swift version. + for (auto &versioned : module.SwiftVersions) + sortTopLevelItems(versioned.Items); // Output the YAML representation. Output yout(os); diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 29861d140f242..4a666ccbd9421 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -352,8 +352,8 @@ void Sema::ProcessAPINotes(Decl *D) { // Objective-C classes. if (auto Class = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupObjCClass(Class->getName())) { - ::ProcessAPINotes(*this, Class, Info->second); + if (auto Info = Reader->lookupObjCClassInfo(Class->getName())) { + ::ProcessAPINotes(*this, Class, *Info); } } @@ -363,8 +363,8 @@ void Sema::ProcessAPINotes(Decl *D) { // Objective-C protocols. if (auto Protocol = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupObjCProtocol(Protocol->getName())) { - ::ProcessAPINotes(*this, Protocol, Info->second); + if (auto Info = Reader->lookupObjCProtocolInfo(Protocol->getName())) { + ::ProcessAPINotes(*this, Protocol, *Info); } } @@ -414,8 +414,8 @@ void Sema::ProcessAPINotes(Decl *D) { auto GetContext = [&](api_notes::APINotesReader *Reader) -> Optional { if (auto Protocol = dyn_cast(ObjCContainer)) { - if (auto Found = Reader->lookupObjCProtocol(Protocol->getName())) - return Found->first; + if (auto Found = Reader->lookupObjCProtocolID(Protocol->getName())) + return *Found; return None; } @@ -442,8 +442,8 @@ void Sema::ProcessAPINotes(Decl *D) { } if (auto Class = dyn_cast(ObjCContainer)) { - if (auto Found = Reader->lookupObjCClass(Class->getName())) - return Found->first; + if (auto Found = Reader->lookupObjCClassID(Class->getName())) + return *Found; return None; diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 10ac249817d73..c0e91f93fde0f 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -146,3 +146,22 @@ Typedefs: SwiftName: Typedef SwiftBridge: '' NSErrorDomain: '' +SwiftVersions: + - Version: 3.0 + Classes: + - Name: NSCell + Availability: available + AvailabilityMsg: '' + SwiftPrivate: false + SwiftName: NSBox + SwiftBridge: '' + NSErrorDomain: '' + Methods: + - Selector: init + MethodKind: Instance + NullabilityOfRet: N + Availability: available + AvailabilityMsg: '' + SwiftPrivate: true + SwiftName: '' + DesignatedInit: true From 940124f4ac92308177e259f881916cf98b733261 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 3 Oct 2016 00:12:26 -0700 Subject: [PATCH 093/585] [Modules] Fix module hash computation when module file extensions are involved. Fix an egregious error in with modules and module file extensions, where only the hash code of the module file extension and sysroots would affect the module cache, leading to module file collisions. The effect of this error was likely masked by the old client of module file extensions (Swift) embedding Clang version information. apple-llvm-split-commit: 977557058d1208e7d099569d3327c00c1aa4b1cf apple-llvm-split-dir: clang/ --- clang/lib/Frontend/CompilerInvocation.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 3b3870df95598..663364ddb966d 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -2548,7 +2548,9 @@ std::string CompilerInvocation::getModuleHash() const { // Extend the signature with the module file extensions. const FrontendOptions &frontendOpts = getFrontendOpts(); for (const auto &ext : frontendOpts.ModuleFileExtensions) { - code = ext->hashExtension(code); + code = hash_combine(code, ext->hashExtension(code)); + } + } // Darwin-specific hack: if we have a sysroot, use the contents and From 11403d1119df7dd1709fc657c99357fedb15a169 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 3 Oct 2016 00:12:34 -0700 Subject: [PATCH 094/585] [API Notes] Add a command-line option for Swift version selection. Introduce -fapinotes-swift-version=XX.YY, which sets the Swift version to use when applying versioned API notes to the Clang AST. When set, a versioned API note that matches the provided Swift version will take precedence over an unversioned API note. More of rdar://problem/28455809. apple-llvm-split-commit: e48790bfa156772574ef81db98f79da29eebe445 apple-llvm-split-dir: clang/ --- .../include/clang/APINotes/APINotesManager.h | 9 ++ .../include/clang/APINotes/APINotesOptions.h | 4 + clang/include/clang/APINotes/APINotesReader.h | 84 ++++++++++++--- clang/include/clang/Driver/Options.td | 3 + clang/lib/APINotes/APINotesManager.cpp | 7 +- clang/lib/APINotes/APINotesReader.cpp | 102 +++++++++++------- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 2 +- clang/lib/Frontend/CompilerInstance.cpp | 3 + clang/lib/Frontend/CompilerInvocation.cpp | 20 +++- .../APINotes/SomeKit.apinotes | 9 ++ clang/test/APINotes/nullability.m | 12 ++- 11 files changed, 195 insertions(+), 60 deletions(-) diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h index a300c14ee9dd3..2adc29c0bf4c2 100644 --- a/clang/include/clang/APINotes/APINotesManager.h +++ b/clang/include/clang/APINotes/APINotesManager.h @@ -16,6 +16,7 @@ #include "clang/Basic/SourceLocation.h" #include "clang/Basic/Module.h" +#include "clang/Basic/VersionTuple.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/PointerUnion.h" @@ -55,6 +56,9 @@ class APINotesManager { /// source file from which an entity was declared. bool ImplicitAPINotes; + /// The Swift version to use when interpreting versioned API notes. + VersionTuple SwiftVersion; + /// API notes readers for the current module. /// /// There can be up to two of these, one for public headers and one @@ -109,6 +113,11 @@ class APINotesManager { APINotesManager(SourceManager &sourceMgr, const LangOptions &langOpts); ~APINotesManager(); + /// Set the Swift version to use when filtering API notes. + void setSwiftVersion(VersionTuple swiftVersion) { + SwiftVersion = swiftVersion; + } + /// Load the API notes for the current module. /// /// \param module The current module. diff --git a/clang/include/clang/APINotes/APINotesOptions.h b/clang/include/clang/APINotes/APINotesOptions.h index 01c7513a1d317..24bb9134b21b2 100644 --- a/clang/include/clang/APINotes/APINotesOptions.h +++ b/clang/include/clang/APINotes/APINotesOptions.h @@ -14,6 +14,7 @@ #ifndef LLVM_CLANG_APINOTES_APINOTESOPTIONS_H #define LLVM_CLANG_APINOTES_APINOTESOPTIONS_H +#include "clang/Basic/VersionTuple.h" #include #include @@ -23,6 +24,9 @@ namespace clang { /// notes are found and handled. class APINotesOptions { public: + /// The Swift version which should be used for API notes. + VersionTuple SwiftVersion; + /// The set of search paths where we API notes can be found for /// particular modules. /// diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 593e8174d4e08..a09bcc76df181 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -33,7 +33,7 @@ class APINotesReader { Implementation &Impl; APINotesReader(llvm::MemoryBuffer *inputBuffer, bool ownsInputBuffer, - bool &failed); + VersionTuple swiftVersion, bool &failed); public: /// Create a new API notes reader from the given member buffer, which @@ -41,14 +41,16 @@ class APINotesReader { /// /// \returns the new API notes reader, or null if an error occurred. static std::unique_ptr - get(std::unique_ptr inputBuffer); + get(std::unique_ptr inputBuffer, + VersionTuple swiftVersion); /// Create a new API notes reader from the given member buffer, which /// contains the contents of a binary API notes file. /// /// \returns the new API notes reader, or null if an error occurred. static std::unique_ptr - getUnmanaged(llvm::MemoryBuffer *inputBuffer); + getUnmanaged(llvm::MemoryBuffer *inputBuffer, + VersionTuple swiftVersion); ~APINotesReader(); @@ -66,6 +68,56 @@ class APINotesReader { /// Retrieve the module options ModuleOptions getModuleOptions() const; + /// Captures the completed versioned information for a particular part of + /// API notes, including both unversioned API notes and each versioned API + /// note for that particular entity. + template + class VersionedInfo { + /// The complete set of results. + SmallVector, 1> Results; + + /// The index of the result that is the "selected" set based on the desired + /// Swift version, or \c Results.size() if nothing matched. + unsigned Selected; + + public: + /// Form an empty set of versioned information. + VersionedInfo(llvm::NoneType) : Selected(0) { } + + /// Form a versioned info set given the desired version and a set of + /// results. + VersionedInfo(VersionTuple version, + SmallVector, 1> results); + + /// Determine whether there is a result that should be applied directly + /// to the AST. + explicit operator bool() const { return Selected != size(); } + + /// Retrieve the information to apply directly to the AST. + const T& operator*() const { + assert(*this && "No result to apply directly"); + return (*this)[Selected].second; + } + + /// Retrieve the selected index in the result set. + Optional getSelected() const { + if (Selected == Results.size()) return None; + return Selected; + } + + /// Return the number of versioned results we know about. + unsigned size() const { return Results.size(); } + + /// Access all versioned results. + const std::pair *begin() const { return Results.begin(); } + const std::pair *end() const { return Results.end(); } + + /// Access a specific versioned result. + const std::pair &operator[](unsigned index) const { + return Results[index]; + } + }; + /// Look for the context ID of the given Objective-C class. /// /// \param name The name of the class we're looking for. @@ -78,7 +130,7 @@ class APINotesReader { /// \param name The name of the class we're looking for. /// /// \returns The information about the class, if known. - Optional lookupObjCClassInfo(StringRef name); + VersionedInfo lookupObjCClassInfo(StringRef name); /// Look for the context ID of the given Objective-C protocol. /// @@ -92,7 +144,7 @@ class APINotesReader { /// \param name The name of the protocol we're looking for. /// /// \returns The information about the protocol, if known. - Optional lookupObjCProtocolInfo(StringRef name); + VersionedInfo lookupObjCProtocolInfo(StringRef name); /// Look for information regarding the given Objective-C property in /// the given context. @@ -104,9 +156,9 @@ class APINotesReader { /// \param swiftVersion The Swift version to filter for, if any. /// /// \returns Information about the property, if known. - Optional lookupObjCProperty(ContextID contextID, - StringRef name, - bool isInstance); + VersionedInfo lookupObjCProperty(ContextID contextID, + StringRef name, + bool isInstance); /// Look for information regarding the given Objective-C method in /// the given context. @@ -116,30 +168,30 @@ class APINotesReader { /// \param isInstanceMethod Whether we are looking for an instance method. /// /// \returns Information about the method, if known. - Optional lookupObjCMethod(ContextID contextID, - ObjCSelectorRef selector, - bool isInstanceMethod); + VersionedInfo lookupObjCMethod(ContextID contextID, + ObjCSelectorRef selector, + bool isInstanceMethod); /// Look for information regarding the given global variable. /// /// \param name The name of the global variable. /// /// \returns information about the global variable, if known. - Optional lookupGlobalVariable(StringRef name); + VersionedInfo lookupGlobalVariable(StringRef name); /// Look for information regarding the given global function. /// /// \param name The name of the global function. /// /// \returns information about the global function, if known. - Optional lookupGlobalFunction(StringRef name); + VersionedInfo lookupGlobalFunction(StringRef name); /// Look for information regarding the given enumerator. /// /// \param name The name of the enumerator. /// /// \returns information about the enumerator, if known. - Optional lookupEnumConstant(StringRef name); + VersionedInfo lookupEnumConstant(StringRef name); /// Look for information regarding the given tag /// (struct/union/enum/C++ class). @@ -147,14 +199,14 @@ class APINotesReader { /// \param name The name of the tag. /// /// \returns information about the tag, if known. - Optional lookupTag(StringRef name); + VersionedInfo lookupTag(StringRef name); /// Look for information regarding the given typedef. /// /// \param name The name of the typedef. /// /// \returns information about the typedef, if known. - Optional lookupTypedef(StringRef name); + VersionedInfo lookupTypedef(StringRef name); /// Visitor used when walking the contents of the API notes file. class Visitor { diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 4c2e97d667502..b055f2ced00b1 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -558,6 +558,9 @@ def fno_apinotes_modules : Flag<["-"], "fno-apinotes-modules">, Group">, HelpText<"Specify the API notes cache path">; +def fapinotes_swift_version : Joined<["-"], "fapinotes-swift-version=">, + Group, Flags<[CC1Option]>, MetaVarName<"">, + HelpText<"Specify the Swift version to use when filtering API notes">; def fblocks : Flag<["-"], "fblocks">, Group, Flags<[CC1Option]>, HelpText<"Enable the 'blocks' language feature">; diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index 834d7e1f72f41..1622f8f40675a 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -153,7 +153,7 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { if (!buffer) return nullptr; // Load the binary form. - return APINotesReader::getUnmanaged(buffer); + return APINotesReader::getUnmanaged(buffer, SwiftVersion); } // If we haven't pruned the API notes cache yet during this execution, do @@ -184,7 +184,8 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { // Load the file contents. if (auto buffer = fileMgr.getBufferForFile(compiledFile)) { // Load the file. - if (auto reader = APINotesReader::get(std::move(buffer.get()))) { + if (auto reader = APINotesReader::get(std::move(buffer.get()), + SwiftVersion)) { bool outOfDate = false; if (auto sizeAndModTime = reader->getSourceFileSizeAndModTime()) { if (sizeAndModTime->first != apiNotesFile->getSize() || @@ -272,7 +273,7 @@ APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { } // Load the binary form we just compiled. - auto reader = APINotesReader::get(std::move(compiledBuffer)); + auto reader = APINotesReader::get(std::move(compiledBuffer), SwiftVersion); assert(reader && "Could not load the API notes we just generated?"); return reader; } diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 71e1e411602b2..dda721e411293 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -486,6 +486,9 @@ class APINotesReader::Implementation { /// Whether we own the input buffer. bool OwnsInputBuffer; + /// The Swift version to use for filtering. + VersionTuple SwiftVersion; + /// The reader attached to \c InputBuffer. llvm::BitstreamReader InputReader; @@ -1252,6 +1255,7 @@ bool APINotesReader::Implementation::readTypedefBlock( APINotesReader::APINotesReader(llvm::MemoryBuffer *inputBuffer, bool ownsInputBuffer, + VersionTuple swiftVersion, bool &failed) : Impl(*new Implementation) { @@ -1260,6 +1264,7 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *inputBuffer, // Initialize the input buffer. Impl.InputBuffer = inputBuffer; Impl.OwnsInputBuffer = ownsInputBuffer; + Impl.SwiftVersion = swiftVersion; Impl.InputReader.init( reinterpret_cast(Impl.InputBuffer->getBufferStart()), reinterpret_cast(Impl.InputBuffer->getBufferEnd())); @@ -1399,11 +1404,12 @@ APINotesReader::~APINotesReader() { } std::unique_ptr -APINotesReader::get(std::unique_ptr inputBuffer) { +APINotesReader::get(std::unique_ptr inputBuffer, + VersionTuple swiftVersion) { bool failed = false; std::unique_ptr reader(new APINotesReader(inputBuffer.release(), /*ownsInputBuffer=*/true, - failed)); + swiftVersion, failed)); if (failed) return nullptr; @@ -1411,11 +1417,12 @@ APINotesReader::get(std::unique_ptr inputBuffer) { } std::unique_ptr -APINotesReader::getUnmanaged(llvm::MemoryBuffer *inputBuffer) { +APINotesReader::getUnmanaged(llvm::MemoryBuffer *inputBuffer, + VersionTuple swiftVersion) { bool failed = false; std::unique_ptr reader(new APINotesReader(inputBuffer, /*ownsInputBuffer=*/false, - failed)); + swiftVersion, failed)); if (failed) return nullptr; @@ -1435,15 +1442,31 @@ ModuleOptions APINotesReader::getModuleOptions() const { return Impl.ModuleOpts; } -namespace { - template - Optional getUnversioned( - const SmallVectorImpl>& array) { - for (const auto &versioned : array) { - if (!versioned.first) return versioned.second; +template +APINotesReader::VersionedInfo::VersionedInfo( + VersionTuple version, + SmallVector, 1> results) + : Results(std::move(results)) { + + // Look for an exact version match. + Optional unversioned; + Selected = Results.size(); + for (unsigned i = 0, n = Results.size(); i != n; ++i) { + if (Results[i].first == version) { + Selected = i; + break; + } + + if (!Results[i].first) { + assert(!unversioned && "Two unversioned entries?"); + unversioned = i; } - return None; } + + // If we didn't find a match but we have an unversioned result, use the + // unversioned result. + if (Selected == Results.size() && unversioned) + Selected = *unversioned; } auto APINotesReader::lookupObjCClassID(StringRef name) -> Optional { @@ -1462,7 +1485,7 @@ auto APINotesReader::lookupObjCClassID(StringRef name) -> Optional { } auto APINotesReader::lookupObjCClassInfo(StringRef name) - -> Optional { + -> VersionedInfo { if (!Impl.ObjCContextInfoTable) return None; @@ -1474,7 +1497,7 @@ auto APINotesReader::lookupObjCClassInfo(StringRef name) if (knownInfo == Impl.ObjCContextInfoTable->end()) return None; - return getUnversioned(*knownInfo); + return { Impl.SwiftVersion, *knownInfo }; } auto APINotesReader::lookupObjCProtocolID(StringRef name) @@ -1494,7 +1517,7 @@ auto APINotesReader::lookupObjCProtocolID(StringRef name) } auto APINotesReader::lookupObjCProtocolInfo(StringRef name) - -> Optional { + -> VersionedInfo { if (!Impl.ObjCContextInfoTable) return None; @@ -1506,13 +1529,14 @@ auto APINotesReader::lookupObjCProtocolInfo(StringRef name) if (knownInfo == Impl.ObjCContextInfoTable->end()) return None; - return getUnversioned(*knownInfo); + return { Impl.SwiftVersion, *knownInfo }; } -Optional APINotesReader::lookupObjCProperty( - ContextID contextID, - StringRef name, - bool isInstance) { + +auto APINotesReader::lookupObjCProperty(ContextID contextID, + StringRef name, + bool isInstance) + -> VersionedInfo { if (!Impl.ObjCPropertyTable) return None; @@ -1526,13 +1550,14 @@ Optional APINotesReader::lookupObjCProperty( if (known == Impl.ObjCPropertyTable->end()) return None; - return getUnversioned(*known); + return { Impl.SwiftVersion, *known }; } -Optional APINotesReader::lookupObjCMethod( - ContextID contextID, - ObjCSelectorRef selector, - bool isInstanceMethod) { +auto APINotesReader::lookupObjCMethod( + ContextID contextID, + ObjCSelectorRef selector, + bool isInstanceMethod) + -> VersionedInfo { if (!Impl.ObjCMethodTable) return None; @@ -1546,11 +1571,12 @@ Optional APINotesReader::lookupObjCMethod( if (known == Impl.ObjCMethodTable->end()) return None; - return getUnversioned(*known); + return { Impl.SwiftVersion, *known }; } -Optional APINotesReader::lookupGlobalVariable( - StringRef name) { +auto APINotesReader::lookupGlobalVariable( + StringRef name) + -> VersionedInfo { if (!Impl.GlobalVariableTable) return None; @@ -1562,11 +1588,11 @@ Optional APINotesReader::lookupGlobalVariable( if (known == Impl.GlobalVariableTable->end()) return None; - return getUnversioned(*known); + return { Impl.SwiftVersion, *known }; } -Optional APINotesReader::lookupGlobalFunction( - StringRef name) { +auto APINotesReader::lookupGlobalFunction(StringRef name) + -> VersionedInfo { if (!Impl.GlobalFunctionTable) return None; @@ -1578,10 +1604,11 @@ Optional APINotesReader::lookupGlobalFunction( if (known == Impl.GlobalFunctionTable->end()) return None; - return getUnversioned(*known); + return { Impl.SwiftVersion, *known }; } -Optional APINotesReader::lookupEnumConstant(StringRef name) { +auto APINotesReader::lookupEnumConstant(StringRef name) + -> VersionedInfo { if (!Impl.EnumConstantTable) return None; @@ -1593,10 +1620,10 @@ Optional APINotesReader::lookupEnumConstant(StringRef name) { if (known == Impl.EnumConstantTable->end()) return None; - return getUnversioned(*known); + return { Impl.SwiftVersion, *known }; } -Optional APINotesReader::lookupTag(StringRef name) { +auto APINotesReader::lookupTag(StringRef name) -> VersionedInfo { if (!Impl.TagTable) return None; @@ -1608,10 +1635,11 @@ Optional APINotesReader::lookupTag(StringRef name) { if (known == Impl.TagTable->end()) return None; - return getUnversioned(*known); + return { Impl.SwiftVersion, *known }; } -Optional APINotesReader::lookupTypedef(StringRef name) { +auto APINotesReader::lookupTypedef(StringRef name) + -> VersionedInfo { if (!Impl.TypedefTable) return None; @@ -1623,7 +1651,7 @@ Optional APINotesReader::lookupTypedef(StringRef name) { if (known == Impl.TypedefTable->end()) return None; - return getUnversioned(*known); + return { Impl.SwiftVersion, *known }; } APINotesReader::Visitor::~Visitor() { } diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index f15099e4677ca..8f1e5a29d4ebd 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -1318,7 +1318,7 @@ static void sortTopLevelItems(TopLevelItems &items) { bool api_notes::decompileAPINotes(std::unique_ptr input, llvm::raw_ostream &os) { // Try to read the file. - auto reader = APINotesReader::get(std::move(input)); + auto reader = APINotesReader::get(std::move(input), VersionTuple()); if (!reader) { llvm::errs() << "not a well-formed API notes binary file\n"; return true; diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 43d89485d8ab5..3696149f69476 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -540,6 +540,9 @@ void CompilerInstance::createSema(TranslationUnitKind TUKind, TheSema.reset(new Sema(getPreprocessor(), getASTContext(), getASTConsumer(), TUKind, CompletionConsumer)); + // Set up API notes. + TheSema->APINotes.setSwiftVersion(getAPINotesOpts().SwiftVersion); + // If we're building a module and are supposed to load API notes, // notify the API notes manager. if (auto currentModule = getPreprocessor().getCurrentModule()) { diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 663364ddb966d..6df547942d6bd 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1505,8 +1505,14 @@ static void ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args) { Opts.AddVFSOverlayFile(A->getValue()); } -static void ParseAPINotesArgs(APINotesOptions &Opts, ArgList &Args) { +static void ParseAPINotesArgs(APINotesOptions &Opts, ArgList &Args, + DiagnosticsEngine &diags) { using namespace options; + if (const Arg *A = Args.getLastArg(OPT_fapinotes_swift_version)) { + if (Opts.SwiftVersion.tryParse(A->getValue())) + diags.Report(diag::err_drv_invalid_value) + << A->getAsString(Args) << A->getValue(); + } for (const Arg *A : Args.filtered(OPT_iapinotes_modules)) Opts.ModuleSearchPaths.push_back(A->getValue()); } @@ -2415,7 +2421,7 @@ bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Res, Success &= ParseCodeGenArgs(Res.getCodeGenOpts(), Args, DashX, Diags, Res.getTargetOpts()); ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args); - ParseAPINotesArgs(Res.getAPINotesOpts(), Args); + ParseAPINotesArgs(Res.getAPINotesOpts(), Args, Diags); if (DashX == IK_AST || DashX == IK_LLVM_IR) { // ObjCAAutoRefCount and Sanitize LangOpts are used to setup the @@ -2551,6 +2557,16 @@ std::string CompilerInvocation::getModuleHash() const { code = hash_combine(code, ext->hashExtension(code)); } + // Extend the signature with the SWift version for API notes. + const APINotesOptions &apiNotesOpts = getAPINotesOpts(); + if (apiNotesOpts.SwiftVersion) { + code = hash_combine(code, apiNotesOpts.SwiftVersion.getMajor()); + if (auto minor = apiNotesOpts.SwiftVersion.getMinor()) + code = hash_combine(code, *minor); + if (auto subminor = apiNotesOpts.SwiftVersion.getSubminor()) + code = hash_combine(code, *subminor); + if (auto build = apiNotesOpts.SwiftVersion.getBuild()) + code = hash_combine(code, *build); } // Darwin-specific hack: if we have a sysroot, use the contents and diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes index e79a210e39aaf..9c855c6dc6ac8 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes @@ -31,3 +31,12 @@ Classes: - Selector: "initWithA:" MethodKind: Instance DesignatedInit: true +SwiftVersions: + - Version: 3.0 + Classes: + - Name: A + Methods: + - Selector: "transform:integer:" + MethodKind: Instance + NullabilityOfRet: O + Nullability: [ O, S ] diff --git a/clang/test/APINotes/nullability.m b/clang/test/APINotes/nullability.m index fc149b465ea35..c11cea5bd520d 100644 --- a/clang/test/APINotes/nullability.m +++ b/clang/test/APINotes/nullability.m @@ -1,12 +1,22 @@ // RUN: rm -rf %t && mkdir -p %t // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// Test with Swift version 3.0. This should only affect the few APIs that have an entry in the 3.0 tables. + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3.0 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify -DSWIFT_VERSION_3_0 + #import int main() { A *a; - [a transform: 0 integer: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} +#if SWIFT_VERSION_3_0 + float *fp = // expected-warning{{incompatible pointer types initializing 'float *' with an expression of type 'A * _Nullable'}} + [a transform: 0 integer: 0]; +#else + float *fp = // expected-warning{{incompatible pointer types initializing 'float *' with an expression of type 'A *'}} + [a transform: 0 integer: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} +#endif [a setNonnullAInstance: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} [A setNonnullAInstance: 0]; // no warning From 49e71de6b635d32c46de68a91cfc7de00a014198 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 3 Oct 2016 00:39:43 -0700 Subject: [PATCH 095/585] [API Notes] Look for framework API notes in Headers/PrivateHeaders. Rather than relying on the addition of a new "top-level" directory within a framework, have frameworks put API notes alongside the headers. apple-llvm-split-commit: 1237d45515611f604812d85758bf0bf0fea927e1 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesManager.cpp | 16 ++++--- .../Headers/SomeKit.apinotes | 42 +++++++++++++++++++ .../Headers/SomeKitExplicitNullability.h | 5 +++ .../PrivateHeaders/SomeKit_private.apinotes | 15 +++++++ .../Headers/SomeOtherKit.apinotes | 8 ++++ 5 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKitExplicitNullability.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_private.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Headers/SomeOtherKit.apinotes diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index 1622f8f40675a..5d50d3cd1dd4e 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -396,14 +396,20 @@ bool APINotesManager::loadCurrentModuleAPINotes( }; if (module->IsFramework) { - // For frameworks, we search in the "APINotes" subdirectory. + // For frameworks, we search in the "Headers" or "PrivateHeaders" + // subdirectory. llvm::SmallString<128> path; path += module->Directory->getName(); - llvm::sys::path::append(path, "APINotes"); - if (auto apinotesDir = fileMgr.getDirectory(path)) { + unsigned pathLen = path.size(); + + llvm::sys::path::append(path, "Headers"); + if (auto apinotesDir = fileMgr.getDirectory(path)) tryAPINotes(apinotesDir, /*wantPublic=*/true); - tryAPINotes(apinotesDir, /*wantPublic=*/false); - } + + path.resize(pathLen); + llvm::sys::path::append(path, "PrivateHeaders"); + if (auto privateAPINotesDir = fileMgr.getDirectory(path)) + tryAPINotes(privateAPINotesDir, /*wantPublic=*/false); } else { tryAPINotes(module->Directory, /*wantPublic=*/true); tryAPINotes(module->Directory, /*wantPublic=*/false); diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes new file mode 100644 index 0000000000000..9c855c6dc6ac8 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes @@ -0,0 +1,42 @@ +Name: SomeKit +Classes: + - Name: A + Methods: + - Selector: "transform:" + MethodKind: Instance + Availability: none + AvailabilityMsg: "anything but this" + - Selector: "transform:integer:" + MethodKind: Instance + NullabilityOfRet: N + Nullability: [ N, S ] + Properties: + - Name: intValue + PropertyKind: Instance + Availability: none + AvailabilityMsg: "wouldn't work anyway" + - Name: nonnullAInstance + PropertyKind: Instance + Nullability: N + - Name: nonnullAClass + PropertyKind: Class + Nullability: N + - Name: nonnullABoth + Nullability: N + - Name: B + Availability: none + AvailabilityMsg: "just don't" + - Name: C + Methods: + - Selector: "initWithA:" + MethodKind: Instance + DesignatedInit: true +SwiftVersions: + - Version: 3.0 + Classes: + - Name: A + Methods: + - Selector: "transform:integer:" + MethodKind: Instance + NullabilityOfRet: O + Nullability: [ O, S ] diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKitExplicitNullability.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKitExplicitNullability.h new file mode 100644 index 0000000000000..40be241eb934f --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKitExplicitNullability.h @@ -0,0 +1,5 @@ +@interface A(ExplicitNullabilityProperties) +@property (nonatomic, readwrite, retain, nonnull) A *explicitNonnullInstance; +@property (nonatomic, readwrite, retain, nullable) A *explicitNullableInstance; +@end + diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_private.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_private.apinotes new file mode 100644 index 0000000000000..28ede9dfa25c0 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/PrivateHeaders/SomeKit_private.apinotes @@ -0,0 +1,15 @@ +Name: SomeKit +Classes: + - Name: A + Methods: + - Selector: "privateTransform:input:" + MethodKind: Instance + NullabilityOfRet: N + Nullability: [ N, S ] + Properties: + - Name: internalProperty + Nullability: N +Protocols: + - Name: InternalProtocol + Availability: none + AvailabilityMsg: "not for you" diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Headers/SomeOtherKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Headers/SomeOtherKit.apinotes new file mode 100644 index 0000000000000..2ad546b8f8bcc --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SomeOtherKit.framework/Headers/SomeOtherKit.apinotes @@ -0,0 +1,8 @@ +Name: SomeOtherKit +Classes: + - Name: A + Methods: + - Selector: "methodA" + MethodKind: Instance + Availability: none + AvailabilityMsg: "anything but this" From e1edc62ef482909c2ddce792f9c5d700dd34aad8 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 3 Oct 2016 11:41:02 -0700 Subject: [PATCH 096/585] [API Notes] Fix serialization of FactoryAsInit data. apple-llvm-split-commit: 8099c09e822b238ae700ab27d8a6d45e2b3adcf3 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesWriter.cpp | 6 +++--- clang/test/APINotes/Inputs/roundtrip.apinotes | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 36c504afc34cd..6debe997bf001 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -721,9 +721,9 @@ namespace { } void emitUnversionedInfo(raw_ostream &out, const ObjCMethodInfo &info) { - uint8_t payload = info.FactoryAsInit << 2; - payload = (payload | info.DesignatedInit) << 1; - payload = (payload | info.Required); + uint8_t payload = info.FactoryAsInit; + payload = (payload << 1) | info.DesignatedInit; + payload = (payload << 1) | info.Required; endian::Writer writer(out); writer.write(payload); diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index c0e91f93fde0f..13e08a1126b0b 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -12,6 +12,13 @@ Classes: SwiftBridge: '' NSErrorDomain: '' Methods: + - Selector: 'cellWithImage:' + MethodKind: Class + Availability: available + AvailabilityMsg: '' + SwiftPrivate: false + SwiftName: '' + FactoryAsInit: C - Selector: init MethodKind: Instance NullabilityOfRet: U From 1e92684e88abd8a69aff45fc4a5f5593274a0cae Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 4 Oct 2016 10:41:35 -0700 Subject: [PATCH 097/585] [API Notes] Allow versioned API notes to override information in headers. When a versioned API note is used, allow it to override information explicitly described in a header. This allows a workflow where the headers are considered the "truth" for the latest version of Swift, but version-specific API notes can reset the state of the headers back to a prior version of Swift. At present, this only effectively works for nullability, because most of the Boolean or naming-related API notes fields don't distinguish between "not specified" and "false/empty". Part of rdar://problem/28455809. apple-llvm-split-commit: abcdca4e15477521394af9735db6b04c6f0d5892 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 22 ++ clang/include/clang/AST/AttrIterator.h | 2 + clang/include/clang/Sema/Sema.h | 6 +- clang/lib/APINotes/APINotesReader.cpp | 11 +- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 1 - clang/lib/Sema/SemaAPINotes.cpp | 311 ++++++++++++------ clang/lib/Sema/SemaType.cpp | 37 ++- .../APINotes/SomeKit.apinotes | 7 + .../Headers/SomeKit.apinotes | 7 + .../SomeKit.framework/Headers/SomeKit.h | 2 + .../Headers/VersionedKit.apinotes | 6 + .../Headers/VersionedKit.h | 1 + .../Modules/module.modulemap | 5 + clang/test/APINotes/nullability.m | 12 +- clang/test/APINotes/versioned.m | 15 + 15 files changed, 342 insertions(+), 103 deletions(-) create mode 100644 clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Modules/module.modulemap create mode 100644 clang/test/APINotes/versioned.m diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index a09bcc76df181..f96c7b65c9154 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -25,6 +25,20 @@ namespace clang { namespace api_notes { +/// Describes the role of a specific bit of versioned information. +enum class VersionedInfoRole : unsigned { + /// Augment the AST, but do not override information explicitly specified + /// in the source code. + AugmentSource, + + /// Replace information that may have been explicitly specified in the source + /// code. + ReplaceSource, + + /// Describes an alternate version of this information. + Versioned, +}; + /// A class that reads API notes data from a binary file that was written by /// the \c APINotesWriter. class APINotesReader { @@ -80,6 +94,9 @@ class APINotesReader { /// Swift version, or \c Results.size() if nothing matched. unsigned Selected; + /// The role of the selected index. + VersionedInfoRole SelectedRole; + public: /// Form an empty set of versioned information. VersionedInfo(llvm::NoneType) : Selected(0) { } @@ -105,6 +122,11 @@ class APINotesReader { return Selected; } + /// Describes the role of the selected entity. + VersionedInfoRole getSelectedRole() const { + return SelectedRole; + } + /// Return the number of versioned results we know about. unsigned size() const { return Results.size(); } diff --git a/clang/include/clang/AST/AttrIterator.h b/clang/include/clang/AST/AttrIterator.h index fb9b049e5d6b1..5ef250cf81499 100644 --- a/clang/include/clang/AST/AttrIterator.h +++ b/clang/include/clang/AST/AttrIterator.h @@ -108,6 +108,8 @@ class specific_attr_iterator { specific_attr_iterator Right) { return !(Left == Right); } + + Iterator getCurrent() const { return Current; } }; template diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 06800e3ece627..518aee137db12 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3140,11 +3140,15 @@ class Sema { /// method) or an Objective-C property attribute, rather than as an /// underscored type specifier. /// + /// \param overrideExisting Whether to override an existing, locally-specified + /// nullability specifier rather than complaining about the conflict. + /// /// \returns true if nullability cannot be applied, false otherwise. bool checkNullabilityTypeSpecifier(QualType &type, NullabilityKind nullability, SourceLocation nullabilityLoc, bool isContextSensitive, - bool implicit); + bool implicit, + bool overrideExisting = false); /// \brief Stmt attributes - this routine is the top level dispatcher. StmtResult ProcessStmtAttributes(Stmt *Stmt, AttributeList *Attrs, diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index dda721e411293..964cc1bc6525a 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -1451,9 +1451,14 @@ APINotesReader::VersionedInfo::VersionedInfo( // Look for an exact version match. Optional unversioned; Selected = Results.size(); + SelectedRole = VersionedInfoRole::Versioned; + for (unsigned i = 0, n = Results.size(); i != n; ++i) { if (Results[i].first == version) { Selected = i; + + if (version) SelectedRole = VersionedInfoRole::ReplaceSource; + else SelectedRole = VersionedInfoRole::AugmentSource; break; } @@ -1465,9 +1470,11 @@ APINotesReader::VersionedInfo::VersionedInfo( // If we didn't find a match but we have an unversioned result, use the // unversioned result. - if (Selected == Results.size() && unversioned) + if (Selected == Results.size() && unversioned) { Selected = *unversioned; -} + SelectedRole = VersionedInfoRole::AugmentSource; + } + } auto APINotesReader::lookupObjCClassID(StringRef name) -> Optional { if (!Impl.ObjCContextIDTable) diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 8f1e5a29d4ebd..868544f6afd00 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -929,7 +929,6 @@ namespace { // Convert the versioned information. for (const auto &versioned : TheModule.SwiftVersions) { convertTopLevelItems(versioned.Items, versioned.Version); - } if (!ErrorOccured) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 4a666ccbd9421..4d4ea6eee8d50 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -15,6 +15,7 @@ #include "clang/AST/DeclObjC.h" #include "clang/APINotes/APINotesReader.h" using namespace clang; +using clang::api_notes::VersionedInfoRole; /// Determine whether this is a multi-level pointer type. static bool isMultiLevelPointerType(QualType type) { @@ -27,7 +28,23 @@ static bool isMultiLevelPointerType(QualType type) { } // Apply nullability to the given declaration. -static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability) { +static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability, + VersionedInfoRole role) { + bool overrideExisting; + switch (role) { + case VersionedInfoRole::AugmentSource: + overrideExisting = false; + break; + + case VersionedInfoRole::ReplaceSource: + overrideExisting = true; + break; + + case VersionedInfoRole::Versioned: + // FIXME: Record versioned info? + return; + } + QualType type; // Nullability for a function/method appertains to the retain type. @@ -47,7 +64,8 @@ static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability) { QualType origType = type; S.checkNullabilityTypeSpecifier(type, nullability, decl->getLocation(), /*isContextSensitive=*/false, - /*implicit=*/true); + /*implicit=*/true, + overrideExisting); if (type.getTypePtr() == origType.getTypePtr()) return; @@ -100,104 +118,196 @@ static StringRef CopyString(ASTContext &ctx, StringRef string) { return StringRef(static_cast(mem), string.size()); } +namespace { + /// Handle an attribute introduced by API notes. + /// + /// \param shouldAddAttribute Whether we should add a new attribute + /// (otherwise, we might remove an existing attribute). + /// \param createAttr Create the new attribute to be added. + /// \param getExistingAttr Get an existing, matching attribute on the given + /// declaration. + template + void handleAPINotedAttribute( + Sema &S, Decl *D, bool shouldAddAttribute, + VersionedInfoRole role, + llvm::function_ref createAttr, + llvm::function_ref(Decl *)> getExistingAttr = + [](Decl *decl) { return decl->specific_attr_begin(); }) { + switch (role) { + case VersionedInfoRole::AugmentSource: + // If we're not adding an attribute, there's nothing to do. + if (!shouldAddAttribute) return; + + // If the attribute is already present, we're done. + if (getExistingAttr(D) != D->specific_attr_end()) return; + + // Add the attribute. + if (auto attr = createAttr()) + D->addAttr(attr); + break; + + case VersionedInfoRole::ReplaceSource: { + auto end = D->specific_attr_end(); + auto existing = getExistingAttr(D); + if (existing != end) { + // Remove the existing attribute. + D->getAttrs().erase(existing.getCurrent()); + } + + // If we're supposed to add a new attribute, do so. + if (shouldAddAttribute) { + if (auto attr = createAttr()) { + D->addAttr(attr); + } + } + break; + } + + case VersionedInfoRole::Versioned: + // FIXME: Retain versioned attributes separately. + break; + } + } +} + static void ProcessAPINotes(Sema &S, Decl *D, - const api_notes::CommonEntityInfo &Info) { + const api_notes::CommonEntityInfo &info, + VersionedInfoRole role) { // Availability - if (Info.Unavailable && !D->hasAttr()) { - D->addAttr(UnavailableAttr::CreateImplicit(S.Context, - CopyString(S.Context, - Info.UnavailableMsg))); + if (info.Unavailable) { + handleAPINotedAttribute(S, D, true, role, + [&] { + return UnavailableAttr::CreateImplicit(S.Context, + CopyString(S.Context, + info.UnavailableMsg)); + }); } - if (Info.UnavailableInSwift) { - D->addAttr(AvailabilityAttr::CreateImplicit( - S.Context, - &S.Context.Idents.get("swift"), - VersionTuple(), - VersionTuple(), - VersionTuple(), - /*Unavailable=*/true, - CopyString(S.Context, Info.UnavailableMsg), - /*Strict=*/false, - /*Replacement=*/StringRef())); + if (info.UnavailableInSwift) { + handleAPINotedAttribute(S, D, true, role, [&] { + return AvailabilityAttr::CreateImplicit( + S.Context, + &S.Context.Idents.get("swift"), + VersionTuple(), + VersionTuple(), + VersionTuple(), + /*Unavailable=*/true, + CopyString(S.Context, info.UnavailableMsg), + /*Strict=*/false, + /*Replacement=*/StringRef()); + }, + [](Decl *decl) { + auto existing = decl->specific_attr_begin(), + end = decl->specific_attr_end(); + while (existing != end) { + if (auto platform = (*existing)->getPlatform()) { + if (platform->isStr("swift")) + break; + } + + ++existing; + } + + return existing; + }); } // swift_private - if (Info.SwiftPrivate && !D->hasAttr()) { - D->addAttr(SwiftPrivateAttr::CreateImplicit(S.Context)); + if (info.SwiftPrivate) { + handleAPINotedAttribute(S, D, true, role, [&] { + return SwiftPrivateAttr::CreateImplicit(S.Context); + }); } // swift_name - if (!Info.SwiftName.empty() && !D->hasAttr()) { - auto &APINoteName = S.getASTContext().Idents.get("SwiftName API Note"); - - if (!S.DiagnoseSwiftName(D, Info.SwiftName, D->getLocation(), - &APINoteName)) { - return; - } - D->addAttr(SwiftNameAttr::CreateImplicit(S.Context, - CopyString(S.Context, Info.SwiftName))); + if (!info.SwiftName.empty()) { + handleAPINotedAttribute(S, D, true, role, + [&]() -> SwiftNameAttr * { + auto &APINoteName = S.getASTContext().Idents.get("SwiftName API Note"); + + if (!S.DiagnoseSwiftName(D, info.SwiftName, D->getLocation(), + &APINoteName)) { + return nullptr; + } + + return SwiftNameAttr::CreateImplicit(S.Context, + CopyString(S.Context, + info.SwiftName)); + }); } } static void ProcessAPINotes(Sema &S, Decl *D, - const api_notes::CommonTypeInfo &Info) { + const api_notes::CommonTypeInfo &info, + VersionedInfoRole role) { // swift_bridge - if (!Info.getSwiftBridge().empty() && - !D->getAttr()) { - D->addAttr( - SwiftBridgeAttr::CreateImplicit(S.Context, - CopyString(S.Context, - Info.getSwiftBridge()))); + if (!info.getSwiftBridge().empty()) { + handleAPINotedAttribute(S, D, true, role, [&] { + return SwiftBridgeAttr::CreateImplicit(S.Context, + CopyString(S.Context, + info.getSwiftBridge())); + }); } // ns_error_domain - if (!Info.getNSErrorDomain().empty() && - !D->getAttr()) { - D->addAttr( - NSErrorDomainAttr::CreateImplicit( - S.Context, - &S.Context.Idents.get(Info.getNSErrorDomain()))); + if (!info.getNSErrorDomain().empty()) { + handleAPINotedAttribute(S, D, true, role, [&] { + return NSErrorDomainAttr::CreateImplicit( + S.Context, + &S.Context.Idents.get(info.getNSErrorDomain())); + }); } - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } /// Process API notes for a variable or property. static void ProcessAPINotes(Sema &S, Decl *D, - const api_notes::VariableInfo &Info) { + const api_notes::VariableInfo &info, + VersionedInfoRole role) { // Nullability. - if (auto Nullability = Info.getNullability()) { - applyNullability(S, D, *Nullability); + if (auto Nullability = info.getNullability()) { + applyNullability(S, D, *Nullability, role); } // Handle common entity information. - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } /// Process API notes for a parameter. static void ProcessAPINotes(Sema &S, ParmVarDecl *D, - const api_notes::ParamInfo &Info) { + const api_notes::ParamInfo &info, + VersionedInfoRole role) { // noescape - if (Info.isNoEscape() && !D->getAttr()) - D->addAttr(NoEscapeAttr::CreateImplicit(S.Context)); + if (info.isNoEscape()) { + handleAPINotedAttribute(S, D, true, role, [&] { + return NoEscapeAttr::CreateImplicit(S.Context); + }); + } // Handle common entity information. - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } /// Process API notes for a global variable. static void ProcessAPINotes(Sema &S, VarDecl *D, - const api_notes::GlobalVariableInfo &Info) { + const api_notes::GlobalVariableInfo &info, + VersionedInfoRole role) { // Handle common entity information. - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } /// Process API notes for an Objective-C property. static void ProcessAPINotes(Sema &S, ObjCPropertyDecl *D, - const api_notes::ObjCPropertyInfo &Info) { + const api_notes::ObjCPropertyInfo &info, + VersionedInfoRole role) { // Handle common entity information. - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } namespace { @@ -206,7 +316,8 @@ namespace { /// Process API notes for a function or method. static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, - const api_notes::FunctionInfo &Info) { + const api_notes::FunctionInfo &info, + VersionedInfoRole role) { // Find the declaration itself. FunctionDecl *FD = AnyFunc.dyn_cast(); Decl *D = FD; @@ -217,8 +328,8 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, } // Nullability of return type. - if (Info.NullabilityAudited) { - applyNullability(S, D, Info.getReturnTypeInfo()); + if (info.NullabilityAudited) { + applyNullability(S, D, info.getReturnTypeInfo(), role); } // Parameters. @@ -236,48 +347,57 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, Param = MD->param_begin()[I]; // Nullability. - if (Info.NullabilityAudited) - applyNullability(S, Param, Info.getParamTypeInfo(I)); + if (info.NullabilityAudited) + applyNullability(S, Param, info.getParamTypeInfo(I), role); - if (I < Info.Params.size()) { - ProcessAPINotes(S, Param, Info.Params[I]); + if (I < info.Params.size()) { + ProcessAPINotes(S, Param, info.Params[I], role); } } // Handle common entity information. - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } /// Process API notes for a global function. static void ProcessAPINotes(Sema &S, FunctionDecl *D, - const api_notes::GlobalFunctionInfo &Info) { + const api_notes::GlobalFunctionInfo &info, + VersionedInfoRole role) { // Handle common function information. ProcessAPINotes(S, FunctionOrMethod(D), - static_cast(Info)); + static_cast(info), role); } /// Process API notes for an enumerator. static void ProcessAPINotes(Sema &S, EnumConstantDecl *D, - const api_notes::EnumConstantInfo &Info) { + const api_notes::EnumConstantInfo &info, + VersionedInfoRole role) { // Handle common information. ProcessAPINotes(S, D, - static_cast(Info)); + static_cast(info), + role); } /// Process API notes for an Objective-C method. static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, - const api_notes::ObjCMethodInfo &Info) { + const api_notes::ObjCMethodInfo &info, + VersionedInfoRole role) { // Designated initializers. - if (Info.DesignatedInit && !D->getAttr()) { - if (ObjCInterfaceDecl *IFace = D->getClassInterface()) { - D->addAttr(ObjCDesignatedInitializerAttr::CreateImplicit(S.Context)); - IFace->setHasDesignatedInitializers(); - } + if (info.DesignatedInit) { + handleAPINotedAttribute(S, D, true, role, [&] { + if (ObjCInterfaceDecl *IFace = D->getClassInterface()) { + IFace->setHasDesignatedInitializers(); + } + return ObjCDesignatedInitializerAttr::CreateImplicit(S.Context); + }); } - if (Info.getFactoryAsInitKind() + // FIXME: This doesn't work well with versioned API notes. + if (role == VersionedInfoRole::AugmentSource && + info.getFactoryAsInitKind() == api_notes::FactoryAsInitKind::AsClassMethod && !D->getAttr()) { D->addAttr(SwiftSuppressFactoryAsInitAttr::CreateImplicit(S.Context)); @@ -285,36 +405,43 @@ static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, // Handle common function information. ProcessAPINotes(S, FunctionOrMethod(D), - static_cast(Info)); + static_cast(info), role); } /// Process API notes for a tag. static void ProcessAPINotes(Sema &S, TagDecl *D, - const api_notes::TagInfo &Info) { + const api_notes::TagInfo &info, + VersionedInfoRole role) { // Handle common type information. - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } /// Process API notes for a typedef. static void ProcessAPINotes(Sema &S, TypedefNameDecl *D, - const api_notes::TypedefInfo &Info) { + const api_notes::TypedefInfo &info, + VersionedInfoRole role) { // Handle common type information. - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } /// Process API notes for an Objective-C class or protocol. static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, - const api_notes::ObjCContextInfo &Info) { + const api_notes::ObjCContextInfo &info, + VersionedInfoRole role) { // Handle common type information. - ProcessAPINotes(S, D, static_cast(Info)); + ProcessAPINotes(S, D, static_cast(info), + role); } /// Process API notes for an Objective-C class. static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, - const api_notes::ObjCContextInfo &Info) { + const api_notes::ObjCContextInfo &info, + VersionedInfoRole role) { // Handle information common to Objective-C classes and protocols. - ProcessAPINotes(S, static_cast(D), Info); + ProcessAPINotes(S, static_cast(D), info, role); } /// Process API notes that are associated with this declaration, mapping them @@ -329,7 +456,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto VD = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupGlobalVariable(VD->getName())) { - ::ProcessAPINotes(*this, VD, *Info); + ::ProcessAPINotes(*this, VD, *Info, Info.getSelectedRole()); } } @@ -341,7 +468,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (FD->getDeclName().isIdentifier()) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupGlobalFunction(FD->getName())) { - ::ProcessAPINotes(*this, FD, *Info); + ::ProcessAPINotes(*this, FD, *Info, Info.getSelectedRole()); } } } @@ -353,7 +480,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto Class = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupObjCClassInfo(Class->getName())) { - ::ProcessAPINotes(*this, Class, *Info); + ::ProcessAPINotes(*this, Class, *Info, Info.getSelectedRole()); } } @@ -364,7 +491,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto Protocol = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupObjCProtocolInfo(Protocol->getName())) { - ::ProcessAPINotes(*this, Protocol, *Info); + ::ProcessAPINotes(*this, Protocol, *Info, Info.getSelectedRole()); } } @@ -375,7 +502,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto Tag = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupTag(Tag->getName())) { - ::ProcessAPINotes(*this, Tag, *Info); + ::ProcessAPINotes(*this, Tag, *Info, Info.getSelectedRole()); } } @@ -386,7 +513,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto Typedef = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupTypedef(Typedef->getName())) { - ::ProcessAPINotes(*this, Typedef, *Info); + ::ProcessAPINotes(*this, Typedef, *Info, Info.getSelectedRole()); } } @@ -401,7 +528,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto EnumConstant = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Info = Reader->lookupEnumConstant(EnumConstant->getName())) { - ::ProcessAPINotes(*this, EnumConstant, *Info); + ::ProcessAPINotes(*this, EnumConstant, *Info, Info.getSelectedRole()); } } @@ -472,7 +599,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto Info = Reader->lookupObjCMethod(*Context, SelectorRef, Method->isInstanceMethod())){ - ::ProcessAPINotes(*this, Method, *Info); + ::ProcessAPINotes(*this, Method, *Info, Info.getSelectedRole()); } } } @@ -488,7 +615,7 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto Info = Reader->lookupObjCProperty(*Context, Property->getName(), isInstanceProperty)) { - ::ProcessAPINotes(*this, Property, *Info); + ::ProcessAPINotes(*this, Property, *Info, Info.getSelectedRole()); } } } diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 620018e94c958..d966f8efeac56 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -5823,11 +5823,31 @@ static bool handleMSPointerTypeQualifierAttr(TypeProcessingState &State, return false; } +/// Rebuild an attributed type without the nullability attribute on it. +static QualType rebuildAttributedTypeWithoutNullability(ASTContext &ctx, + QualType type) { + auto attributed = dyn_cast(type.getTypePtr()); + if (!attributed) return type; + + // Skip the nullability attribute; we're done. + if (attributed->getImmediateNullability()) { + return attributed->getModifiedType(); + } + + // Build the modified type. + auto modified = rebuildAttributedTypeWithoutNullability( + ctx, attributed->getModifiedType()); + assert(modified.getTypePtr() != attributed->getModifiedType().getTypePtr()); + return ctx.getAttributedType(attributed->getAttrKind(), modified, + attributed->getEquivalentType()); +} + bool Sema::checkNullabilityTypeSpecifier(QualType &type, NullabilityKind nullability, SourceLocation nullabilityLoc, bool isContextSensitive, - bool implicit) { + bool implicit, + bool overrideExisting) { if (!implicit) { // We saw a nullability type specifier. If this is the first one for // this file, note that. @@ -5864,11 +5884,16 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &type, break; } - // Conflicting nullability. - Diag(nullabilityLoc, diag::err_nullability_conflicting) - << DiagNullabilityKind(nullability, isContextSensitive) - << DiagNullabilityKind(*existingNullability, false); - return true; + if (!overrideExisting) { + // Conflicting nullability. + Diag(nullabilityLoc, diag::err_nullability_conflicting) + << DiagNullabilityKind(nullability, isContextSensitive) + << DiagNullabilityKind(*existingNullability, false); + return true; + } + + // Rebuild the attributed type, dropping the existing nullability. + type = rebuildAttributedTypeWithoutNullability(Context, type); } desugared = attributed->getModifiedType(); diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes index 9c855c6dc6ac8..aa43d849be1d0 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes @@ -40,3 +40,10 @@ SwiftVersions: MethodKind: Instance NullabilityOfRet: O Nullability: [ O, S ] + Properties: + - Name: explicitNonnullInstance + PropertyKind: Instance + Nullability: O + - Name: explicitNullableInstance + PropertyKind: Instance + Nullability: N diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes index 9c855c6dc6ac8..aa43d849be1d0 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes @@ -40,3 +40,10 @@ SwiftVersions: MethodKind: Instance NullabilityOfRet: O Nullability: [ O, S ] + Properties: + - Name: explicitNonnullInstance + PropertyKind: Instance + Nullability: O + - Name: explicitNullableInstance + PropertyKind: Instance + Nullability: N diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h index eb25cc0c7fcc8..60d9afa30509a 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h @@ -35,4 +35,6 @@ __attribute__((objc_root_class)) @property (class, nonatomic, readwrite, retain) A *nonnullABoth; @end +#import + #endif diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes new file mode 100644 index 0000000000000..c4e2bc0b6490f --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -0,0 +1,6 @@ +Name: SomeKit +SwiftVersions: + - Version: 3.0 + Functions: + - Name: moveToPoint + SwiftName: 'moveTo(a:b:)' diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h new file mode 100644 index 0000000000000..8b0c2901cbba4 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -0,0 +1 @@ +void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Modules/module.modulemap b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Modules/module.modulemap new file mode 100644 index 0000000000000..6d957fd68009f --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module VersionedKit { + umbrella header "VersionedKit.h" + export * + module * { export * } +} diff --git a/clang/test/APINotes/nullability.m b/clang/test/APINotes/nullability.m index c11cea5bd520d..f70c3634b5997 100644 --- a/clang/test/APINotes/nullability.m +++ b/clang/test/APINotes/nullability.m @@ -3,7 +3,7 @@ // Test with Swift version 3.0. This should only affect the few APIs that have an entry in the 3.0 tables. -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3.0 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify -DSWIFT_VERSION_3_0 +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3.0 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify -DSWIFT_VERSION_3_0 -fmodules-ignore-macro=SWIFT_VERSION_3_0 #import @@ -29,6 +29,16 @@ int main() { [a setInternalProperty: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} +#if SWIFT_VERSION_3_0 + // Version 3 information overrides header information. + [a setExplicitNonnullInstance: 0]; // okay + [a setExplicitNullableInstance: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} +#else + // Header information overrides unversioned information. + [a setExplicitNonnullInstance: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + [a setExplicitNullableInstance: 0]; // okay +#endif + return 0; } diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m new file mode 100644 index 0000000000000..c92ccd10e1878 --- /dev/null +++ b/clang/test/APINotes/versioned.m @@ -0,0 +1,15 @@ +// RUN: rm -rf %t && mkdir -p %t + +// Build and check the unversioned module file. +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Unversioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -ast-print %t/ModulesCache/Unversioned/VersionedKit.pcm | FileCheck -check-prefix=CHECK-UNVERSIONED %s + +// Build and check the versioned module file. +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3.0 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -ast-print %t/ModulesCache/Versioned/VersionedKit.pcm | FileCheck -check-prefix=CHECK-VERSIONED %s + +#import + +// CHECK-UNVERSIONED: void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); +// CHECK-VERSIONED: void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(a:b:)"))); + From 679b3f5565685a7122d7b1d015041bd83dbfe3bf Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 4 Oct 2016 11:30:37 -0700 Subject: [PATCH 098/585] [API Notes] Allow removal of explicit attributes via versioned API notes. Teach API notes to distinguish between unspecified API notes and false/empty API notes. When a versioned API note explicitly specifies an empty/false state, *remove* an the corresponding attribute from the AST even when it was explicitly specified in the source. This allows headers to move forward while API notes provide backward compatibility. Another part of rdar://problem/28455809. apple-llvm-split-commit: 1fbea6c310b39c87e1f78e56430569a9dcd75488 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 67 +++++++++++++++---- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 22 +++--- clang/lib/APINotes/APINotesWriter.cpp | 27 ++++++-- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 32 ++++++--- clang/lib/AST/DeclPrinter.cpp | 3 + clang/lib/Sema/SemaAPINotes.cpp | 18 ++--- .../Headers/VersionedKit.apinotes | 12 ++++ .../Headers/VersionedKit.h | 14 ++++ clang/test/APINotes/Inputs/roundtrip.apinotes | 8 --- clang/test/APINotes/versioned.m | 20 +++++- 11 files changed, 168 insertions(+), 57 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 9761510ead2b6..4620bfd496e72 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -127,26 +127,48 @@ class CommonTypeInfo : public CommonEntityInfo { /// The Swift type to which a given type is bridged. /// /// Reflects the swift_bridge attribute. - std::string SwiftBridge; + Optional SwiftBridge; /// The NS error domain for this type. - std::string NSErrorDomain; + Optional NSErrorDomain; public: CommonTypeInfo() : CommonEntityInfo() { } - const std::string &getSwiftBridge() const { return SwiftBridge; } - void setSwiftBridge(const std::string &swiftType) { SwiftBridge = swiftType; } + const Optional &getSwiftBridge() const { return SwiftBridge; } - const std::string &getNSErrorDomain() const { return NSErrorDomain; } - void setNSErrorDomain(const std::string &domain) { NSErrorDomain = domain; } + void setSwiftBridge(const Optional &swiftType) { + SwiftBridge = swiftType; + } + + void setSwiftBridge(const Optional &swiftType) { + if (swiftType) + SwiftBridge = *swiftType; + else + SwiftBridge = None; + } + + const Optional &getNSErrorDomain() const { + return NSErrorDomain; + } + + void setNSErrorDomain(const Optional &domain) { + NSErrorDomain = domain; + } + + void setNSErrorDomain(const Optional &domain) { + if (domain) + NSErrorDomain = *domain; + else + NSErrorDomain = None; + } friend CommonTypeInfo &operator|=(CommonTypeInfo &lhs, const CommonTypeInfo &rhs) { static_cast(lhs) |= rhs; - if (lhs.SwiftBridge.empty() && !rhs.SwiftBridge.empty()) + if (!lhs.SwiftBridge && rhs.SwiftBridge) lhs.SwiftBridge = rhs.SwiftBridge; - if (lhs.NSErrorDomain.empty() && !rhs.NSErrorDomain.empty()) + if (!lhs.NSErrorDomain && rhs.NSErrorDomain) lhs.NSErrorDomain = rhs.NSErrorDomain; return lhs; } @@ -308,24 +330,41 @@ class ObjCPropertyInfo : public VariableInfo { /// Describes a function or method parameter. class ParamInfo : public VariableInfo { + /// Whether noescape was specified. + unsigned NoEscapeSpecified : 1; + /// Whether the this parameter has the 'noescape' attribute. unsigned NoEscape : 1; public: - ParamInfo() : VariableInfo(), NoEscape(false) { } - - bool isNoEscape() const { return NoEscape; } - void setNoEscape(bool noescape) { NoEscape = noescape; } + ParamInfo() : VariableInfo(), NoEscapeSpecified(false), NoEscape(false) { } + + Optional isNoEscape() const { + if (!NoEscapeSpecified) return None; + return NoEscape; + } + void setNoEscape(Optional noescape) { + if (noescape) { + NoEscapeSpecified = true; + NoEscape = *noescape; + } else { + NoEscapeSpecified = false; + NoEscape = false; + } + } friend ParamInfo &operator|=(ParamInfo &lhs, const ParamInfo &rhs) { static_cast(lhs) |= rhs; - if (!lhs.NoEscape && rhs.NoEscape) - lhs.NoEscape = true; + if (!lhs.NoEscapeSpecified && rhs.NoEscapeSpecified) { + lhs.NoEscapeSpecified = true; + lhs.NoEscape = rhs.NoEscape; + } return lhs; } friend bool operator==(const ParamInfo &lhs, const ParamInfo &rhs) { return static_cast(lhs) == rhs && + lhs.NoEscapeSpecified == rhs.NoEscapeSpecified && lhs.NoEscape == rhs.NoEscape; } diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 9b68cf292386d..6f7734bb7154c 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 16; // versioned API notes. +const uint16_t VERSION_MINOR = 17; // optional NSErrorDomain using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 964cc1bc6525a..0e254094c9d5e 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -126,15 +126,19 @@ namespace { unsigned swiftBridgeLength = endian::readNext(data); - info.setSwiftBridge( - StringRef(reinterpret_cast(data), swiftBridgeLength)); - data += swiftBridgeLength; + if (swiftBridgeLength > 0) { + info.setSwiftBridge( + std::string(reinterpret_cast(data), swiftBridgeLength-1)); + data += swiftBridgeLength-1; + } unsigned errorDomainLength = endian::readNext(data); - info.setNSErrorDomain( - StringRef(reinterpret_cast(data), errorDomainLength)); - data += errorDomainLength; + if (errorDomainLength > 0) { + info.setNSErrorDomain( + std::string(reinterpret_cast(data), errorDomainLength-1)); + data += errorDomainLength-1; + } } /// Used to deserialize the on-disk identifier table. @@ -303,8 +307,10 @@ namespace { if (payload & 0x01) pi.setNullabilityAudited(static_cast(nullabilityValue)); payload >>= 1; - pi.setNoEscape(payload & 0x01); - payload >>= 1; assert(payload == 0 && "Bad API notes"); + if (payload & 0x01) { + pi.setNoEscape(payload & 0x02); + } + payload >>= 2; assert(payload == 0 && "Bad API notes"); info.Params.push_back(pi); --numParams; diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 6debe997bf001..2cd577613cc20 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -343,8 +343,8 @@ namespace { // Retrieve the serialized size of the given CommonTypeInfo, for use // in on-disk hash tables. static unsigned getCommonTypeInfoSize(const CommonTypeInfo &info) { - return 2 + info.getSwiftBridge().size() + - 2 + info.getNSErrorDomain().size() + + return 2 + (info.getSwiftBridge() ? info.getSwiftBridge()->size() : 0) + + 2 + (info.getNSErrorDomain() ? info.getNSErrorDomain()->size() : 0) + getCommonEntityInfoSize(info); } @@ -352,10 +352,18 @@ namespace { static void emitCommonTypeInfo(raw_ostream &out, const CommonTypeInfo &info) { emitCommonEntityInfo(out, info); endian::Writer writer(out); - writer.write(info.getSwiftBridge().size()); - out.write(info.getSwiftBridge().c_str(), info.getSwiftBridge().size()); - writer.write(info.getNSErrorDomain().size()); - out.write(info.getNSErrorDomain().c_str(), info.getNSErrorDomain().size()); + if (auto swiftBridge = info.getSwiftBridge()) { + writer.write(swiftBridge->size() + 1); + out.write(swiftBridge->c_str(), swiftBridge->size()); + } else { + writer.write(0); + } + if (auto nsErrorDomain = info.getNSErrorDomain()) { + writer.write(nsErrorDomain->size() + 1); + out.write(nsErrorDomain->c_str(), info.getNSErrorDomain()->size()); + } else { + writer.write(0); + } } /// Used to serialize the on-disk Objective-C context table. @@ -687,7 +695,12 @@ namespace { // Parameters. writer.write(info.Params.size()); for (const auto &pi : info.Params) { - uint8_t payload = pi.isNoEscape(); + uint8_t payload = 0; + if (auto noescape = pi.isNoEscape()) { + payload |= 0x01; + if (*noescape) + payload |= 0x02; + } auto nullability = pi.getNullability(); payload = (payload << 1) | nullability.hasValue(); diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 868544f6afd00..69ed5425ded89 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -171,7 +171,7 @@ namespace { struct Param { unsigned Position; - bool NoEscape = false; + Optional NoEscape = false; llvm::Optional Nullability; }; typedef std::vector ParamsSeq; @@ -208,8 +208,8 @@ namespace { AvailabilityItem Availability; bool SwiftPrivate = false; StringRef SwiftName; - StringRef SwiftBridge; - StringRef NSErrorDomain; + Optional SwiftBridge; + Optional NSErrorDomain; MethodsSeq Methods; PropertiesSeq Properties; }; @@ -248,8 +248,8 @@ namespace { AvailabilityItem Availability; StringRef SwiftName; bool SwiftPrivate = false; - StringRef SwiftBridge; - StringRef NSErrorDomain; + Optional SwiftBridge; + Optional NSErrorDomain; }; typedef std::vector TagsSeq; @@ -258,8 +258,8 @@ namespace { AvailabilityItem Availability; StringRef SwiftName; bool SwiftPrivate = false; - StringRef SwiftBridge; - StringRef NSErrorDomain; + Optional SwiftBridge; + Optional NSErrorDomain; }; typedef std::vector TypedefsSeq; @@ -1033,6 +1033,20 @@ namespace { return StringRef(reinterpret_cast(ptr), string.size()); } + /// Copy an optional string into allocated memory so it does disappear on us. + Optional maybeCopyString(Optional string) { + if (!string) return None; + + return copyString(*string); + } + + /// Copy an optional string into allocated memory so it does disappear on us. + Optional maybeCopyString(Optional string) { + if (!string) return None; + + return copyString(*string); + } + template void handleCommon(T &record, const CommonEntityInfo &info) { handleAvailability(record.Availability, info); @@ -1043,8 +1057,8 @@ namespace { template void handleCommonType(T &record, const CommonTypeInfo &info) { handleCommon(record, info); - record.SwiftBridge = copyString(info.getSwiftBridge()); - record.NSErrorDomain = copyString(info.getNSErrorDomain()); + record.SwiftBridge = maybeCopyString(info.getSwiftBridge()); + record.NSErrorDomain = maybeCopyString(info.getNSErrorDomain()); } /// Map Objective-C context info. diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 6d47dd7b88376..e599840a05919 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -1137,6 +1137,9 @@ void DeclPrinter::VisitObjCInterfaceDecl(ObjCInterfaceDecl *OID) { return; } bool eolnOut = false; + prettyPrintAttributes(OID); + if (OID->hasAttrs()) Out << "\n"; + Out << "@interface " << I; if (auto TypeParams = OID->getTypeParamListAsWritten()) { diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 4d4ea6eee8d50..89cf5b613981e 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -241,20 +241,22 @@ static void ProcessAPINotes(Sema &S, Decl *D, const api_notes::CommonTypeInfo &info, VersionedInfoRole role) { // swift_bridge - if (!info.getSwiftBridge().empty()) { - handleAPINotedAttribute(S, D, true, role, [&] { + if (auto swiftBridge = info.getSwiftBridge()) { + handleAPINotedAttribute(S, D, !swiftBridge->empty(), role, + [&] { return SwiftBridgeAttr::CreateImplicit(S.Context, CopyString(S.Context, - info.getSwiftBridge())); + *swiftBridge)); }); } // ns_error_domain - if (!info.getNSErrorDomain().empty()) { - handleAPINotedAttribute(S, D, true, role, [&] { + if (auto nsErrorDomain = info.getNSErrorDomain()) { + handleAPINotedAttribute(S, D, !nsErrorDomain->empty(), + role, [&] { return NSErrorDomainAttr::CreateImplicit( S.Context, - &S.Context.Idents.get(info.getNSErrorDomain())); + &S.Context.Idents.get(*nsErrorDomain)); }); } @@ -281,8 +283,8 @@ static void ProcessAPINotes(Sema &S, ParmVarDecl *D, const api_notes::ParamInfo &info, VersionedInfoRole role) { // noescape - if (info.isNoEscape()) { - handleAPINotedAttribute(S, D, true, role, [&] { + if (auto noescape = info.isNoEscape()) { + handleAPINotedAttribute(S, D, *noescape, role, [&] { return NoEscapeAttr::CreateImplicit(S.Context); }); } diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index c4e2bc0b6490f..1fc6aa4de620d 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -1,6 +1,18 @@ Name: SomeKit SwiftVersions: - Version: 3.0 + Classes: + - Name: MyReferenceType + SwiftBridge: '' Functions: - Name: moveToPoint SwiftName: 'moveTo(a:b:)' + - Name: acceptClosure + Parameters: + - Position: 0 + NoEscape: false + Tags: + - Name: MyErrorCode + NSErrorDomain: '' + + \ No newline at end of file diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h index 8b0c2901cbba4..741bdaaba0cdf 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -1 +1,15 @@ void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); + +void acceptClosure(void (^ __attribute__((noescape)) block)(void)); + +@class NSString; + +extern NSString *MyErrorDomain; + +enum __attribute__((ns_error_domain(MyErrorDomain))) MyErrorCode { + MyErrorCodeFailed = 1 +}; + +__attribute__((swift_bridge("MyValueType"))) +@interface MyReferenceType +@end diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 13e08a1126b0b..7e2e68a667cf3 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -9,8 +9,6 @@ Classes: AvailabilityMsg: '' SwiftPrivate: false SwiftName: '' - SwiftBridge: '' - NSErrorDomain: '' Methods: - Selector: 'cellWithImage:' MethodKind: Class @@ -62,7 +60,6 @@ Classes: SwiftPrivate: false SwiftName: '' SwiftBridge: View - NSErrorDomain: '' Methods: - Selector: 'addSubview:' MethodKind: Instance @@ -78,7 +75,6 @@ Classes: - Position: 0 NoEscape: false - Position: 1 - NoEscape: false - Position: 2 NoEscape: true Nullability: [ N, N, O ] @@ -136,14 +132,12 @@ Tags: AvailabilityMsg: '' SwiftPrivate: false SwiftName: SomeEnum - SwiftBridge: '' NSErrorDomain: some_error_domain - Name: NSSomeStruct Availability: available AvailabilityMsg: '' SwiftPrivate: false SwiftName: SomeStruct - SwiftBridge: '' NSErrorDomain: '' Typedefs: - Name: NSTypedef @@ -152,7 +146,6 @@ Typedefs: SwiftPrivate: false SwiftName: Typedef SwiftBridge: '' - NSErrorDomain: '' SwiftVersions: - Version: 3.0 Classes: @@ -162,7 +155,6 @@ SwiftVersions: SwiftPrivate: false SwiftName: NSBox SwiftBridge: '' - NSErrorDomain: '' Methods: - Selector: init MethodKind: Instance diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index c92ccd10e1878..53af3339163e7 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -1,11 +1,11 @@ // RUN: rm -rf %t && mkdir -p %t // Build and check the unversioned module file. -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Unversioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Unversioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: %clang_cc1 -ast-print %t/ModulesCache/Unversioned/VersionedKit.pcm | FileCheck -check-prefix=CHECK-UNVERSIONED %s // Build and check the versioned module file. -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3.0 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3.0 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: %clang_cc1 -ast-print %t/ModulesCache/Versioned/VersionedKit.pcm | FileCheck -check-prefix=CHECK-VERSIONED %s #import @@ -13,3 +13,19 @@ // CHECK-UNVERSIONED: void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); // CHECK-VERSIONED: void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(a:b:)"))); +// CHECK-UNVERSIONED: void acceptClosure(void (^block)(void) __attribute__((noescape))); +// CHECK-VERSIONED: void acceptClosure(void (^block)(void)); + +// CHECK-UNVERSIONED: enum MyErrorCode { +// CHECK-UNVERSIONED-NEXT: MyErrorCodeFailed = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((ns_error_domain(MyErrorDomain))); + +// CHECK-UNVERSIONED: __attribute__((swift_bridge("MyValueType"))) +// CHECK-UNVERSIONED: @interface MyReferenceType + +// CHECK-VERSIONED: enum MyErrorCode { +// CHECK-VERSIONED-NEXT: MyErrorCodeFailed = 1 +// CHECK-VERSIONED-NEXT: }; + +// CHECK-VERSIONED-NOT: __attribute__((swift_bridge("MyValueType"))) +// CHECK-VERSIONED: @interface MyReferenceType From 31441f4eb94323e664c3efa98506f0172cfac083 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 4 Oct 2016 14:18:27 -0700 Subject: [PATCH 099/585] [Driver] Propagate -fapinotes-swift-version=XX.YY to -cc1. apple-llvm-split-commit: c24e0dabac8400ab013b96b4ce6fc66840c41824 apple-llvm-split-dir: clang/ --- clang/lib/Driver/Tools.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/lib/Driver/Tools.cpp b/clang/lib/Driver/Tools.cpp index d1b203b7db0f5..34414debca24e 100644 --- a/clang/lib/Driver/Tools.cpp +++ b/clang/lib/Driver/Tools.cpp @@ -5435,6 +5435,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, const char Arg[] = "-fapinotes-cache-path="; APINotesCachePath.insert(APINotesCachePath.begin(), Arg, Arg + strlen(Arg)); CmdArgs.push_back(Args.MakeArgString(APINotesCachePath)); + + Args.AddLastArg(CmdArgs, options::OPT_fapinotes_swift_version); } // -fblocks=0 is default. From 7a9a24ecd8ed7c05fb31ab9ea042bce11664e2fc Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 4 Oct 2016 16:25:05 -0700 Subject: [PATCH 100/585] [API Notes] Suppress an unused-variable warning when assertions are disabled. Fixes rdar://problem/28589320. apple-llvm-split-commit: 70cfd267b9ca0057fe0dee1309ba6c6f01309920 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 0e254094c9d5e..2b7ec59b87c73 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -88,7 +88,7 @@ namespace { result.reserve(numElements); for (unsigned i = 0; i != numElements; ++i) { auto version = readVersionTuple(data); - auto dataBefore = data; (void)data; + auto dataBefore = data; (void)dataBefore; auto unversionedData = Derived::readUnversioned(key, data); assert(data != dataBefore && "Unversioned data reader didn't move pointer"); From 3ee59bc91b8a118e8d20d9b880a5d78e95bfc56e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 4 Oct 2016 16:49:39 -0700 Subject: [PATCH 101/585] [API Notes] Allow removal of SwiftPrivate via versioned API notes. More of rdar://problem/28455809. apple-llvm-split-commit: 9b0bccba91f27449ea2e7d60f5d47c63bcd8d551 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 31 +++++++++++++++++-- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 3 +- clang/lib/APINotes/APINotesWriter.cpp | 15 +++++++-- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 28 ++++++++--------- clang/lib/Sema/SemaAPINotes.cpp | 4 +-- .../Headers/VersionedKit.apinotes | 2 ++ .../Headers/VersionedKit.h | 2 ++ clang/test/APINotes/Inputs/roundtrip.apinotes | 3 -- clang/test/APINotes/versioned.m | 4 +++ 10 files changed, 67 insertions(+), 27 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 4620bfd496e72..79ec22ce8b684 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -70,19 +70,42 @@ class CommonEntityInfo { /// Whether this entity is marked unavailable in Swift. unsigned UnavailableInSwift : 1; +private: + /// Whether SwiftPrivate was specified. + unsigned SwiftPrivateSpecified : 1; + /// Whether this entity is considered "private" to a Swift overlay. unsigned SwiftPrivate : 1; +public: /// Swift name of this entity. std::string SwiftName; - CommonEntityInfo() : Unavailable(0), UnavailableInSwift(0), SwiftPrivate(0) { } + CommonEntityInfo() + : Unavailable(0), UnavailableInSwift(0), SwiftPrivateSpecified(0), + SwiftPrivate(0) { } + + Optional isSwiftPrivate() const { + if (!SwiftPrivateSpecified) return None; + return SwiftPrivate; + } + + void setSwiftPrivate(Optional swiftPrivate) { + if (swiftPrivate) { + SwiftPrivateSpecified = 1; + SwiftPrivate = *swiftPrivate; + } else { + SwiftPrivateSpecified = 0; + SwiftPrivate = 0; + } + } friend bool operator==(const CommonEntityInfo &lhs, const CommonEntityInfo &rhs) { return lhs.UnavailableMsg == rhs.UnavailableMsg && lhs.Unavailable == rhs.Unavailable && lhs.UnavailableInSwift == rhs.UnavailableInSwift && + lhs.SwiftPrivateSpecified == rhs.SwiftPrivateSpecified && lhs.SwiftPrivate == rhs.SwiftPrivate && lhs.SwiftName == rhs.SwiftName; } @@ -111,8 +134,10 @@ class CommonEntityInfo { } } - if (rhs.SwiftPrivate) - lhs.SwiftPrivate = true; + if (rhs.SwiftPrivateSpecified && !lhs.SwiftPrivateSpecified) { + lhs.SwiftPrivateSpecified = 1; + lhs.SwiftPrivate = rhs.SwiftPrivate; + } if (rhs.SwiftName.length() != 0 && lhs.SwiftName.length() == 0) diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 6f7734bb7154c..4e763efa8a198 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 17; // optional NSErrorDomain +const uint16_t VERSION_MINOR = 18; // three-state SwiftPrivate using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 2b7ec59b87c73..9a87a445c55d4 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -104,7 +104,8 @@ namespace { uint8_t unavailableBits = *data++; info.Unavailable = (unavailableBits >> 1) & 0x01; info.UnavailableInSwift = unavailableBits & 0x01; - info.SwiftPrivate = (unavailableBits >> 2) & 0x01; + if ((unavailableBits >> 2) & 0x01) + info.setSwiftPrivate(static_cast((unavailableBits >> 3) & 0x01)); unsigned msgLength = endian::readNext(data); info.UnavailableMsg diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 2cd577613cc20..cbf76c1af3c10 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -331,9 +331,18 @@ namespace { static void emitCommonEntityInfo(raw_ostream &out, const CommonEntityInfo &info) { endian::Writer writer(out); - writer.write(info.SwiftPrivate << 2 - | info.Unavailable << 1 - | info.UnavailableInSwift); + uint8_t payload = 0; + if (auto swiftPrivate = info.isSwiftPrivate()) { + payload |= 0x01; + if (*swiftPrivate) payload |= 0x02; + } + payload <<= 1; + payload |= info.Unavailable; + payload <<= 1; + payload |= info.UnavailableInSwift; + + writer.write(payload); + writer.write(info.UnavailableMsg.size()); out.write(info.UnavailableMsg.c_str(), info.UnavailableMsg.size()); writer.write(info.SwiftName.size()); diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 69ed5425ded89..000ffb58721ec 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -183,7 +183,7 @@ namespace { NullabilitySeq Nullability; llvm::Optional NullabilityOfRet; AvailabilityItem Availability; - bool SwiftPrivate = false; + Optional SwiftPrivate; StringRef SwiftName; api_notes::FactoryAsInitKind FactoryAsInit = api_notes::FactoryAsInitKind::Infer; @@ -197,7 +197,7 @@ namespace { llvm::Optional Kind; llvm::Optional Nullability; AvailabilityItem Availability; - bool SwiftPrivate = false; + Optional SwiftPrivate; StringRef SwiftName; }; typedef std::vector PropertiesSeq; @@ -206,7 +206,7 @@ namespace { StringRef Name; bool AuditedForNullability = false; AvailabilityItem Availability; - bool SwiftPrivate = false; + Optional SwiftPrivate; StringRef SwiftName; Optional SwiftBridge; Optional NSErrorDomain; @@ -221,7 +221,7 @@ namespace { NullabilitySeq Nullability; llvm::Optional NullabilityOfRet; AvailabilityItem Availability; - bool SwiftPrivate = false; + Optional SwiftPrivate; StringRef SwiftName; }; typedef std::vector FunctionsSeq; @@ -230,7 +230,7 @@ namespace { StringRef Name; llvm::Optional Nullability; AvailabilityItem Availability; - bool SwiftPrivate = false; + Optional SwiftPrivate; StringRef SwiftName; }; typedef std::vector GlobalVariablesSeq; @@ -238,7 +238,7 @@ namespace { struct EnumConstant { StringRef Name; AvailabilityItem Availability; - bool SwiftPrivate = false; + Optional SwiftPrivate; StringRef SwiftName; }; typedef std::vector EnumConstantsSeq; @@ -247,7 +247,7 @@ namespace { StringRef Name; AvailabilityItem Availability; StringRef SwiftName; - bool SwiftPrivate = false; + Optional SwiftPrivate; Optional SwiftBridge; Optional NSErrorDomain; }; @@ -257,7 +257,7 @@ namespace { StringRef Name; AvailabilityItem Availability; StringRef SwiftName; - bool SwiftPrivate = false; + Optional SwiftPrivate; Optional SwiftBridge; Optional NSErrorDomain; }; @@ -652,7 +652,7 @@ namespace { return true; convertAvailability(common.Availability, info, apiName); - info.SwiftPrivate = common.SwiftPrivate; + info.setSwiftPrivate(common.SwiftPrivate); info.SwiftName = common.SwiftName; return false; } @@ -769,7 +769,7 @@ namespace { if (!isAvailable(prop.Availability)) continue; convertAvailability(prop.Availability, pInfo, prop.Name); - pInfo.SwiftPrivate = prop.SwiftPrivate; + pInfo.setSwiftPrivate(prop.SwiftPrivate); pInfo.SwiftName = prop.SwiftName; if (prop.Nullability) pInfo.setNullabilityAudited(*prop.Nullability); @@ -825,7 +825,7 @@ namespace { if (!isAvailable(global.Availability)) continue; convertAvailability(global.Availability, info, global.Name); - info.SwiftPrivate = global.SwiftPrivate; + info.setSwiftPrivate(global.SwiftPrivate); info.SwiftName = global.SwiftName; if (global.Nullability) info.setNullabilityAudited(*global.Nullability); @@ -846,7 +846,7 @@ namespace { if (!isAvailable(function.Availability)) continue; convertAvailability(function.Availability, info, function.Name); - info.SwiftPrivate = function.SwiftPrivate; + info.setSwiftPrivate(function.SwiftPrivate); info.SwiftName = function.SwiftName; convertParams(function.Params, info); convertNullability(function.Nullability, @@ -870,7 +870,7 @@ namespace { if (!isAvailable(enumConstant.Availability)) continue; convertAvailability(enumConstant.Availability, info, enumConstant.Name); - info.SwiftPrivate = enumConstant.SwiftPrivate; + info.setSwiftPrivate(enumConstant.SwiftPrivate); info.SwiftName = enumConstant.SwiftName; Writer->addEnumConstant(enumConstant.Name, info, swiftVersion); } @@ -1050,7 +1050,7 @@ namespace { template void handleCommon(T &record, const CommonEntityInfo &info) { handleAvailability(record.Availability, info); - record.SwiftPrivate = info.SwiftPrivate; + record.SwiftPrivate = info.isSwiftPrivate(); record.SwiftName = copyString(info.SwiftName); } diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 89cf5b613981e..0bcaaac327102 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -213,8 +213,8 @@ static void ProcessAPINotes(Sema &S, Decl *D, } // swift_private - if (info.SwiftPrivate) { - handleAPINotedAttribute(S, D, true, role, [&] { + if (auto swiftPrivate = info.isSwiftPrivate()) { + handleAPINotedAttribute(S, D, *swiftPrivate, role, [&] { return SwiftPrivateAttr::CreateImplicit(S.Context); }); } diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index 1fc6aa4de620d..e840ed48f0e21 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -11,6 +11,8 @@ SwiftVersions: Parameters: - Position: 0 NoEscape: false + - Name: privateFunc + SwiftPrivate: false Tags: - Name: MyErrorCode NSErrorDomain: '' diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h index 741bdaaba0cdf..d90cb8f26a07a 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -13,3 +13,5 @@ enum __attribute__((ns_error_domain(MyErrorDomain))) MyErrorCode { __attribute__((swift_bridge("MyValueType"))) @interface MyReferenceType @end + +void privateFunc(void) __attribute__((swift_private)); diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 7e2e68a667cf3..a6f368e09c207 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -81,7 +81,6 @@ Classes: NullabilityOfRet: N Availability: available AvailabilityMsg: '' - SwiftPrivate: false SwiftName: '' - Selector: 'beginDraggingSessionWithItems:event:source:' MethodKind: Instance @@ -97,7 +96,6 @@ Classes: Nullability: O Availability: available AvailabilityMsg: '' - SwiftPrivate: false SwiftName: enclosing - Name: makeBackingLayer PropertyKind: Class @@ -111,7 +109,6 @@ Functions: NullabilityOfRet: N Availability: available AvailabilityMsg: '' - SwiftPrivate: false SwiftName: 'availableWindowDepths()' Globals: - Name: NSCalibratedWhiteColorSpace diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index 53af3339163e7..bfeacc36102ae 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -23,9 +23,13 @@ // CHECK-UNVERSIONED: __attribute__((swift_bridge("MyValueType"))) // CHECK-UNVERSIONED: @interface MyReferenceType +// CHECK-UNVERSIONED: void privateFunc() __attribute__((swift_private)); + // CHECK-VERSIONED: enum MyErrorCode { // CHECK-VERSIONED-NEXT: MyErrorCodeFailed = 1 // CHECK-VERSIONED-NEXT: }; // CHECK-VERSIONED-NOT: __attribute__((swift_bridge("MyValueType"))) // CHECK-VERSIONED: @interface MyReferenceType + +// CHECK-VERSIONED: void privateFunc(); From b930314f24e7cc5af8c4f8b5ceaa746575d8e53d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 4 Oct 2016 17:24:26 -0700 Subject: [PATCH 102/585] Fix a bad merge; swift_newtype doesn't imply xray_always_instrument apple-llvm-split-commit: faf50eefe1099c61d10a05b2d7c748ebf7edb775 apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaDeclAttr.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index b779b51f8bbbb..f062999de0bdd 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6546,6 +6546,7 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, break; case AttributeList::AT_SwiftNewtype: handleSwiftNewtypeAttr(S, D, Attr); + break; // XRay attributes. case AttributeList::AT_XRayInstrument: handleSimpleAttribute(S, D, Attr); From a29637f0515df3234b91714f2918d8a826c4a083 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 4 Oct 2016 17:25:12 -0700 Subject: [PATCH 103/585] [API Notes] Add support for SwiftWrapper via API notes apple-llvm-split-commit: 62a7f71c267753a19e051740643ffffa22b41ad5 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 9 +++++++ clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 6 +++++ clang/lib/APINotes/APINotesWriter.cpp | 23 +++++++++++++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 15 ++++++++++- clang/lib/Sema/SemaAPINotes.cpp | 27 +++++++++++++++++++ .../Headers/VersionedKit.apinotes | 4 +++ .../Headers/VersionedKit.h | 2 ++ clang/test/APINotes/Inputs/roundtrip.apinotes | 1 + clang/test/APINotes/versioned.m | 4 +++ 10 files changed, 90 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 79ec22ce8b684..fae55017e6d0b 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -585,9 +585,18 @@ class TagInfo : public CommonTypeInfo { TagInfo() : CommonTypeInfo() { } }; +/// The kind of a swift_wrapper/swift_newtype. +enum class SwiftWrapperKind { + None, + Struct, + Enum +}; + /// Describes API notes data for a typedef. class TypedefInfo : public CommonTypeInfo { public: + Optional SwiftWrapper; + TypedefInfo() : CommonTypeInfo() { } }; diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 4e763efa8a198..4623773a1f0d4 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 18; // three-state SwiftPrivate +const uint16_t VERSION_MINOR = 19; // SwiftWrapper using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 9a87a445c55d4..9dee73cb43fb3 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -479,6 +479,12 @@ namespace { static TypedefInfo readUnversioned(internal_key_type key, const uint8_t *&data) { TypedefInfo info; + + uint8_t payload = *data++; + if (payload > 0) { + info.SwiftWrapper = static_cast((payload & 0x3) - 1); + } + readCommonTypeInfo(data, info); return info; } diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index cbf76c1af3c10..c0527aa483a5e 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -1026,6 +1026,7 @@ namespace { /// Used to serialize the on-disk tag table. class TagTableInfo : public CommonTypeTableInfo { }; + } // end anonymous namespace void APINotesWriter::Implementation::writeTagBlock( @@ -1055,7 +1056,27 @@ void APINotesWriter::Implementation::writeTagBlock( namespace { /// Used to serialize the on-disk typedef table. class TypedefTableInfo - : public CommonTypeTableInfo { }; + : public CommonTypeTableInfo { + + public: + unsigned getUnversionedInfoSize(const TypedefInfo &info) { + return 1 + getCommonTypeInfoSize(info); + } + + void emitUnversionedInfo(raw_ostream &out, const TypedefInfo &info) { + endian::Writer writer(out); + + uint8_t payload = 0; + if (auto swiftWrapper = info.SwiftWrapper) { + payload |= static_cast(*swiftWrapper) + 1; + } + + writer.write(payload); + + emitCommonTypeInfo(out, info); + } + + }; } // end anonymous namespace void APINotesWriter::Implementation::writeTypedefBlock( diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 000ffb58721ec..d24a63fafef1b 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -260,6 +260,7 @@ namespace { Optional SwiftPrivate; Optional SwiftBridge; Optional NSErrorDomain; + Optional SwiftWrapper; }; typedef std::vector TypedefsSeq; @@ -349,6 +350,15 @@ namespace llvm { } }; + template<> + struct ScalarEnumerationTraits { + static void enumeration(IO &io, api_notes::SwiftWrapperKind &value) { + io.enumCase(value, "none", api_notes::SwiftWrapperKind::None); + io.enumCase(value, "struct", api_notes::SwiftWrapperKind::Struct); + io.enumCase(value, "enum", api_notes::SwiftWrapperKind::Enum); + } + }; + template <> struct ScalarTraits { static void output(const VersionTuple &value, void*, @@ -489,6 +499,7 @@ namespace llvm { io.mapOptional("SwiftName", t.SwiftName); io.mapOptional("SwiftBridge", t.SwiftBridge); io.mapOptional("NSErrorDomain", t.NSErrorDomain); + io.mapOptional("SwiftWrapper", t.SwiftWrapper); } }; @@ -903,7 +914,8 @@ namespace { TypedefInfo typedefInfo; if (convertCommonType(t, typedefInfo, t.Name)) continue; - + typedefInfo.SwiftWrapper = t.SwiftWrapper; + Writer->addTypedef(t.Name, typedefInfo, swiftVersion); } } @@ -1246,6 +1258,7 @@ namespace { Typedef td; td.Name = name; handleCommonType(td, info); + td.SwiftWrapper = info.SwiftWrapper; auto &items = getTopLevelItems(swiftVersion); items.Typedefs.push_back(td); } diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 0bcaaac327102..121a7309a8334 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -423,6 +423,33 @@ static void ProcessAPINotes(Sema &S, TagDecl *D, static void ProcessAPINotes(Sema &S, TypedefNameDecl *D, const api_notes::TypedefInfo &info, VersionedInfoRole role) { + // swift_wrapper + using SwiftWrapperKind = api_notes::SwiftWrapperKind; + + if (auto swiftWrapper = info.SwiftWrapper) { + handleAPINotedAttribute(S, D, + *swiftWrapper != SwiftWrapperKind::None, role, + [&] { + SwiftNewtypeAttr::NewtypeKind kind; + switch (*swiftWrapper) { + case SwiftWrapperKind::None: + llvm_unreachable("Shouldn't build an attribute"); + + case SwiftWrapperKind::Struct: + kind = SwiftNewtypeAttr::NK_Struct; + break; + + case SwiftWrapperKind::Enum: + kind = SwiftNewtypeAttr::NK_Enum; + break; + } + return SwiftNewtypeAttr::CreateImplicit( + S.Context, + SwiftNewtypeAttr::GNU_swift_wrapper, + kind); + }); + } + // Handle common type information. ProcessAPINotes(S, D, static_cast(info), role); diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index e840ed48f0e21..4215fb9d15318 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -16,5 +16,9 @@ SwiftVersions: Tags: - Name: MyErrorCode NSErrorDomain: '' + Typedefs: + - Name: MyDoubleWrapper + SwiftWrapper: none + \ No newline at end of file diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h index d90cb8f26a07a..db71d8f576f6d 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -15,3 +15,5 @@ __attribute__((swift_bridge("MyValueType"))) @end void privateFunc(void) __attribute__((swift_private)); + +typedef double MyDoubleWrapper __attribute__((swift_wrapper(struct))); diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index a6f368e09c207..c6beaf1928f26 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -143,6 +143,7 @@ Typedefs: SwiftPrivate: false SwiftName: Typedef SwiftBridge: '' + SwiftWrapper: struct SwiftVersions: - Version: 3.0 Classes: diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index bfeacc36102ae..d761620b80eb8 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -25,6 +25,8 @@ // CHECK-UNVERSIONED: void privateFunc() __attribute__((swift_private)); +// CHECK-UNVERSIONED: typedef double MyDoubleWrapper __attribute__((swift_wrapper("struct"))); + // CHECK-VERSIONED: enum MyErrorCode { // CHECK-VERSIONED-NEXT: MyErrorCodeFailed = 1 // CHECK-VERSIONED-NEXT: }; @@ -33,3 +35,5 @@ // CHECK-VERSIONED: @interface MyReferenceType // CHECK-VERSIONED: void privateFunc(); + +// CHECK-VERSIONED: typedef double MyDoubleWrapper; From 78bf8a182725e7b771222eb08861e11d13c8edfa Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 5 Oct 2016 15:42:43 -0700 Subject: [PATCH 104/585] [API Notes] Remove comment for nonexistent parameter 'swiftVersion'. NFC apple-llvm-split-commit: a1912f0994dbb3e3f65921149b2aedf964bbbc09 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index f96c7b65c9154..2b985c6ce73b5 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -175,7 +175,6 @@ class APINotesReader { /// \param name The name of the property we're looking for. /// \param isInstance Whether we are looking for an instance property (vs. /// a class property). - /// \param swiftVersion The Swift version to filter for, if any. /// /// \returns Information about the property, if known. VersionedInfo lookupObjCProperty(ContextID contextID, From bea24bcfdabf3aa8244388ae49599a677b36108d Mon Sep 17 00:00:00 2001 From: Tim Northover Date: Thu, 13 Oct 2016 16:06:29 -0700 Subject: [PATCH 105/585] Update internal tool after API change. apple-llvm-split-commit: c841da503fc183567261caa12b05b18e309decac apple-llvm-split-dir: clang/ --- clang/tools/driver/apinotes_main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/tools/driver/apinotes_main.cpp b/clang/tools/driver/apinotes_main.cpp index e4ccc2e7f3244..930dc39c3b726 100644 --- a/clang/tools/driver/apinotes_main.cpp +++ b/clang/tools/driver/apinotes_main.cpp @@ -44,8 +44,7 @@ int cc1apinotes_main(ArrayRef Argv, const char *Argv0, "Convert binary format to YAML"), clEnumValN(api_notes::ActionType::Dump, "dump", - "Parse and dump the output"), - clEnumValEnd), + "Parse and dump the output")), cl::cat(APINotesCategory)); static cl::opt From 9c2e10a6a77558f617779547f1ca0461118c0a49 Mon Sep 17 00:00:00 2001 From: Manman Ren Date: Fri, 16 Sep 2016 17:15:06 -0700 Subject: [PATCH 106/585] Revert "ObjectiveC Generics: Start using ObjCTypeParamType." This reverts commit c7c63c5b9fd8211c606407cb8b6eef67321f16a3. The series of patches broke swift bot. apple-llvm-split-commit: 1859c18983ee8b0615ad4587b0d52c1440b11ed6 apple-llvm-split-dir: clang/ --- clang/lib/AST/ASTContext.cpp | 5 --- clang/lib/AST/DeclObjC.cpp | 8 ++--- clang/lib/AST/Type.cpp | 19 +++-------- clang/lib/Sema/SemaDeclObjC.cpp | 2 +- clang/lib/Sema/SemaType.cpp | 22 +------------ .../Checkers/DynamicTypePropagation.cpp | 2 +- clang/test/SemaObjC/kindof.m | 10 +----- .../SemaObjC/parameterized_classes_subst.m | 33 ------------------- 8 files changed, 10 insertions(+), 91 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 318523eed91df..9488ac436ac92 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -3975,11 +3975,6 @@ ASTContext::applyObjCProtocolQualifiers(QualType type, bool allowOnPointerType) const { hasError = false; - if (const ObjCTypeParamType *objT = - dyn_cast(type.getTypePtr())) { - return getObjCTypeParamType(objT->getDecl(), protocols); - } - // Apply protocol qualifiers to ObjCObjectPointerType. if (allowOnPointerType) { if (const ObjCObjectPointerType *objPtr = diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp index fdbac00fc9426..d1c77bb0b1b3e 100644 --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -1324,12 +1324,8 @@ ObjCTypeParamDecl *ObjCTypeParamDecl::Create(ASTContext &ctx, DeclContext *dc, IdentifierInfo *name, SourceLocation colonLoc, TypeSourceInfo *boundInfo) { - auto *TPDecl = - new (ctx, dc) ObjCTypeParamDecl(ctx, dc, variance, varianceLoc, index, - nameLoc, name, colonLoc, boundInfo); - QualType TPType = ctx.getObjCTypeParamType(TPDecl, {}); - TPDecl->setTypeForDecl(TPType.getTypePtr()); - return TPDecl; + return new (ctx, dc) ObjCTypeParamDecl(ctx, dc, variance, varianceLoc, index, + nameLoc, name, colonLoc, boundInfo); } ObjCTypeParamDecl *ObjCTypeParamDecl::CreateDeserialized(ASTContext &ctx, diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 3e776e3043850..0a034f6ed4739 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -1081,24 +1081,13 @@ QualType QualType::substObjCTypeArgs( // Replace an Objective-C type parameter reference with the corresponding // type argument. - if (const auto *OTPTy = dyn_cast(splitType.Ty)) { - if (auto *typeParam = dyn_cast(OTPTy->getDecl())) { + if (const auto *typedefTy = dyn_cast(splitType.Ty)) { + if (auto *typeParam = dyn_cast(typedefTy->getDecl())) { // If we have type arguments, use them. if (!typeArgs.empty()) { + // FIXME: Introduce SubstObjCTypeParamType ? QualType argType = typeArgs[typeParam->getIndex()]; - if (OTPTy->qual_empty()) - return ctx.getQualifiedType(argType, splitType.Quals); - - // Apply protocol lists if exists. - bool hasError; - SmallVector protocolsVec; - protocolsVec.append(OTPTy->qual_begin(), - OTPTy->qual_end()); - ArrayRef protocolsToApply = protocolsVec; - QualType resultTy = ctx.applyObjCProtocolQualifiers(argType, - protocolsToApply, hasError, true/*allowOnPointerType*/); - - return ctx.getQualifiedType(resultTy, splitType.Quals); + return ctx.getQualifiedType(argType, splitType.Quals); } switch (context) { diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index cfa34377d288c..527d8ac9fa498 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -2358,7 +2358,7 @@ static bool CheckMethodOverrideParam(Sema &S, } if (S.Context.hasSameUnqualifiedType(ImplTy, IfaceTy)) return true; - + if (!Warn) return false; unsigned DiagID = diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index f8ed64ec28aaf..1b74942de9708 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -1157,20 +1157,6 @@ TypeResult Sema::actOnObjCTypeArgsAndProtocolQualifiers( ResultTL = ObjCObjectPointerTL.getPointeeLoc(); } - if (auto OTPTL = ResultTL.getAs()) { - // Protocol qualifier information. - if (OTPTL.getNumProtocols() > 0) { - assert(OTPTL.getNumProtocols() == Protocols.size()); - OTPTL.setProtocolLAngleLoc(ProtocolLAngleLoc); - OTPTL.setProtocolRAngleLoc(ProtocolRAngleLoc); - for (unsigned i = 0, n = Protocols.size(); i != n; ++i) - OTPTL.setProtocolLoc(i, ProtocolLocs[i]); - } - - // We're done. Return the completed type to the parser. - return CreateParsedType(Result, ResultTInfo); - } - auto ObjCObjectTL = ResultTL.castAs(); // Type argument information. @@ -5926,6 +5912,7 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &type, // For the context-sensitive keywords/Objective-C property // attributes, require that the type be a single-level pointer. if (isContextSensitive) { + // Make sure that the pointee isn't itself a pointer type. QualType pointeeType = desugared->getPointeeType(); if (pointeeType->isAnyPointerType() || pointeeType->isObjCObjectPointerType() || @@ -5949,13 +5936,6 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &type, } bool Sema::checkObjCKindOfType(QualType &type, SourceLocation loc) { - if (isa(type)) { - // Build the attributed type to record where __kindof occurred. - type = Context.getAttributedType(AttributedType::attr_objc_kindof, - type, type); - return false; - } - // Find out if it's an Objective-C object or object pointer type; const ObjCObjectPointerType *ptrType = type->getAs(); const ObjCObjectType *objType = ptrType ? ptrType->getObjectType() diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index ec2153cc8266d..a8f2f3a4a5005 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -626,7 +626,7 @@ static bool isObjCTypeParamDependent(QualType Type) { : public RecursiveASTVisitor { public: IsObjCTypeParamDependentTypeVisitor() : Result(false) {} - bool VisitObjCTypeParamType(const ObjCTypeParamType *Type) { + bool VisitTypedefType(const TypedefType *Type) { if (isa(Type->getDecl())) { Result = true; return false; diff --git a/clang/test/SemaObjC/kindof.m b/clang/test/SemaObjC/kindof.m index 9d758d3cfb2a6..63ba18fe89bc0 100644 --- a/clang/test/SemaObjC/kindof.m +++ b/clang/test/SemaObjC/kindof.m @@ -385,7 +385,7 @@ - (void)test:(id)T { @end @interface NSGeneric : NSObject -- (void)test:(__kindof ObjectType)T; // expected-note{{passing argument to parameter 'T' here}} +- (void)test:(__kindof ObjectType)T; - (void)mapUsingBlock:(id (^)(__kindof ObjectType))block; @end @implementation NSGeneric @@ -395,14 +395,6 @@ - (void)mapUsingBlock:(id (^)(id))block { } @end -void testGeneric(NSGeneric *generic) { - NSObject *NSObject_obj; - // Assign from NSObject_obj to __kindof NSString*. - [generic test:NSObject_obj]; // expected-warning{{incompatible pointer types sending 'NSObject *' to parameter of type '__kindof NSString *'}} - NSString *NSString_str; - [generic test:NSString_str]; -} - // Check that clang doesn't crash when a type parameter is illegal. @interface Array1 : NSObject @end diff --git a/clang/test/SemaObjC/parameterized_classes_subst.m b/clang/test/SemaObjC/parameterized_classes_subst.m index da2d56f11bc80..f90ee9093592c 100644 --- a/clang/test/SemaObjC/parameterized_classes_subst.m +++ b/clang/test/SemaObjC/parameterized_classes_subst.m @@ -426,36 +426,3 @@ + (void)useSuperMethod { // warning about likely protocol/class name typos. // -------------------------------------------------------------------------- typedef NSArray ArrayOfNSObjectWarning; // expected-warning{{parameterized class 'NSArray' already conforms to the protocols listed; did you forget a '*'?}} - -// rdar://25060179 -@interface MyMutableDictionary : NSObject -- (void)setObject:(ObjectType)obj forKeyedSubscript:(KeyType )key; // expected-note{{passing argument to parameter 'obj' here}} \ - // expected-note{{passing argument to parameter 'key' here}} -@end - -void bar(MyMutableDictionary *stringsByString, - NSNumber *n1, NSNumber *n2) { - // We warn here when the key types do not match. - stringsByString[n1] = n2; // expected-warning{{incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'}} \ - // expected-warning{{incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'}} -} - -@interface MyTest : NSObject -- (V)test:(K)key; -- (V)test2:(K)key; // expected-note{{previous definition is here}} -- (void)mapUsingBlock:(id (^)(V))block; -- (void)mapUsingBlock2:(id (^)(V))block; // expected-note{{previous definition is here}} -@end - -@implementation MyTest -- (id)test:(id)key { - return key; -} -- (int)test2:(id)key{ // expected-warning{{conflicting return type in implementation}} - return 0; -} -- (void)mapUsingBlock:(id (^)(id))block { -} -- (void)mapUsingBlock2:(id)block { // expected-warning{{conflicting parameter types in implementation}} -} -@end From 411ba51ab0505880d81b86ccd4417486dc65a765 Mon Sep 17 00:00:00 2001 From: Brian Gesiak Date: Sun, 23 Oct 2016 22:24:50 -0400 Subject: [PATCH 107/585] [AST] Remove superfluous docblock change Upstream Clang documents the `Message` parameter as simply `\param`, while Swift's fork of Clang documents it as `\param[out]`. Although `\param[out]` may be more correct, the change to the documentation should be made in upstream Clang, not Swift Clang. Furthermore, upstream Clang does not appear to have a strong convention as to the use of `\param[out]`: of 4616 instances of `\param`, only 49 are denoted as `\param[out]`. apple-llvm-split-commit: 75035e4a3a2a80296b0a868e1e6150a860a336fb apple-llvm-split-dir: clang/ --- clang/include/clang/AST/DeclBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h index 7685241996091..3a80ff5b234e9 100644 --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -605,7 +605,7 @@ class LLVM_ALIGNAS(/*alignof(uint64_t)*/ 8) Decl { /// the given declaration (e.g., preferring 'unavailable' to /// 'deprecated'). /// - /// \param[out] Message If non-NULL and the result is not \c + /// \param Message If non-NULL and the result is not \c /// AR_Available, will be set to a (possibly empty) message /// describing why the declaration has not been introduced, is /// deprecated, or is unavailable. From 5165a199b97de802308cb0ca4863866fb6f81280 Mon Sep 17 00:00:00 2001 From: Mehdi Amini Date: Mon, 24 Oct 2016 12:12:36 -0700 Subject: [PATCH 108/585] Fix build after incorrect merge conflict fix apple-llvm-split-commit: d636f484881c4fc740f7c0232691af475e2e2e37 apple-llvm-split-dir: clang/ --- clang/lib/Driver/Tools.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/Driver/Tools.cpp b/clang/lib/Driver/Tools.cpp index 86f7473cc2c1e..def5bd04c8e62 100644 --- a/clang/lib/Driver/Tools.cpp +++ b/clang/lib/Driver/Tools.cpp @@ -5615,8 +5615,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back( Args.MakeArgString("-fbuild-session-timestamp=" + Twine((uint64_t)Status.getLastModificationTime() - .time_since_epoch() - .count()))); + .toEpochTime()))); } if (Args.getLastArg(options::OPT_fmodules_validate_once_per_build_session)) { From b5e9ed4b9c7abc3201b427a773575b5391608f0b Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 24 Oct 2016 14:12:51 +0100 Subject: [PATCH 109/585] [Sema] Display an objc_subclassing_restricted error for Objective-C implementation declarations when appropriate. rdar://28753587 apple-llvm-split-commit: eb81c6fb87dd0fd5661209e2cf2aef5d77ca56b1 apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaDeclObjC.cpp | 12 ++++++++++++ clang/test/SemaObjC/subclassing-restricted-attr.m | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 527d8ac9fa498..7ec3202d7529d 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -3859,6 +3859,18 @@ Decl *Sema::ActOnAtEnd(Scope *S, SourceRange AtEnd, ArrayRef allMethods, Diag(IDecl->getLocation(), diag::err_objc_root_class_subclass); } + if (const ObjCInterfaceDecl *Super = IDecl->getSuperClass()) { + // An interface can subclass another interface with a + // objc_subclassing_restricted attribute when it has that attribute as + // well (because of interfaces imported from Swift). Therefore we have + // to check if we can subclass in the implementation as well. + if (IDecl->hasAttr() && + Super->hasAttr()) { + Diag(IC->getLocation(), diag::err_restricted_superclass_mismatch); + Diag(Super->getLocation(), diag::note_class_declared); + } + } + if (LangOpts.ObjCRuntime.isNonFragile()) { while (IDecl->getSuperClass()) { DiagnoseDuplicateIvars(IDecl, IDecl->getSuperClass()); diff --git a/clang/test/SemaObjC/subclassing-restricted-attr.m b/clang/test/SemaObjC/subclassing-restricted-attr.m index 2e77df8763aab..5edd342acfc99 100644 --- a/clang/test/SemaObjC/subclassing-restricted-attr.m +++ b/clang/test/SemaObjC/subclassing-restricted-attr.m @@ -21,3 +21,16 @@ @interface PlainRoot __attribute__((objc_subclassing_restricted)) @interface Sub2Class : PlainRoot // okay @end + +// rdar://28753587 +__attribute__((objc_subclassing_restricted)) +@interface SuperImplClass // expected-note {{class is declared here}} +@end +@implementation SuperImplClass +@end + +__attribute__((objc_subclassing_restricted)) +@interface SubImplClass : SuperImplClass +@end +@implementation SubImplClass // expected-error {{cannot subclass a class with objc_subclassing_restricted attribute}} +@end From 114ba7ff213988ae9454678e21e68e400cc7ab3d Mon Sep 17 00:00:00 2001 From: Mehdi Amini Date: Tue, 25 Oct 2016 14:11:14 -0700 Subject: [PATCH 110/585] Revert "Fix build after incorrect merge conflict fix" This reverts commit d636f484881c4fc740f7c0232691af475e2e2e37. It seems that my LLVM was out-of-sync with clang, probably because the automerger hadn't pushback the LLVM part. apple-llvm-split-commit: 3a3f8258a8f422a3315b86449dbf03ff193ce6aa apple-llvm-split-dir: clang/ --- clang/lib/Driver/Tools.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Driver/Tools.cpp b/clang/lib/Driver/Tools.cpp index def5bd04c8e62..86f7473cc2c1e 100644 --- a/clang/lib/Driver/Tools.cpp +++ b/clang/lib/Driver/Tools.cpp @@ -5615,7 +5615,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back( Args.MakeArgString("-fbuild-session-timestamp=" + Twine((uint64_t)Status.getLastModificationTime() - .toEpochTime()))); + .time_since_epoch() + .count()))); } if (Args.getLastArg(options::OPT_fmodules_validate_once_per_build_session)) { From bd651478911c0711118fb1adad94380ea497d7c9 Mon Sep 17 00:00:00 2001 From: Brian Gesiak Date: Sat, 29 Oct 2016 11:50:09 -0400 Subject: [PATCH 111/585] Remove whitespace changes Remove trailing whitespace that was added in swift-clang, which cause unnecessary divergence from Clang trunk. This reduces the number of files that differ from trunk, from 144 files down to 143 files. apple-llvm-split-commit: beff10e95f9db587d0c48aa5e45054aba3d8d5d0 apple-llvm-split-dir: clang/ --- clang/lib/CodeGen/CGBuiltin.cpp | 2 +- clang/lib/Sema/SemaDeclObjC.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index a22e1c7174556..c73a6e1b061ed 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2250,7 +2250,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD, return RValue::get(llvm::ConstantExpr::getBitCast(GV, CGM.Int8PtrTy)); break; } - + case Builtin::BI__builtin_coro_size: { auto & Context = getContext(); auto SizeTy = Context.getSizeType(); diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 757b19b5d5da8..59882ccc46e47 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -2358,7 +2358,7 @@ static bool CheckMethodOverrideParam(Sema &S, } if (S.Context.hasSameUnqualifiedType(ImplTy, IfaceTy)) return true; - + if (!Warn) return false; unsigned DiagID = From 15ec27b9be465a3559f0f9abf4c38fc445cbb9af Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 10 Nov 2016 13:38:38 -0800 Subject: [PATCH 112/585] [APINotes] Update for upstream changes to llvm::BitstreamReader. (#37) rdar://problem/29078044 apple-llvm-split-commit: d39a991b0e768818ba5460c43a2c7d2c60ce138f apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesManager.cpp | 17 +++++++++++++++++ clang/lib/APINotes/APINotesReader.cpp | 21 ++++++++------------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index 5d50d3cd1dd4e..ebbd73e4e4aa0 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -26,6 +26,7 @@ #include "llvm/ADT/Statistic.h" #include "llvm/ADT/SetVector.h" #include "llvm/Support/Path.h" +#include "llvm/Support/PrettyStackTrace.h" #include using namespace clang; @@ -51,6 +52,20 @@ STATISTIC(NumBinaryCacheMisses, STATISTIC(NumBinaryCacheRebuilds, "binary form cache rebuilds"); +namespace { + /// Prints two successive strings, which much be kept alive as long as the + /// PrettyStackTrace entry. + class PrettyStackTraceDoubleString : public llvm::PrettyStackTraceEntry { + StringRef First, Second; + public: + PrettyStackTraceDoubleString(StringRef first, StringRef second) + : First(first), Second(second) {} + void print(raw_ostream &OS) const override { + OS << First << Second; + } + }; +} + APINotesManager::APINotesManager(SourceManager &sourceMgr, const LangOptions &langOpts) : SourceMgr(sourceMgr), ImplicitAPINotes(langOpts.APINotes), @@ -140,6 +155,8 @@ static void pruneAPINotesCache(StringRef APINotesCachePath) { std::unique_ptr APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { FileManager &fileMgr = SourceMgr.getFileManager(); + PrettyStackTraceDoubleString trace("Loading API notes from ", + apiNotesFile->getName()); // If the API notes file is already in the binary form, load it directly. StringRef apiNotesFileName = apiNotesFile->getName(); diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 9dee73cb43fb3..8681d972d679a 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -502,9 +502,6 @@ class APINotesReader::Implementation { /// The Swift version to use for filtering. VersionTuple SwiftVersion; - /// The reader attached to \c InputBuffer. - llvm::BitstreamReader InputReader; - /// The name of the module that we read from the control block. std::string ModuleName; @@ -1278,10 +1275,7 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *inputBuffer, Impl.InputBuffer = inputBuffer; Impl.OwnsInputBuffer = ownsInputBuffer; Impl.SwiftVersion = swiftVersion; - Impl.InputReader.init( - reinterpret_cast(Impl.InputBuffer->getBufferStart()), - reinterpret_cast(Impl.InputBuffer->getBufferEnd())); - llvm::BitstreamCursor cursor(Impl.InputReader); + llvm::BitstreamCursor cursor(Impl.InputBuffer->getBuffer()); // Validate signature. for (auto byte : API_NOTES_SIGNATURE) { @@ -1294,11 +1288,14 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *inputBuffer, // Look at all of the blocks. bool hasValidControlBlock = false; SmallVector scratch; - auto topLevelEntry = cursor.advance(); - while (topLevelEntry.Kind == llvm::BitstreamEntry::SubBlock) { + while (!cursor.AtEndOfStream()) { + auto topLevelEntry = cursor.advance(); + if (topLevelEntry.Kind != llvm::BitstreamEntry::SubBlock) + break; + switch (topLevelEntry.ID) { case llvm::bitc::BLOCKINFO_BLOCK_ID: - if (cursor.ReadBlockInfoBlock()) { + if (!cursor.ReadBlockInfoBlock()) { failed = true; break; } @@ -1399,11 +1396,9 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *inputBuffer, } break; } - - topLevelEntry = cursor.advance(llvm::BitstreamCursor::AF_DontPopBlockAtEnd); } - if (topLevelEntry.Kind != llvm::BitstreamEntry::EndBlock) { + if (!cursor.AtEndOfStream()) { failed = true; return; } From 3a1210fa3393ab842e412b768fb0a5edd5efd76d Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 10 Nov 2016 14:03:35 -0800 Subject: [PATCH 113/585] [APINotes] Go with a slightly simpler overload. NFC. (#38) apple-llvm-split-commit: df2cd0cde3d5ad1e59ffafdcf3c07a726c013ceb apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 8681d972d679a..11117a4a1680c 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -1275,7 +1275,7 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *inputBuffer, Impl.InputBuffer = inputBuffer; Impl.OwnsInputBuffer = ownsInputBuffer; Impl.SwiftVersion = swiftVersion; - llvm::BitstreamCursor cursor(Impl.InputBuffer->getBuffer()); + llvm::BitstreamCursor cursor(*Impl.InputBuffer); // Validate signature. for (auto byte : API_NOTES_SIGNATURE) { From 10eff17ffc1ad1a99c4175363413def4479497d4 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Tue, 25 Oct 2016 17:37:58 -0700 Subject: [PATCH 114/585] [APINotes] Add support for nullability on arrays. Everything just works once we call the method correctly. Part of rdar://problem/25846421. apple-llvm-split-commit: 3c6ec7a509ad01f4e00d8e61c5c6462a7d60c172 apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaAPINotes.cpp | 2 +- clang/test/APINotes/Inputs/Headers/HeaderLib.apinotes | 6 ++++++ clang/test/APINotes/Inputs/Headers/HeaderLib.h | 1 + clang/test/APINotes/availability.m | 4 ++-- clang/test/APINotes/nullability.c | 5 +++++ 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 121a7309a8334..2257c14965876 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -64,7 +64,7 @@ static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability, QualType origType = type; S.checkNullabilityTypeSpecifier(type, nullability, decl->getLocation(), /*isContextSensitive=*/false, - /*implicit=*/true, + isa(decl), /*implicit=*/true, overrideExisting); if (type.getTypePtr() == origType.getTypePtr()) return; diff --git a/clang/test/APINotes/Inputs/Headers/HeaderLib.apinotes b/clang/test/APINotes/Inputs/Headers/HeaderLib.apinotes index f1cd086126862..c822964ad29c7 100644 --- a/clang/test/APINotes/Inputs/Headers/HeaderLib.apinotes +++ b/clang/test/APINotes/Inputs/Headers/HeaderLib.apinotes @@ -10,6 +10,12 @@ Functions: - Name: do_something_with_pointers NullabilityOfRet: O Nullability: [ N, O ] + - Name: do_something_with_arrays + Parameters: + - Position: 0 + Nullability: N + - Position: 1 + Nullability: N - Name: take_pointer_and_int Parameters: - Position: 0 diff --git a/clang/test/APINotes/Inputs/Headers/HeaderLib.h b/clang/test/APINotes/Inputs/Headers/HeaderLib.h index ec66166adb236..8065249607851 100644 --- a/clang/test/APINotes/Inputs/Headers/HeaderLib.h +++ b/clang/test/APINotes/Inputs/Headers/HeaderLib.h @@ -9,6 +9,7 @@ int unavailable_function(void); int unavailable_global_int; void do_something_with_pointers(int *ptr1, int *ptr2); +void do_something_with_arrays(int simple[], int nested[][2]); typedef int unavailable_typedef; struct unavailable_struct { int x, y, z; }; diff --git a/clang/test/APINotes/availability.m b/clang/test/APINotes/availability.m index 5537316dcaa01..6a3034c0ceb50 100644 --- a/clang/test/APINotes/availability.m +++ b/clang/test/APINotes/availability.m @@ -13,10 +13,10 @@ int main() { // expected-note@HeaderLib.h:9{{'unavailable_global_int' has been explicitly marked unavailable here}} unavailable_typedef t; // expected-error{{'unavailable_typedef' is unavailable}} - // expected-note@HeaderLib.h:13{{'unavailable_typedef' has been explicitly marked unavailable here}} + // expected-note@HeaderLib.h:14{{'unavailable_typedef' has been explicitly marked unavailable here}} struct unavailable_struct s; // expected-error{{'unavailable_struct' is unavailable}} - // expected-note@HeaderLib.h:14{{'unavailable_struct' has been explicitly marked unavailable here}} + // expected-note@HeaderLib.h:15{{'unavailable_struct' has been explicitly marked unavailable here}} B *b = 0; // expected-error{{'B' is unavailable: just don't}} // expected-note@SomeKit/SomeKit.h:15{{'B' has been explicitly marked unavailable here}} diff --git a/clang/test/APINotes/nullability.c b/clang/test/APINotes/nullability.c index 36507f1b9724b..1fcd0ee1b42bc 100644 --- a/clang/test/APINotes/nullability.c +++ b/clang/test/APINotes/nullability.c @@ -8,6 +8,11 @@ int main() { int i = 0; do_something_with_pointers(&i, 0); do_something_with_pointers(0, &i); // expected-warning{{null passed to a callee that requires a non-null argument}} + + extern void *p; + do_something_with_arrays(0, p); // expected-warning{{null passed to a callee that requires a non-null argument}} + do_something_with_arrays(p, 0); // expected-warning{{null passed to a callee that requires a non-null argument}} + take_pointer_and_int(0, 0); // expected-warning{{null passed to a callee that requires a non-null argument}} float *fp = global_int; // expected-warning{{incompatible pointer types initializing 'float *' with an expression of type 'int * _Nonnull'}} From 2288a35a530bc537d38d1bbbd5fd4f0deca605cc Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Fri, 11 Nov 2016 14:56:11 -0800 Subject: [PATCH 115/585] [APINotes] Add a 'SwiftImportAsAccessors' entry for properties. (#39) If true, Swift will import the property as a getter and setter (or just a getter for read-only properties), rather than as a Swift 'var' property. At least, it will once this lands and the Swift side gets implemented. Part of rdar://problem/28455962 apple-llvm-split-commit: ac91fd5da8be22915b9bf9592e20536df6a18b70 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 33 ++++++++++++++++++- clang/include/clang/Basic/Attr.td | 8 +++++ clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 3 ++ clang/lib/APINotes/APINotesWriter.cpp | 8 ++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 6 ++++ clang/lib/Sema/SemaAPINotes.cpp | 7 ++++ .../Headers/VersionedKit.apinotes | 31 ++++++++++++++++- .../Headers/VersionedKit.h | 11 +++++++ clang/test/APINotes/Inputs/roundtrip.apinotes | 12 +++++++ clang/test/APINotes/properties.m | 31 +++++++++++++++++ 11 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 clang/test/APINotes/properties.m diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index fae55017e6d0b..09fbc8dcecb2e 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -336,8 +336,13 @@ class VariableInfo : public CommonEntityInfo { /// Describes API notes data for an Objective-C property. class ObjCPropertyInfo : public VariableInfo { + unsigned SwiftImportAsAccessorsSpecified : 1; + unsigned SwiftImportAsAccessors : 1; + public: - ObjCPropertyInfo() : VariableInfo() { } + ObjCPropertyInfo() + : VariableInfo(), SwiftImportAsAccessorsSpecified(false), + SwiftImportAsAccessors(false) {} /// Merge class-wide information into the given property. friend ObjCPropertyInfo &operator|=(ObjCPropertyInfo &lhs, @@ -351,6 +356,32 @@ class ObjCPropertyInfo : public VariableInfo { return lhs; } + + Optional getSwiftImportAsAccessors() const { + if (SwiftImportAsAccessorsSpecified) + return SwiftImportAsAccessors; + return None; + } + void setSwiftImportAsAccessors(Optional value) { + if (value.hasValue()) { + SwiftImportAsAccessorsSpecified = true; + SwiftImportAsAccessors = value.getValue(); + } else { + SwiftImportAsAccessorsSpecified = false; + SwiftImportAsAccessors = false; + } + } + + friend ObjCPropertyInfo &operator|=(ObjCPropertyInfo &lhs, + const ObjCPropertyInfo &rhs) { + lhs |= static_cast(rhs); + if (!lhs.SwiftImportAsAccessorsSpecified && + rhs.SwiftImportAsAccessorsSpecified) { + lhs.SwiftImportAsAccessorsSpecified = true; + lhs.SwiftImportAsAccessors = rhs.SwiftImportAsAccessors; + } + return lhs; + } }; /// Describes a function or method parameter. diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 11ee3426e4df1..84a3ef9589cb5 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1475,6 +1475,14 @@ def SwiftSuppressFactoryAsInit : InheritableAttr { let Documentation = [Undocumented]; } +def SwiftImportPropertyAsAccessors : InheritableAttr { + // This attribute has no spellings as it is only ever created implicitly + // from API notes. + let Spellings = []; + let SemaHandler = 0; + let Documentation = [Undocumented]; +} + def ReqdWorkGroupSize : InheritableAttr { let Spellings = [GNU<"reqd_work_group_size">]; let Args = [UnsignedArgument<"XDim">, UnsignedArgument<"YDim">, diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 4623773a1f0d4..b3d2d36c93da4 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 19; // SwiftWrapper +const uint16_t VERSION_MINOR = 20; // ImportPropertyAsAccessors using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 11117a4a1680c..2599830cc105c 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -285,6 +285,9 @@ namespace { const uint8_t *&data) { ObjCPropertyInfo info; readVariableInfo(data, info); + uint8_t flags = *data++; + if (flags & (1 << 0)) + info.setSwiftImportAsAccessors(flags & (1 << 1)); return info; } }; diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index c0527aa483a5e..d696aa604e28c 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -606,11 +606,17 @@ namespace { } unsigned getUnversionedInfoSize(const ObjCPropertyInfo &info) { - return getVariableInfoSize(info); + return getVariableInfoSize(info) + 1; } void emitUnversionedInfo(raw_ostream &out, const ObjCPropertyInfo &info) { emitVariableInfo(out, info); + uint8_t flags = 0; + if (Optional value = info.getSwiftImportAsAccessors()) { + flags |= 1 << 0; + flags |= value.getValue() << 1; + } + out << flags; } }; } // end anonymous namespace diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index d24a63fafef1b..1f3783c6a18a7 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -199,6 +199,7 @@ namespace { AvailabilityItem Availability; Optional SwiftPrivate; StringRef SwiftName; + Optional SwiftImportAsAccessors; }; typedef std::vector PropertiesSeq; @@ -398,6 +399,7 @@ namespace llvm { io.mapOptional("AvailabilityMsg", p.Availability.Msg); io.mapOptional("SwiftPrivate", p.SwiftPrivate); io.mapOptional("SwiftName", p.SwiftName); + io.mapOptional("SwiftImportAsAccessors", p.SwiftImportAsAccessors); } }; @@ -784,6 +786,8 @@ namespace { pInfo.SwiftName = prop.SwiftName; if (prop.Nullability) pInfo.setNullabilityAudited(*prop.Nullability); + if (prop.SwiftImportAsAccessors) + pInfo.setSwiftImportAsAccessors(*prop.SwiftImportAsAccessors); if (prop.Kind) { Writer->addObjCProperty(clID, prop.Name, *prop.Kind == MethodKind::Instance, pInfo, @@ -1197,6 +1201,8 @@ namespace { property.Nullability = *nullability; } + property.SwiftImportAsAccessors = info.getSwiftImportAsAccessors(); + auto &items = getTopLevelItems(swiftVersion); knownContexts[contextID.Value].getContext(swiftVersion, items) .Properties.push_back(property); diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 2257c14965876..73329229ae2d9 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -310,6 +310,13 @@ static void ProcessAPINotes(Sema &S, ObjCPropertyDecl *D, // Handle common entity information. ProcessAPINotes(S, D, static_cast(info), role); + if (auto asAccessors = info.getSwiftImportAsAccessors()) { + handleAPINotedAttribute(S, D, + *asAccessors, + role, [&] { + return SwiftImportPropertyAsAccessorsAttr::CreateImplicit(S.Context); + }); + } } namespace { diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index 4215fb9d15318..c3b70b6e9520b 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -1,9 +1,38 @@ -Name: SomeKit +Name: VersionedKit +Classes: + - Name: TestProperties + Properties: + - Name: accessorsOnly + PropertyKind: Instance + SwiftImportAsAccessors: true + - Name: accessorsOnlyForClass + PropertyKind: Class + SwiftImportAsAccessors: true + - Name: accessorsOnlyExceptInVersion3 + PropertyKind: Instance + SwiftImportAsAccessors: true + - Name: accessorsOnlyForClassExceptInVersion3 + PropertyKind: Class + SwiftImportAsAccessors: true SwiftVersions: - Version: 3.0 Classes: - Name: MyReferenceType SwiftBridge: '' + - Name: TestProperties + Properties: + - Name: accessorsOnlyInVersion3 + PropertyKind: Instance + SwiftImportAsAccessors: true + - Name: accessorsOnlyForClassInVersion3 + PropertyKind: Class + SwiftImportAsAccessors: true + - Name: accessorsOnlyExceptInVersion3 + PropertyKind: Instance + SwiftImportAsAccessors: false + - Name: accessorsOnlyForClassExceptInVersion3 + PropertyKind: Class + SwiftImportAsAccessors: false Functions: - Name: moveToPoint SwiftName: 'moveTo(a:b:)' diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h index db71d8f576f6d..ffbc7df7229d2 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -17,3 +17,14 @@ __attribute__((swift_bridge("MyValueType"))) void privateFunc(void) __attribute__((swift_private)); typedef double MyDoubleWrapper __attribute__((swift_wrapper(struct))); + +@interface TestProperties +@property (nonatomic, readwrite, retain) id accessorsOnly; +@property (nonatomic, readwrite, retain, class) id accessorsOnlyForClass; + +@property (nonatomic, readwrite, retain) id accessorsOnlyInVersion3; +@property (nonatomic, readwrite, retain, class) id accessorsOnlyForClassInVersion3; + +@property (nonatomic, readwrite, retain) id accessorsOnlyExceptInVersion3; +@property (nonatomic, readwrite, retain, class) id accessorsOnlyForClassExceptInVersion3; +@end diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index c6beaf1928f26..68169c4e4d259 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -104,6 +104,7 @@ Classes: AvailabilityMsg: '' SwiftPrivate: false SwiftName: '' + SwiftImportAsAccessors: false Functions: - Name: NSAvailableWindowDepths NullabilityOfRet: N @@ -162,3 +163,14 @@ SwiftVersions: SwiftPrivate: true SwiftName: '' DesignatedInit: true + - Name: NSView + Availability: available + AvailabilityMsg: '' + SwiftName: '' + Properties: + - Name: makeBackingLayer + PropertyKind: Class + Availability: available + AvailabilityMsg: '' + SwiftName: '' + SwiftImportAsAccessors: true diff --git a/clang/test/APINotes/properties.m b/clang/test/APINotes/properties.m new file mode 100644 index 0000000000000..a4c925b67449a --- /dev/null +++ b/clang/test/APINotes/properties.m @@ -0,0 +1,31 @@ +// RUN: rm -rf %t && mkdir -p %t + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fblocks -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'TestProperties::' | FileCheck -check-prefix=CHECK -check-prefix=CHECK-4 %s +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fblocks -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'TestProperties::' -fapinotes-swift-version=3 | FileCheck -check-prefix=CHECK -check-prefix=CHECK-3 %s + +// I know, FileChecking an AST dump is brittle. However, the attributes being +// tested aren't used for anything by Clang, and don't even have a spelling. + +@import VersionedKit; + +// CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnly 'id' +// CHECK-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit + +// CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyForClass 'id' +// CHECK-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit + +// CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyInVersion3 'id' +// CHECK-3-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit +// CHECK-4-NEXT: {{^$}} + +// CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyForClassInVersion3 'id' +// CHECK-3-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit +// CHECK-4-NEXT: {{^$}} + +// CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyExceptInVersion3 'id' +// CHECK-3-NEXT: {{^$}} +// CHECK-4-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit + +// CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyForClassExceptInVersion3 'id' +// CHECK-3-NEXT: {{^$}} +// CHECK-4-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit From 09cdee995b9c8ef17fdcf350093e992cd83ab563 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 11 Nov 2016 16:27:19 -0800 Subject: [PATCH 116/585] [API Notes] Add support for expressing the types of entities. Introduces the "Type" key for global variables, properties, and parameters and the "ResultType" key for functions and methods, to describe the (Objective-)C type of that entity. This commit handles YAML, the representation of this information, and round-tripping through the binary API notes. Part of rdar://problem/28943642. apple-llvm-split-commit: ebca3ea28181818c7fe7ba320c9ae7775d8678fa apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 20 ++++++- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 33 ++++++++---- clang/lib/APINotes/APINotesWriter.cpp | 53 ++++++++++++------- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 26 +++++++-- clang/test/APINotes/Inputs/roundtrip.apinotes | 5 ++ 6 files changed, 103 insertions(+), 36 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 09fbc8dcecb2e..6fbd5d809dad6 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -297,6 +297,9 @@ class VariableInfo : public CommonEntityInfo { /// has been audited. unsigned Nullable : 2; + /// The C type of the variable, as a string. + std::string Type; + public: VariableInfo() : CommonEntityInfo(), @@ -315,10 +318,14 @@ class VariableInfo : public CommonEntityInfo { Nullable = static_cast(kind); } + const std::string &getType() const { return Type; } + void setType(const std::string &type) { Type = type; } + friend bool operator==(const VariableInfo &lhs, const VariableInfo &rhs) { return static_cast(lhs) == rhs && lhs.NullabilityAudited == rhs.NullabilityAudited && - lhs.Nullable == rhs.Nullable; + lhs.Nullable == rhs.Nullable && + lhs.Type == rhs.Type; } friend bool operator!=(const VariableInfo &lhs, const VariableInfo &rhs) { @@ -330,6 +337,8 @@ class VariableInfo : public CommonEntityInfo { static_cast(lhs) |= rhs; if (!lhs.NullabilityAudited && rhs.NullabilityAudited) lhs.setNullabilityAudited(*rhs.getNullability()); + if (lhs.Type.empty() && !rhs.Type.empty()) + lhs.Type = rhs.Type; return lhs; } }; @@ -347,6 +356,8 @@ class ObjCPropertyInfo : public VariableInfo { /// Merge class-wide information into the given property. friend ObjCPropertyInfo &operator|=(ObjCPropertyInfo &lhs, const ObjCContextInfo &rhs) { + static_cast(lhs) |= rhs; + // Merge nullability. if (!lhs.getNullability()) { if (auto nullable = rhs.getDefaultNullability()) { @@ -462,6 +473,9 @@ class FunctionInfo : public CommonEntityInfo { // of the parameters. uint64_t NullabilityPayload = 0; + /// The result type of this function, as a C type. + std::string ResultType; + /// The function parameters. std::vector Params; @@ -525,7 +539,9 @@ class FunctionInfo : public CommonEntityInfo { return static_cast(lhs) == rhs && lhs.NullabilityAudited == rhs.NullabilityAudited && lhs.NumAdjustedNullable == rhs.NumAdjustedNullable && - lhs.NullabilityPayload == rhs.NullabilityPayload; + lhs.NullabilityPayload == rhs.NullabilityPayload && + lhs.ResultType == rhs.ResultType && + lhs.Params == rhs.Params; } friend bool operator!=(const FunctionInfo &lhs, const FunctionInfo &rhs) { diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index b3d2d36c93da4..ef3ef73ab0cdd 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 20; // ImportPropertyAsAccessors +const uint16_t VERSION_MINOR = 21; // Override types using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 2599830cc105c..8fbe2a184541e 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -265,6 +265,11 @@ namespace { info.setNullabilityAudited(static_cast(*data)); } ++data; + + auto typeLen + = endian::readNext(data); + info.setType(std::string(data, data + typeLen)); + data += typeLen; } /// Used to deserialize the on-disk Objective-C property table. @@ -292,6 +297,17 @@ namespace { } }; + /// Read serialized ParamInfo. + void readParamInfo(const uint8_t *&data, ParamInfo &info) { + readVariableInfo(data, info); + + uint8_t payload = endian::readNext(data); + if (payload & 0x01) { + info.setNoEscape(payload & 0x02); + } + payload >>= 2; assert(payload == 0 && "Bad API notes"); + } + /// Read serialized FunctionInfo. void readFunctionInfo(const uint8_t *&data, FunctionInfo &info) { readCommonEntityInfo(data, info); @@ -304,21 +320,16 @@ namespace { unsigned numParams = endian::readNext(data); while (numParams > 0) { - uint8_t payload = endian::readNext(data); - ParamInfo pi; - uint8_t nullabilityValue = payload & 0x3; payload >>= 2; - if (payload & 0x01) - pi.setNullabilityAudited(static_cast(nullabilityValue)); - payload >>= 1; - if (payload & 0x01) { - pi.setNoEscape(payload & 0x02); - } - payload >>= 2; assert(payload == 0 && "Bad API notes"); - + readParamInfo(data, pi); info.Params.push_back(pi); --numParams; } + + unsigned resultTypeLen + = endian::readNext(data); + info.ResultType = std::string(data, data + resultTypeLen); + data += resultTypeLen; } /// Used to deserialize the on-disk Objective-C method table. diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index d696aa604e28c..86b6abd2a36e6 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -490,7 +490,7 @@ namespace { /// Retrieve the serialized size of the given VariableInfo, for use in /// on-disk hash tables. unsigned getVariableInfoSize(const VariableInfo &info) { - return 2 + getCommonEntityInfoSize(info); + return 2 + getCommonEntityInfoSize(info) + 2 + info.getType().size(); } /// Emit a serialized representation of the variable information. @@ -506,6 +506,10 @@ namespace { } out.write(reinterpret_cast(bytes), 2); + + endian::Writer writer(out); + writer.write(info.getType().size()); + out.write(info.getType().data(), info.getType().size()); } /// On-dish hash table info key base for handling versioned data. @@ -691,11 +695,34 @@ void APINotesWriter::Implementation::writeObjCPropertyBlock( } namespace { + static unsigned getParamInfoSize(const ParamInfo &info) { + return getVariableInfoSize(info) + 1; + } + + static void emitParamInfo(raw_ostream &out, const ParamInfo &info) { + emitVariableInfo(out, info); + + endian::Writer writer(out); + + uint8_t payload = 0; + if (auto noescape = info.isNoEscape()) { + payload |= 0x01; + if (*noescape) + payload |= 0x02; + } + writer.write(payload); + } + /// Retrieve the serialized size of the given FunctionInfo, for use in /// on-disk hash tables. static unsigned getFunctionInfoSize(const FunctionInfo &info) { - return 2 + sizeof(uint64_t) + getCommonEntityInfoSize(info) + - 2 + info.Params.size() * 1; + unsigned size = 2 + sizeof(uint64_t) + getCommonEntityInfoSize(info) + 2; + + for (const auto ¶m : info.Params) + size += getParamInfoSize(param); + + size += 2 + info.ResultType.size(); + return size; } /// Emit a serialized representation of the function information. @@ -709,22 +736,12 @@ namespace { // Parameters. writer.write(info.Params.size()); - for (const auto &pi : info.Params) { - uint8_t payload = 0; - if (auto noescape = pi.isNoEscape()) { - payload |= 0x01; - if (*noescape) - payload |= 0x02; - } - - auto nullability = pi.getNullability(); - payload = (payload << 1) | nullability.hasValue(); + for (const auto &pi : info.Params) + emitParamInfo(out, pi); - payload = payload << 2; - if (nullability) - payload |= static_cast(*nullability); - writer.write(payload); - } + // Result type. + writer.write(info.ResultType.size()); + out.write(info.ResultType.data(), info.ResultType.size()); } /// Used to serialize the on-disk Objective-C method table. diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 1f3783c6a18a7..0ac0b6d65b767 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -173,6 +173,7 @@ namespace { unsigned Position; Optional NoEscape = false; llvm::Optional Nullability; + StringRef Type; }; typedef std::vector ParamsSeq; @@ -189,6 +190,7 @@ namespace { = api_notes::FactoryAsInitKind::Infer; bool DesignatedInit = false; bool Required = false; + StringRef ResultType; }; typedef std::vector MethodsSeq; @@ -200,6 +202,7 @@ namespace { Optional SwiftPrivate; StringRef SwiftName; Optional SwiftImportAsAccessors; + StringRef Type; }; typedef std::vector PropertiesSeq; @@ -224,6 +227,8 @@ namespace { AvailabilityItem Availability; Optional SwiftPrivate; StringRef SwiftName; + StringRef Type; + StringRef ResultType; }; typedef std::vector FunctionsSeq; @@ -233,6 +238,7 @@ namespace { AvailabilityItem Availability; Optional SwiftPrivate; StringRef SwiftName; + StringRef Type; }; typedef std::vector GlobalVariablesSeq; @@ -385,6 +391,7 @@ namespace llvm { io.mapOptional("Nullability", p.Nullability, AbsentNullability); io.mapOptional("NoEscape", p.NoEscape); + io.mapOptional("Type", p.Type, StringRef("")); } }; @@ -400,6 +407,7 @@ namespace llvm { io.mapOptional("SwiftPrivate", p.SwiftPrivate); io.mapOptional("SwiftName", p.SwiftName); io.mapOptional("SwiftImportAsAccessors", p.SwiftImportAsAccessors); + io.mapOptional("Type", p.Type, StringRef("")); } }; @@ -420,6 +428,7 @@ namespace llvm { api_notes::FactoryAsInitKind::Infer); io.mapOptional("DesignatedInit", m.DesignatedInit, false); io.mapOptional("Required", m.Required, false); + io.mapOptional("ResultType", m.ResultType, StringRef("")); } }; @@ -451,6 +460,7 @@ namespace llvm { io.mapOptional("AvailabilityMsg", f.Availability.Msg); io.mapOptional("SwiftPrivate", f.SwiftPrivate); io.mapOptional("SwiftName", f.SwiftName); + io.mapOptional("ResultType", f.ResultType, StringRef("")); } }; @@ -464,6 +474,7 @@ namespace llvm { io.mapOptional("AvailabilityMsg", v.Availability.Msg); io.mapOptional("SwiftPrivate", v.SwiftPrivate); io.mapOptional("SwiftName", v.SwiftName); + io.mapOptional("Type", v.Type, StringRef("")); } }; @@ -621,7 +632,7 @@ namespace { if (p.Nullability) pi.setNullabilityAudited(*p.Nullability); pi.setNoEscape(p.NoEscape); - + pi.setType(p.Type); while (outInfo.Params.size() <= p.Position) { outInfo.Params.push_back(ParamInfo()); } @@ -712,6 +723,7 @@ namespace { mInfo.Required = meth.Required; if (meth.FactoryAsInit != FactoryAsInitKind::Infer) mInfo.setFactoryAsInitKind(meth.FactoryAsInit); + mInfo.ResultType = meth.ResultType; // Translate parameter information. convertParams(meth.Params, mInfo); @@ -788,6 +800,7 @@ namespace { pInfo.setNullabilityAudited(*prop.Nullability); if (prop.SwiftImportAsAccessors) pInfo.setSwiftImportAsAccessors(*prop.SwiftImportAsAccessors); + pInfo.setType(prop.Type); if (prop.Kind) { Writer->addObjCProperty(clID, prop.Name, *prop.Kind == MethodKind::Instance, pInfo, @@ -844,6 +857,7 @@ namespace { info.SwiftName = global.SwiftName; if (global.Nullability) info.setNullabilityAudited(*global.Nullability); + info.setType(global.Type); Writer->addGlobalVariable(global.Name, info, swiftVersion); } @@ -867,7 +881,7 @@ namespace { convertNullability(function.Nullability, function.NullabilityOfRet, info, function.Name); - + info.ResultType = function.ResultType; Writer->addGlobalFunction(function.Name, info, swiftVersion); } @@ -1112,6 +1126,7 @@ namespace { p.Position = position++; p.Nullability = pi.getNullability(); p.NoEscape = pi.isNoEscape(); + p.Type = copyString(pi.getType()); params.push_back(p); } } @@ -1181,7 +1196,7 @@ namespace { method.FactoryAsInit = info.getFactoryAsInitKind(); method.DesignatedInit = info.DesignatedInit; method.Required = info.Required; - + method.ResultType = copyString(info.ResultType); auto &items = getTopLevelItems(swiftVersion); knownContexts[contextID.Value].getContext(swiftVersion, items) .Methods.push_back(method); @@ -1203,6 +1218,8 @@ namespace { property.SwiftImportAsAccessors = info.getSwiftImportAsAccessors(); + property.Type = copyString(info.getType()); + auto &items = getTopLevelItems(swiftVersion); knownContexts[contextID.Value].getContext(swiftVersion, items) .Properties.push_back(property); @@ -1218,7 +1235,7 @@ namespace { if (info.NumAdjustedNullable > 0) handleNullability(function.Nullability, function.NullabilityOfRet, info, info.NumAdjustedNullable-1); - + function.ResultType = copyString(info.ResultType); auto &items = getTopLevelItems(swiftVersion); items.Functions.push_back(function); } @@ -1234,6 +1251,7 @@ namespace { if (auto nullability = info.getNullability()) { global.Nullability = *nullability; } + global.Type = copyString(info.getType()); auto &items = getTopLevelItems(swiftVersion); items.Globals.push_back(global); diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 68169c4e4d259..05599a772d5d6 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -17,6 +17,7 @@ Classes: SwiftPrivate: false SwiftName: '' FactoryAsInit: C + ResultType: id - Selector: init MethodKind: Instance NullabilityOfRet: U @@ -77,6 +78,7 @@ Classes: - Position: 1 - Position: 2 NoEscape: true + Type: id Nullability: [ N, N, O ] NullabilityOfRet: N Availability: available @@ -97,6 +99,7 @@ Classes: Availability: available AvailabilityMsg: '' SwiftName: enclosing + Type: id - Name: makeBackingLayer PropertyKind: Class Nullability: N @@ -111,6 +114,7 @@ Functions: Availability: available AvailabilityMsg: '' SwiftName: 'availableWindowDepths()' + ResultType: NSInteger Globals: - Name: NSCalibratedWhiteColorSpace Nullability: N @@ -118,6 +122,7 @@ Globals: AvailabilityMsg: '' SwiftPrivate: false SwiftName: calibratedWhite + Type: id Enumerators: - Name: NSColorRed Availability: available From 239c40199eb1b9d96df188c16a1e73946871fe50 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 11 Nov 2016 16:43:52 -0800 Subject: [PATCH 117/585] [API Notes] Enable support for overriding the types of declarations. Lazily parse the types provided in API notes, and apply them to the AST. The lazy parsing is handled via a callback from Sema into the Parser, which parses the type in the current context. We perform some simplistic sanity checking (e.g., the size of the replacement type is the same as the original type), but otherwise this is a dangerous power tool that will need to be used carefully. The specific API notes features this enables are making use of the 'Type' field property/global variable/parameter API notes and the 'ResultType' field of method and function API notes. Finishes rdar://problem/28943642. apple-llvm-split-commit: 4aa4c8bd93b23c4f83ae954a9f187b7c3bdd2b40 apple-llvm-split-dir: clang/ --- .../clang/Basic/DiagnosticParseKinds.td | 3 + .../clang/Basic/DiagnosticSemaKinds.td | 7 ++ clang/include/clang/Lex/Lexer.h | 6 +- clang/include/clang/Parse/Parser.h | 14 ++- clang/include/clang/Sema/Sema.h | 7 ++ clang/lib/Parse/ParseDecl.cpp | 65 ++++++++++ clang/lib/Parse/Parser.cpp | 8 ++ clang/lib/Sema/SemaAPINotes.cpp | 115 +++++++++++++++++- clang/lib/Sema/SemaDecl.cpp | 18 ++- .../APINotes/SomeKit.apinotes | 25 ++++ .../Headers/SomeKit.apinotes | 25 ++++ .../SomeKit.framework/Headers/SomeKit.h | 12 ++ .../Inputs/Headers/BrokenTypes.apinotes | 10 ++ .../APINotes/Inputs/Headers/BrokenTypes.h | 8 ++ .../APINotes/Inputs/Headers/module.modulemap | 4 + clang/test/APINotes/broken_types.m | 19 +++ clang/test/APINotes/types.m | 23 ++++ 17 files changed, 357 insertions(+), 12 deletions(-) create mode 100644 clang/test/APINotes/Inputs/Headers/BrokenTypes.apinotes create mode 100644 clang/test/APINotes/Inputs/Headers/BrokenTypes.h create mode 100644 clang/test/APINotes/broken_types.m create mode 100644 clang/test/APINotes/types.m diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index b5b90559b6e9c..be8802e5baf97 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1024,6 +1024,9 @@ def err_pragma_loop_invalid_option : Error< def err_pragma_invalid_keyword : Error< "invalid argument; expected 'enable'%select{|, 'full'}0%select{|, 'assume_safety'}1 or 'disable'">; +// API notes. +def err_type_unparsed : Error<"unparsed tokens following type">; + // Pragma unroll support. def warn_pragma_unroll_cuda_value_in_parens : Warning< "argument to '#pragma unroll' should not be in parentheses in CUDA C/C++">, diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 51f10ba1d34e3..62775c0c1ee87 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8248,6 +8248,13 @@ def ext_opencl_ext_vector_type_rgba_selector: ExtWarn< InGroup; } // end of sema category +let CategoryName = "API Notes Issue" in { + +def err_incompatible_replacement_type : Error< + "API notes replacement type %0 has a different size from original type %1">; + +} + let CategoryName = "OpenMP Issue" in { // OpenMP support. def err_omp_expected_var_arg : Error< diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h index 830c25a2e4d29..6f1f7c6151b5d 100644 --- a/clang/include/clang/Lex/Lexer.h +++ b/clang/include/clang/Lex/Lexer.h @@ -133,15 +133,17 @@ class Lexer : public PreprocessorLexer { /// from. Currently this is only used by _Pragma handling. SourceLocation getFileLoc() const { return FileLoc; } -private: /// Lex - Return the next token in the file. If this is the end of file, it /// return the tok::eof token. This implicitly involves the preprocessor. bool Lex(Token &Result); -public: /// isPragmaLexer - Returns true if this Lexer is being used to lex a pragma. bool isPragmaLexer() const { return Is_PragmaLexer; } + /// Note that this Lexer is being used to lex a pragma, or something like it + /// that has simple end-of-file behavior. + void setIsPragmaLexer(bool value) { Is_PragmaLexer = value; } + private: /// IndirectLex - An indirect call to 'Lex' that can be invoked via /// the PreprocessorLexer interface. diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 91d3c846cacf6..b5d7e93449bdb 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2695,7 +2695,19 @@ class Parser : public CodeCompletionHandler { //===--------------------------------------------------------------------===// // C++11/G++: Type Traits [Type-Traits.html in the GCC manual] ExprResult ParseTypeTrait(); - + + /// Parse the given string as a type. + /// + /// This is a dangerous utility function currently employed only by API notes. + /// It is not a general entry-point for safely parsing types from strings. + /// + /// \param typeStr The string to be parsed as a type. + /// \param context The name of the context in which this string is being + /// parsed, which will be used in diagnostics. + /// \param includeLoc The location at which this parse was triggered. + TypeResult parseTypeFromString(StringRef typeStr, StringRef context, + SourceLocation includeLoc); + //===--------------------------------------------------------------------===// // Embarcadero: Arary and Expression Traits ExprResult ParseArrayTypeTrait(); diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 473a1df11995c..4037cf4906ee7 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -54,6 +54,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/TinyPtrVector.h" #include +#include #include #include #include @@ -573,6 +574,10 @@ class Sema { OpaqueParser = P; } + /// \brief Callback to the parser to parse a type expressed as a string. + std::function + ParseTypeFromStringCallback; + class DelayedDiagnostics; class DelayedDiagnosticsState { @@ -1796,6 +1801,8 @@ class Sema { ParmVarDecl *BuildParmVarDeclForTypedef(DeclContext *DC, SourceLocation Loc, QualType T); + QualType adjustParameterTypeForObjCAutoRefCount(QualType T, + SourceLocation Loc); ParmVarDecl *CheckParameter(DeclContext *DC, SourceLocation StartLoc, SourceLocation NameLoc, IdentifierInfo *Name, QualType T, TypeSourceInfo *TSInfo, diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 3d82280d22ab7..89b7a322569c9 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -6583,3 +6583,68 @@ bool Parser::TryAltiVecTokenOutOfLine(DeclSpec &DS, SourceLocation Loc, } return false; } + +TypeResult Parser::parseTypeFromString(StringRef typeStr, StringRef context, + SourceLocation includeLoc) { + // Consume (unexpanded) tokens up to the end-of-directive. + SmallVector tokens; + { + // Create a new buffer from which we will parse the type. + auto &sourceMgr = PP.getSourceManager(); + FileID fileID = sourceMgr.createFileID( + llvm::MemoryBuffer::getMemBufferCopy(typeStr, context), + SrcMgr::C_User, 0, 0, includeLoc); + + // Form a new lexer that references the buffer. + Lexer lexer(fileID, sourceMgr.getBuffer(fileID), PP); + lexer.setParsingPreprocessorDirective(true); + lexer.setIsPragmaLexer(true); + + // Lex the tokens from that buffer. + Token tok; + do { + lexer.Lex(tok); + tokens.push_back(tok); + } while (tok.isNot(tok::eod)); + } + + // Replace the "eod" token with an "eof" token identifying the end of + // the provided string. + Token &endToken = tokens.back(); + endToken.startToken(); + endToken.setKind(tok::eof); + endToken.setLocation(Tok.getLocation()); + endToken.setEofData(typeStr.data()); + + // Add the current token back. + tokens.push_back(Tok); + + // Enter the tokens into the token stream. + PP.EnterTokenStream(tokens, /*DisableMacroExpansion=*/false); + + // Consume the current token so that we'll start parsing the tokens we + // added to the stream. + ConsumeAnyToken(); + + // Enter a new scope. + ParseScope localScope(this, 0); + + // Parse the type. + TypeResult result = ParseTypeName(nullptr); + + // Check if we parsed the whole thing. + if (result.isUsable() && + (Tok.isNot(tok::eof) || Tok.getEofData() != typeStr.data())) { + Diag(Tok.getLocation(), diag::err_type_unparsed); + } + + // There could be leftover tokens (e.g. because of an error). + // Skip through until we reach the 'end of directive' token. + while (Tok.isNot(tok::eof)) + ConsumeAnyToken(); + + // Consume the end token. + if (Tok.is(tok::eof) && Tok.getEofData() == typeStr.data()) + ConsumeAnyToken(); + return result; +} diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 78eba62695dda..0be9b32f14fa4 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -88,6 +88,11 @@ Parser::Parser(Preprocessor &pp, Sema &actions, bool skipFunctionBodies) PP.addCommentHandler(CommentSemaHandler.get()); PP.setCodeCompletionHandler(*this); + + Actions.ParseTypeFromStringCallback = + [this](StringRef typeStr, StringRef context, SourceLocation includeLoc) { + return this->parseTypeFromString(typeStr, context, includeLoc); + }; } DiagnosticBuilder Parser::Diag(SourceLocation Loc, unsigned DiagID) { @@ -420,6 +425,9 @@ Parser::ParseScopeFlags::~ParseScopeFlags() { //===----------------------------------------------------------------------===// Parser::~Parser() { + // Clear out the parse-type-from-string callback. + Actions.ParseTypeFromStringCallback = nullptr; + // If we still have scopes active, delete the scope tree. delete getCurScope(); Actions.CurScope = nullptr; diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 73329229ae2d9..cf88f735018a7 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -264,10 +264,62 @@ static void ProcessAPINotes(Sema &S, Decl *D, role); } +/// Check that the replacement type provided by API notes is reasonable. +/// +/// This is a very weak form of ABI check. +static bool checkAPINotesReplacementType(Sema &S, SourceLocation loc, + QualType origType, + QualType replacementType) { + if (S.Context.getTypeSize(origType) != + S.Context.getTypeSize(replacementType)) { + S.Diag(loc, diag::err_incompatible_replacement_type) + << replacementType << origType; + return true; + } + + return false; +} + /// Process API notes for a variable or property. static void ProcessAPINotes(Sema &S, Decl *D, const api_notes::VariableInfo &info, VersionedInfoRole role) { + // Type override. + if (role != VersionedInfoRole::Versioned && + !info.getType().empty() && S.ParseTypeFromStringCallback) { + auto parsedType = S.ParseTypeFromStringCallback(info.getType(), + "", + D->getLocation()); + if (parsedType.isUsable()) { + QualType type = Sema::GetTypeFromParser(parsedType.get()); + auto typeInfo = + S.Context.getTrivialTypeSourceInfo(type, D->getLocation()); + + if (auto var = dyn_cast(D)) { + // Make adjustments to parameter types. + if (isa(var)) { + type = S.adjustParameterTypeForObjCAutoRefCount(type, + D->getLocation()); + type = S.Context.getAdjustedParameterType(type); + } + + if (!checkAPINotesReplacementType(S, var->getLocation(), var->getType(), + type)) { + var->setType(type); + var->setTypeSourceInfo(typeInfo); + } + } else if (auto property = dyn_cast(D)) { + if (!checkAPINotesReplacementType(S, property->getLocation(), + property->getType(), + type)) { + property->setType(type, typeInfo); + } + } else { + llvm_unreachable("API notes allowed a type on an unknown declaration"); + } + } + } + // Nullability. if (auto Nullability = info.getNullability()) { applyNullability(S, D, *Nullability, role); @@ -347,20 +399,75 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, NumParams = FD->getNumParams(); else NumParams = MD->param_size(); - + + bool anyTypeChanged = false; for (unsigned I = 0; I != NumParams; ++I) { ParmVarDecl *Param; if (FD) Param = FD->getParamDecl(I); else Param = MD->param_begin()[I]; - + + QualType paramTypeBefore = Param->getType(); + + if (I < info.Params.size()) { + ProcessAPINotes(S, Param, info.Params[I], role); + } + // Nullability. if (info.NullabilityAudited) applyNullability(S, Param, info.getParamTypeInfo(I), role); - if (I < info.Params.size()) { - ProcessAPINotes(S, Param, info.Params[I], role); + if (paramTypeBefore.getAsOpaquePtr() != Param->getType().getAsOpaquePtr()) + anyTypeChanged = true; + } + + // Result type override. + QualType overriddenResultType; + if (role != VersionedInfoRole::Versioned && !info.ResultType.empty() && + S.ParseTypeFromStringCallback) { + auto parsedType = S.ParseTypeFromStringCallback(info.ResultType, + "", + D->getLocation()); + if (parsedType.isUsable()) { + QualType resultType = Sema::GetTypeFromParser(parsedType.get()); + + if (MD) { + if (!checkAPINotesReplacementType(S, D->getLocation(), + MD->getReturnType(), resultType)) { + auto resultTypeInfo = + S.Context.getTrivialTypeSourceInfo(resultType, D->getLocation()); + MD->setReturnType(resultType); + MD->setReturnTypeSourceInfo(resultTypeInfo); + } + } else if (!checkAPINotesReplacementType(S, FD->getLocation(), + FD->getReturnType(), + resultType)) { + overriddenResultType = resultType; + anyTypeChanged = true; + } + } + } + + // If the result type or any of the parameter types changed for a function + // declaration, we have to rebuild the type. + if (FD && anyTypeChanged) { + if (const auto *fnProtoType = FD->getType()->getAs()) { + if (overriddenResultType.isNull()) + overriddenResultType = fnProtoType->getReturnType(); + + SmallVector paramTypes; + for (auto param : FD->parameters()) { + paramTypes.push_back(param->getType()); + } + FD->setType(S.Context.getFunctionType(overriddenResultType, + paramTypes, + fnProtoType->getExtProtoInfo())); + } else if (!overriddenResultType.isNull()) { + const auto *fnNoProtoType = FD->getType()->castAs(); + FD->setType( + S.Context.getFunctionNoProtoType(overriddenResultType, + fnNoProtoType->getExtInfo())); } } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 5369802ed6bda..9cbe8e1ad78ce 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -11257,10 +11257,8 @@ void Sema::DiagnoseSizeOfParametersAndReturnValue( } } -ParmVarDecl *Sema::CheckParameter(DeclContext *DC, SourceLocation StartLoc, - SourceLocation NameLoc, IdentifierInfo *Name, - QualType T, TypeSourceInfo *TSInfo, - StorageClass SC) { +QualType Sema::adjustParameterTypeForObjCAutoRefCount(QualType T, + SourceLocation Loc) { // In ARC, infer a lifetime qualifier for appropriate parameter types. if (getLangOpts().ObjCAutoRefCount && T.getObjCLifetime() == Qualifiers::OCL_None && @@ -11275,7 +11273,7 @@ ParmVarDecl *Sema::CheckParameter(DeclContext *DC, SourceLocation StartLoc, if (!T.isConstQualified()) { DelayedDiagnostics.add( sema::DelayedDiagnostic::makeForbiddenType( - NameLoc, diag::err_arc_array_param_no_ownership, T, false)); + Loc, diag::err_arc_array_param_no_ownership, T, false)); } lifetime = Qualifiers::OCL_ExplicitNone; } else { @@ -11284,6 +11282,16 @@ ParmVarDecl *Sema::CheckParameter(DeclContext *DC, SourceLocation StartLoc, T = Context.getLifetimeQualifiedType(T, lifetime); } + return T; +} + +ParmVarDecl *Sema::CheckParameter(DeclContext *DC, SourceLocation StartLoc, + SourceLocation NameLoc, IdentifierInfo *Name, + QualType T, TypeSourceInfo *TSInfo, + StorageClass SC) { + // Perform Objective-C ARC adjustments. + T = adjustParameterTypeForObjCAutoRefCount(T, NameLoc); + ParmVarDecl *New = ParmVarDecl::Create(Context, DC, StartLoc, NameLoc, Name, Context.getAdjustedParameterType(T), TSInfo, SC, nullptr); diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes index aa43d849be1d0..817af123fc77b 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/APINotes/SomeKit.apinotes @@ -31,6 +31,31 @@ Classes: - Selector: "initWithA:" MethodKind: Instance DesignatedInit: true + - Name: OverriddenTypes + Methods: + - Selector: "methodToMangle:second:" + MethodKind: Instance + ResultType: 'char *' + Parameters: + - Position: 0 + Type: 'SOMEKIT_DOUBLE *' + - Position: 1 + Type: 'float *' + Properties: + - Name: intPropertyToMangle + PropertyKind: Instance + Type: 'double *' +Functions: + - Name: global_int_fun + ResultType: 'char *' + Parameters: + - Position: 0 + Type: 'double *' + - Position: 1 + Type: 'float *' +Globals: + - Name: global_int_ptr + Type: 'double *' SwiftVersions: - Version: 3.0 Classes: diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes index aa43d849be1d0..d2d10037501d7 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes @@ -31,6 +31,31 @@ Classes: - Selector: "initWithA:" MethodKind: Instance DesignatedInit: true + - Name: OverriddenTypes + Methods: + - Selector: "methodToMangle:second:" + MethodKind: Instance + ResultType: 'char *' + Parameters: + - Position: 0 + Type: 'SOMEKIT_DOUBLE *' + - Position: 1 + Type: 'float *' + Properties: + - Name: intPropertyToMangle + PropertyKind: Instance + Type: 'double *' +Functions: + - Name: global_int_fun + ResultType: 'char *' + Parameters: + - Position: 0 + Type: 'double *' + - Position: 1 + Type: 'float *' +Globals: + - Name: global_int_ptr + Type: 'double (*)(int, int)' SwiftVersions: - Version: 3.0 Classes: diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h index 60d9afa30509a..42a9fbcc2b27b 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h @@ -37,4 +37,16 @@ __attribute__((objc_root_class)) #import +extern int *global_int_ptr; + +int *global_int_fun(int *ptr, int *ptr2); + +#define SOMEKIT_DOUBLE double + +__attribute__((objc_root_class)) +@interface OverriddenTypes +-(int *)methodToMangle:(int *)ptr1 second:(int *)ptr2; +@property int *intPropertyToMangle; +@end + #endif diff --git a/clang/test/APINotes/Inputs/Headers/BrokenTypes.apinotes b/clang/test/APINotes/Inputs/Headers/BrokenTypes.apinotes new file mode 100644 index 0000000000000..00f7b5074e985 --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/BrokenTypes.apinotes @@ -0,0 +1,10 @@ +Name: BrokenTypes +Functions: + - Name: break_me_function + ResultType: 'int * with extra junk' + Parameters: + - Position: 0 + Type: 'not_a_type' +Globals: + - Name: break_me_variable + Type: 'double' diff --git a/clang/test/APINotes/Inputs/Headers/BrokenTypes.h b/clang/test/APINotes/Inputs/Headers/BrokenTypes.h new file mode 100644 index 0000000000000..fee054b74cf70 --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/BrokenTypes.h @@ -0,0 +1,8 @@ +#ifndef BROKEN_TYPES_H +#define BROKEN_TYPES_H + +char break_me_function(void *ptr); + +extern char break_me_variable; + +#endif // BROKEN_TYPES_H diff --git a/clang/test/APINotes/Inputs/Headers/module.modulemap b/clang/test/APINotes/Inputs/Headers/module.modulemap index 3e59efcf2c482..54af0f5e76c74 100644 --- a/clang/test/APINotes/Inputs/Headers/module.modulemap +++ b/clang/test/APINotes/Inputs/Headers/module.modulemap @@ -1,3 +1,7 @@ module HeaderLib { header "HeaderLib.h" } + +module BrokenTypes { + header "BrokenTypes.h" +} diff --git a/clang/test/APINotes/broken_types.m b/clang/test/APINotes/broken_types.m new file mode 100644 index 0000000000000..164ae795f4c2d --- /dev/null +++ b/clang/test/APINotes/broken_types.m @@ -0,0 +1,19 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s 2> %t.err +// RUN: FileCheck %s < %t.err + +#include "BrokenTypes.h" + +// CHECK: :1:1: error: unknown type name 'not_a_type' +// CHECK-NEXT: not_a_type +// CHECK-NEXT: ^ + +// CHECK: :1:7: error: unparsed tokens following type +// CHECK-NEXT: int * with extra junk +// CHECK-NEXT: ^ + +// CHECK: BrokenTypes.h:4:6: error: API notes replacement type 'int *' has a different size from original type 'char' + +// CHECK: BrokenTypes.h:6:13: error: API notes replacement type 'double' has a different size from original type 'char' + +// CHECK: 5 errors generated. diff --git a/clang/test/APINotes/types.m b/clang/test/APINotes/types.m new file mode 100644 index 0000000000000..fccff8092cf8e --- /dev/null +++ b/clang/test/APINotes/types.m @@ -0,0 +1,23 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify + +#import + +void test(OverriddenTypes *overridden) { + int *ip1 = global_int_ptr; // expected-warning{{incompatible pointer types initializing 'int *' with an expression of type 'double (*)(int, int)'}} + + int *ip2 = global_int_fun( // expected-warning{{incompatible pointer types initializing 'int *' with an expression of type 'char *'}} + ip2, // expected-warning{{incompatible pointer types passing 'int *' to parameter of type 'double *'}} + ip2); // expected-warning{{incompatible pointer types passing 'int *' to parameter of type 'float *'}} + + int *ip3 = [overridden // expected-warning{{incompatible pointer types initializing 'int *' with an expression of type 'char *'}} + methodToMangle: ip3 // expected-warning{{incompatible pointer types sending 'int *' to parameter of type 'double *'}} + second: ip3]; // expected-warning{{incompatible pointer types sending 'int *' to parameter of type 'float *'}} + + int *ip4 = overridden.intPropertyToMangle; // expected-warning{{incompatible pointer types initializing 'int *' with an expression of type 'double *'}} +} + +// expected-note@SomeKit/SomeKit.h:42{{passing argument to parameter 'ptr' here}} +// expected-note@SomeKit/SomeKit.h:42{{passing argument to parameter 'ptr2' here}} +// expected-note@SomeKit/SomeKit.h:48{{passing argument to parameter 'ptr1' here}} +// expected-note@SomeKit/SomeKit.h:48{{passing argument to parameter 'ptr2' here}} From 7159fb5b2e75e90efe3782f44a727b0c1d1294c1 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 29 Nov 2016 11:22:57 +0000 Subject: [PATCH 118/585] Add -Wapi-notes-message diagnostic group rdar://8104080 apple-llvm-split-commit: d9265c52ae59472204e0ba0ea176ea727dfd24fa apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticCommonKinds.td | 2 +- clang/test/Misc/warning-flags.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index b9f28a747942f..14715fee6820f 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -222,7 +222,7 @@ def err_arcmt_nsinvocation_ownership : Error<"NSInvocation's %0 is not safe to b // API notes def err_apinotes_message : Error<"%0">; -def warn_apinotes_message : Warning<"%0">; +def warn_apinotes_message : Warning<"%0">, InGroup>; def note_apinotes_message : Note<"%0">; // OpenMP diff --git a/clang/test/Misc/warning-flags.c b/clang/test/Misc/warning-flags.c index b756ec5ffd28d..c5294cff8fff0 100644 --- a/clang/test/Misc/warning-flags.c +++ b/clang/test/Misc/warning-flags.c @@ -18,7 +18,7 @@ This test serves two purposes: The list of warnings below should NEVER grow. It should gradually shrink to 0. -CHECK: Warnings without flags (83): +CHECK: Warnings without flags (82): CHECK-NEXT: ext_excess_initializers CHECK-NEXT: ext_excess_initializers_in_char_array_initializer CHECK-NEXT: ext_expected_semi_decl_list @@ -39,7 +39,6 @@ CHECK-NEXT: pp_out_of_date_dependency CHECK-NEXT: pp_poisoning_existing_macro CHECK-NEXT: w_asm_qualifier_ignored CHECK-NEXT: warn_accessor_property_type_mismatch -CHECK-NEXT: warn_apinotes_message CHECK-NEXT: warn_arcmt_nsalloc_realloc CHECK-NEXT: warn_asm_label_on_auto_decl CHECK-NEXT: warn_c_kext From 272f5793699a4e8bb0b78db759d5b07c9e33e587 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 29 Nov 2016 11:44:26 +0000 Subject: [PATCH 119/585] Revert "Add -Wapi-notes-message diagnostic group" This reverts commit d9265c52ae59472204e0ba0ea176ea727dfd24fa. The commit message used the wrong name for the new warning group. apple-llvm-split-commit: 2d7d7b2516e13de7b9c4a650644f573677e12611 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticCommonKinds.td | 2 +- clang/test/Misc/warning-flags.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index 14715fee6820f..b9f28a747942f 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -222,7 +222,7 @@ def err_arcmt_nsinvocation_ownership : Error<"NSInvocation's %0 is not safe to b // API notes def err_apinotes_message : Error<"%0">; -def warn_apinotes_message : Warning<"%0">, InGroup>; +def warn_apinotes_message : Warning<"%0">; def note_apinotes_message : Note<"%0">; // OpenMP diff --git a/clang/test/Misc/warning-flags.c b/clang/test/Misc/warning-flags.c index c5294cff8fff0..b756ec5ffd28d 100644 --- a/clang/test/Misc/warning-flags.c +++ b/clang/test/Misc/warning-flags.c @@ -18,7 +18,7 @@ This test serves two purposes: The list of warnings below should NEVER grow. It should gradually shrink to 0. -CHECK: Warnings without flags (82): +CHECK: Warnings without flags (83): CHECK-NEXT: ext_excess_initializers CHECK-NEXT: ext_excess_initializers_in_char_array_initializer CHECK-NEXT: ext_expected_semi_decl_list @@ -39,6 +39,7 @@ CHECK-NEXT: pp_out_of_date_dependency CHECK-NEXT: pp_poisoning_existing_macro CHECK-NEXT: w_asm_qualifier_ignored CHECK-NEXT: warn_accessor_property_type_mismatch +CHECK-NEXT: warn_apinotes_message CHECK-NEXT: warn_arcmt_nsalloc_realloc CHECK-NEXT: warn_asm_label_on_auto_decl CHECK-NEXT: warn_c_kext From ee724f490a93d2c1a8e242d7e3f8ec08fbc57f1d Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 29 Nov 2016 11:22:57 +0000 Subject: [PATCH 120/585] Add -Wapinotes diagnostic group Recommit as the first commit mentioned outdated warning group name in the commit message. rdar://8104080 apple-llvm-split-commit: 6d14fd1cbde56d443076616e1ebcf250d71cdab5 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticCommonKinds.td | 2 +- clang/test/Misc/warning-flags.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index b9f28a747942f..14715fee6820f 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -222,7 +222,7 @@ def err_arcmt_nsinvocation_ownership : Error<"NSInvocation's %0 is not safe to b // API notes def err_apinotes_message : Error<"%0">; -def warn_apinotes_message : Warning<"%0">; +def warn_apinotes_message : Warning<"%0">, InGroup>; def note_apinotes_message : Note<"%0">; // OpenMP diff --git a/clang/test/Misc/warning-flags.c b/clang/test/Misc/warning-flags.c index b756ec5ffd28d..c5294cff8fff0 100644 --- a/clang/test/Misc/warning-flags.c +++ b/clang/test/Misc/warning-flags.c @@ -18,7 +18,7 @@ This test serves two purposes: The list of warnings below should NEVER grow. It should gradually shrink to 0. -CHECK: Warnings without flags (83): +CHECK: Warnings without flags (82): CHECK-NEXT: ext_excess_initializers CHECK-NEXT: ext_excess_initializers_in_char_array_initializer CHECK-NEXT: ext_expected_semi_decl_list @@ -39,7 +39,6 @@ CHECK-NEXT: pp_out_of_date_dependency CHECK-NEXT: pp_poisoning_existing_macro CHECK-NEXT: w_asm_qualifier_ignored CHECK-NEXT: warn_accessor_property_type_mismatch -CHECK-NEXT: warn_apinotes_message CHECK-NEXT: warn_arcmt_nsalloc_realloc CHECK-NEXT: warn_asm_label_on_auto_decl CHECK-NEXT: warn_c_kext From b99d56f576b1aa1333c68a34ccf3abfffc27c3c8 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 30 Nov 2016 17:22:03 -0800 Subject: [PATCH 121/585] [APINotes] Add missing include. (#49) rdar://problem/29449851 apple-llvm-split-commit: 0d6d326d289b46826d451cc38de8ad774265ad84 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index ebbd73e4e4aa0..832f454c6c897 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -25,6 +25,7 @@ #include "llvm/ADT/Hashing.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include From e9c940d506c33e9ad31dbab0c3581693131c6ab4 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Tue, 6 Dec 2016 17:28:36 -0800 Subject: [PATCH 122/585] Fix merge problem from clang r288295. John added the IsForDefinition argument to CGObjCNonFragileABIMac::GetClassGlobal on trunk. This merged badly with the Swift version of the code that previously had a boolean argument named "ForDefinition". In the new version "ForDefinition" is an enum value so this code did not fail to compile and only caused a test failure. rdar://problem/29538073 apple-llvm-split-commit: 6c8b2c7e393fd136eacabbfa6b190953a73aa90e apple-llvm-split-dir: clang/ --- clang/lib/CodeGen/CGObjCMac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp index 53ff137af5504..01793e2ac9d26 100644 --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -7148,7 +7148,7 @@ CGObjCNonFragileABIMac::GetClassGlobal(StringRef Name, assert(GV->getLinkage() == L); - if (ForDefinition || + if (IsForDefinition || GV->getValueType() == ObjCTypes.ClassnfABITy) return GV; From 09fd205a6c66c931418055bfb828c3af5faae705 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 8 Dec 2016 20:25:46 -0800 Subject: [PATCH 123/585] Re-apply "ObjectiveC Generics: Start using ObjCTypeParamType." (#51) This re-applies Manman's c7c63c5b9, reverted in 1859c189. rdar://problem/28824900 (orig. rdar://problem/24619481) apple-llvm-split-commit: 765e62603911c714ba3a0437d1afb4be22ed5158 apple-llvm-split-dir: clang/ --- clang/lib/AST/ASTContext.cpp | 5 +++ clang/lib/AST/DeclObjC.cpp | 8 +++-- clang/lib/AST/Type.cpp | 19 ++++++++--- clang/lib/Sema/SemaType.cpp | 21 ++++++++++++ .../Checkers/DynamicTypePropagation.cpp | 2 +- clang/test/SemaObjC/kindof.m | 10 +++++- .../SemaObjC/parameterized_classes_subst.m | 33 +++++++++++++++++++ 7 files changed, 90 insertions(+), 8 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index ac99a00bc7610..22be71a7272d6 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -4027,6 +4027,11 @@ ASTContext::applyObjCProtocolQualifiers(QualType type, bool allowOnPointerType) const { hasError = false; + if (const ObjCTypeParamType *objT = + dyn_cast(type.getTypePtr())) { + return getObjCTypeParamType(objT->getDecl(), protocols); + } + // Apply protocol qualifiers to ObjCObjectPointerType. if (allowOnPointerType) { if (const ObjCObjectPointerType *objPtr = diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp index 5d894c5ebc60c..60d05f682e6e0 100644 --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -1329,8 +1329,12 @@ ObjCTypeParamDecl *ObjCTypeParamDecl::Create(ASTContext &ctx, DeclContext *dc, IdentifierInfo *name, SourceLocation colonLoc, TypeSourceInfo *boundInfo) { - return new (ctx, dc) ObjCTypeParamDecl(ctx, dc, variance, varianceLoc, index, - nameLoc, name, colonLoc, boundInfo); + auto *TPDecl = + new (ctx, dc) ObjCTypeParamDecl(ctx, dc, variance, varianceLoc, index, + nameLoc, name, colonLoc, boundInfo); + QualType TPType = ctx.getObjCTypeParamType(TPDecl, {}); + TPDecl->setTypeForDecl(TPType.getTypePtr()); + return TPDecl; } ObjCTypeParamDecl *ObjCTypeParamDecl::CreateDeserialized(ASTContext &ctx, diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index f3724aa85ed87..0d0cd2e305be2 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -1081,13 +1081,24 @@ QualType QualType::substObjCTypeArgs( // Replace an Objective-C type parameter reference with the corresponding // type argument. - if (const auto *typedefTy = dyn_cast(splitType.Ty)) { - if (auto *typeParam = dyn_cast(typedefTy->getDecl())) { + if (const auto *OTPTy = dyn_cast(splitType.Ty)) { + if (auto *typeParam = dyn_cast(OTPTy->getDecl())) { // If we have type arguments, use them. if (!typeArgs.empty()) { - // FIXME: Introduce SubstObjCTypeParamType ? QualType argType = typeArgs[typeParam->getIndex()]; - return ctx.getQualifiedType(argType, splitType.Quals); + if (OTPTy->qual_empty()) + return ctx.getQualifiedType(argType, splitType.Quals); + + // Apply protocol lists if exists. + bool hasError; + SmallVector protocolsVec; + protocolsVec.append(OTPTy->qual_begin(), + OTPTy->qual_end()); + ArrayRef protocolsToApply = protocolsVec; + QualType resultTy = ctx.applyObjCProtocolQualifiers(argType, + protocolsToApply, hasError, true/*allowOnPointerType*/); + + return ctx.getQualifiedType(resultTy, splitType.Quals); } switch (context) { diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index ed9cbb6733f5f..01b8b3a70d401 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -1158,6 +1158,20 @@ TypeResult Sema::actOnObjCTypeArgsAndProtocolQualifiers( ResultTL = ObjCObjectPointerTL.getPointeeLoc(); } + if (auto OTPTL = ResultTL.getAs()) { + // Protocol qualifier information. + if (OTPTL.getNumProtocols() > 0) { + assert(OTPTL.getNumProtocols() == Protocols.size()); + OTPTL.setProtocolLAngleLoc(ProtocolLAngleLoc); + OTPTL.setProtocolRAngleLoc(ProtocolRAngleLoc); + for (unsigned i = 0, n = Protocols.size(); i != n; ++i) + OTPTL.setProtocolLoc(i, ProtocolLocs[i]); + } + + // We're done. Return the completed type to the parser. + return CreateParsedType(Result, ResultTInfo); + } + auto ObjCObjectTL = ResultTL.castAs(); // Type argument information. @@ -6108,6 +6122,13 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &type, } bool Sema::checkObjCKindOfType(QualType &type, SourceLocation loc) { + if (isa(type)) { + // Build the attributed type to record where __kindof occurred. + type = Context.getAttributedType(AttributedType::attr_objc_kindof, + type, type); + return false; + } + // Find out if it's an Objective-C object or object pointer type; const ObjCObjectPointerType *ptrType = type->getAs(); const ObjCObjectType *objType = ptrType ? ptrType->getObjectType() diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index 301a5ce7d5829..a418c82f5a017 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -626,7 +626,7 @@ static bool isObjCTypeParamDependent(QualType Type) { : public RecursiveASTVisitor { public: IsObjCTypeParamDependentTypeVisitor() : Result(false) {} - bool VisitTypedefType(const TypedefType *Type) { + bool VisitObjCTypeParamType(const ObjCTypeParamType *Type) { if (isa(Type->getDecl())) { Result = true; return false; diff --git a/clang/test/SemaObjC/kindof.m b/clang/test/SemaObjC/kindof.m index 63ba18fe89bc0..9d758d3cfb2a6 100644 --- a/clang/test/SemaObjC/kindof.m +++ b/clang/test/SemaObjC/kindof.m @@ -385,7 +385,7 @@ - (void)test:(id)T { @end @interface NSGeneric : NSObject -- (void)test:(__kindof ObjectType)T; +- (void)test:(__kindof ObjectType)T; // expected-note{{passing argument to parameter 'T' here}} - (void)mapUsingBlock:(id (^)(__kindof ObjectType))block; @end @implementation NSGeneric @@ -395,6 +395,14 @@ - (void)mapUsingBlock:(id (^)(id))block { } @end +void testGeneric(NSGeneric *generic) { + NSObject *NSObject_obj; + // Assign from NSObject_obj to __kindof NSString*. + [generic test:NSObject_obj]; // expected-warning{{incompatible pointer types sending 'NSObject *' to parameter of type '__kindof NSString *'}} + NSString *NSString_str; + [generic test:NSString_str]; +} + // Check that clang doesn't crash when a type parameter is illegal. @interface Array1 : NSObject @end diff --git a/clang/test/SemaObjC/parameterized_classes_subst.m b/clang/test/SemaObjC/parameterized_classes_subst.m index f90ee9093592c..da2d56f11bc80 100644 --- a/clang/test/SemaObjC/parameterized_classes_subst.m +++ b/clang/test/SemaObjC/parameterized_classes_subst.m @@ -426,3 +426,36 @@ + (void)useSuperMethod { // warning about likely protocol/class name typos. // -------------------------------------------------------------------------- typedef NSArray ArrayOfNSObjectWarning; // expected-warning{{parameterized class 'NSArray' already conforms to the protocols listed; did you forget a '*'?}} + +// rdar://25060179 +@interface MyMutableDictionary : NSObject +- (void)setObject:(ObjectType)obj forKeyedSubscript:(KeyType )key; // expected-note{{passing argument to parameter 'obj' here}} \ + // expected-note{{passing argument to parameter 'key' here}} +@end + +void bar(MyMutableDictionary *stringsByString, + NSNumber *n1, NSNumber *n2) { + // We warn here when the key types do not match. + stringsByString[n1] = n2; // expected-warning{{incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'}} \ + // expected-warning{{incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'}} +} + +@interface MyTest : NSObject +- (V)test:(K)key; +- (V)test2:(K)key; // expected-note{{previous definition is here}} +- (void)mapUsingBlock:(id (^)(V))block; +- (void)mapUsingBlock2:(id (^)(V))block; // expected-note{{previous definition is here}} +@end + +@implementation MyTest +- (id)test:(id)key { + return key; +} +- (int)test2:(id)key{ // expected-warning{{conflicting return type in implementation}} + return 0; +} +- (void)mapUsingBlock:(id (^)(id))block { +} +- (void)mapUsingBlock2:(id)block { // expected-warning{{conflicting parameter types in implementation}} +} +@end From 83be4cf8ce2b12dddffd84b40403d8c043e6da50 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Tue, 13 Dec 2016 15:01:02 -0800 Subject: [PATCH 124/585] Fix MSVC errors building SemaAPINotes with MSVC (#54) apple-llvm-split-commit: 99c41b42e6d0bfcf53c19f029842e78ea296fced apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaAPINotes.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index cf88f735018a7..6173661d4ec19 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -131,8 +131,7 @@ namespace { Sema &S, Decl *D, bool shouldAddAttribute, VersionedInfoRole role, llvm::function_ref createAttr, - llvm::function_ref(Decl *)> getExistingAttr = - [](Decl *decl) { return decl->specific_attr_begin(); }) { + llvm::function_ref(Decl *)> getExistingAttr) { switch (role) { case VersionedInfoRole::AugmentSource: // If we're not adding an attribute, there's nothing to do. @@ -168,6 +167,17 @@ namespace { break; } } + + template + void handleAPINotedAttribute( + Sema &S, Decl *D, bool shouldAddAttribute, + VersionedInfoRole role, + llvm::function_ref createAttr) { + handleAPINotedAttribute(S, D, shouldAddAttribute, role, createAttr, + [](Decl *decl) { + return decl->specific_attr_begin(); + }); + } } static void ProcessAPINotes(Sema &S, Decl *D, From 841c57d6274e7d09aed4a2bf6dfaf4b2ad1b2557 Mon Sep 17 00:00:00 2001 From: Vedant Kumar Date: Tue, 13 Dec 2016 17:29:36 -0800 Subject: [PATCH 125/585] [profile] Add support for the exit-on-signal '%Nx' specifier The exit-on-signal specifier tells the runtime to handle a signal by writing out a profile and then exiting the program. It can be specified in the LLVM_PROFILE_FILE environment variable or by calling __llvm_profile_set_filename. Here is how you might force a program to exit when it's sent SIGTERM (sig 15): LLVM_PROFILE_FILE="default.profraw%15x" When an instance of is sent SIGTERM, it will write out a profile to "default.profraw" (the specifier is stripped out of the filename) and exit with return code 0. It's possible to use multiple exit-on-signal specifiers (up to 16). rdar://problem/24098975 apple-llvm-split-commit: 2cf73dc0fd684e3127e0ec9f3f8c432ae8ce7022 apple-llvm-split-dir: compiler-rt/ --- compiler-rt/lib/profile/InstrProfilingFile.c | 70 +++++++++++++++++-- .../test/profile/instrprof-exit-on-signal.c | 16 +++++ 2 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 compiler-rt/test/profile/instrprof-exit-on-signal.c diff --git a/compiler-rt/lib/profile/InstrProfilingFile.c b/compiler-rt/lib/profile/InstrProfilingFile.c index f82080c98aac4..65393c74690ec 100644 --- a/compiler-rt/lib/profile/InstrProfilingFile.c +++ b/compiler-rt/lib/profile/InstrProfilingFile.c @@ -14,6 +14,7 @@ #include #include #include +#include #ifdef _MSC_VER /* For _alloca. */ #include @@ -59,6 +60,7 @@ static const char *getPNSStr(ProfileNameSpecifier PNS) { } #define MAX_PID_SIZE 16 +#define MAX_SIGNAL_HANDLERS 16 /* Data structure holding the result of parsed filename pattern. */ typedef struct lprofFilename { /* File name string possibly with %p or %h specifiers. */ @@ -79,11 +81,13 @@ typedef struct lprofFilename { * 2 profile data files. %1m is equivalent to %m. Also %m specifier * can only appear once at the end of the name pattern. */ unsigned MergePoolSize; + char ExitOnSignals[MAX_SIGNAL_HANDLERS]; + unsigned NumExitSignals; ProfileNameSpecifier PNS; } lprofFilename; -COMPILER_RT_WEAK lprofFilename lprofCurFilename = {0, 0, 0, {0}, {0}, - 0, 0, 0, PNS_unknown}; +COMPILER_RT_WEAK lprofFilename lprofCurFilename = { + 0, 0, 0, {0}, {0}, 0, 0, 0, {0}, 0, PNS_unknown}; int getpid(void); static int getCurFilenameLength(); @@ -252,6 +256,26 @@ static void truncateCurrentFile(void) { fclose(File); } +static void exitSignalHandler(int sig) { + (void)sig; + exit(0); +} + +static void installExitSignalHandlers(void) { + unsigned I; + struct sigaction sigact; + int err; + for (I = 0; I < lprofCurFilename.NumExitSignals; ++I) { + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_handler = exitSignalHandler; + err = sigaction(lprofCurFilename.ExitOnSignals[I], &sigact, NULL); + if (err) + PROF_WARN( + "Unable to install an exit signal handler for %d (errno = %d).\n", + lprofCurFilename.ExitOnSignals[I], err); + } +} + static const char *DefaultProfileName = "default.profraw"; static void resetFilenameToDefault(void) { if (lprofCurFilename.FilenamePat && lprofCurFilename.OwnsFilenamePat) { @@ -262,14 +286,23 @@ static void resetFilenameToDefault(void) { lprofCurFilename.PNS = PNS_default; } +static int isNonZeroDigit(char C) { return C >= '1' && C <= '9'; } + static int containsMergeSpecifier(const char *FilenamePat, int I) { return (FilenamePat[I] == 'm' || - (FilenamePat[I] >= '1' && FilenamePat[I] <= '9' && + (isNonZeroDigit(FilenamePat[I]) && /* If FilenamePat[I] is not '\0', the next byte is guaranteed * to be in-bound as the string is null terminated. */ FilenamePat[I + 1] == 'm')); } +static int containsExitOnSignalSpecifier(const char *FilenamePat, int I) { + if (!isNonZeroDigit(FilenamePat[I])) + return 0; + return (FilenamePat[I + 1] == 'x') || + (isNonZeroDigit(FilenamePat[I + 1]) && FilenamePat[I + 2] == 'x'); +} + /* Parses the pattern string \p FilenamePat and stores the result to * lprofcurFilename structure. */ static int parseFilenamePattern(const char *FilenamePat, @@ -278,6 +311,7 @@ static int parseFilenamePattern(const char *FilenamePat, char *PidChars = &lprofCurFilename.PidChars[0]; char *Hostname = &lprofCurFilename.Hostname[0]; int MergingEnabled = 0; + char SignalNo; /* Clean up cached prefix. */ if (lprofCurFilename.ProfilePathPrefix) @@ -327,6 +361,22 @@ static int parseFilenamePattern(const char *FilenamePat, lprofCurFilename.MergePoolSize = FilenamePat[I] - '0'; I++; /* advance to 'm' */ } + } else if (containsExitOnSignalSpecifier(FilenamePat, I)) { + if (lprofCurFilename.NumExitSignals == MAX_SIGNAL_HANDLERS) { + PROF_WARN("%%x specifier has been specified too many times in %s.\n", + FilenamePat); + return -1; + } + /* Grab the signal number. */ + SignalNo = FilenamePat[I] - '0'; + I++; /* advance to either another digit, or 'x' */ + if (FilenamePat[I] != 'x') { + SignalNo = (SignalNo * 10) + (FilenamePat[I] - '0'); + I++; /* advance to 'x' */ + } + lprofCurFilename.ExitOnSignals[lprofCurFilename.NumExitSignals] = + SignalNo; + ++lprofCurFilename.NumExitSignals; } } @@ -370,6 +420,7 @@ static void parseAndSetFilename(const char *FilenamePat, } truncateCurrentFile(); + installExitSignalHandlers(); } /* Return buffer length that is required to store the current profile @@ -378,11 +429,12 @@ static void parseAndSetFilename(const char *FilenamePat, #define SIGLEN 24 static int getCurFilenameLength() { int Len; + unsigned I; if (!lprofCurFilename.FilenamePat || !lprofCurFilename.FilenamePat[0]) return 0; if (!(lprofCurFilename.NumPids || lprofCurFilename.NumHosts || - lprofCurFilename.MergePoolSize)) + lprofCurFilename.MergePoolSize || lprofCurFilename.NumExitSignals)) return strlen(lprofCurFilename.FilenamePat); Len = strlen(lprofCurFilename.FilenamePat) + @@ -390,6 +442,11 @@ static int getCurFilenameLength() { lprofCurFilename.NumHosts * (strlen(lprofCurFilename.Hostname) - 2); if (lprofCurFilename.MergePoolSize) Len += SIGLEN; + for (I = 0; I < lprofCurFilename.NumExitSignals; ++I) { + Len -= 3; /* Drop the '%', signal number, and the 'x'. */ + if (lprofCurFilename.ExitOnSignals[I] >= 10) + --Len; /* Drop the second digit of the signal number. */ + } return Len; } @@ -405,7 +462,7 @@ static const char *getCurFilename(char *FilenameBuf) { return 0; if (!(lprofCurFilename.NumPids || lprofCurFilename.NumHosts || - lprofCurFilename.MergePoolSize)) + lprofCurFilename.MergePoolSize || lprofCurFilename.NumExitSignals)) return lprofCurFilename.FilenamePat; PidLength = strlen(lprofCurFilename.PidChars); @@ -431,6 +488,9 @@ static const char *getCurFilename(char *FilenameBuf) { J += S; if (FilenamePat[I] != 'm') I++; + } else if (containsExitOnSignalSpecifier(FilenamePat, I)) { + while (FilenamePat[I] != 'x') + ++I; } /* Drop any unknown substitutions. */ } else diff --git a/compiler-rt/test/profile/instrprof-exit-on-signal.c b/compiler-rt/test/profile/instrprof-exit-on-signal.c new file mode 100644 index 0000000000000..cf23b68003b80 --- /dev/null +++ b/compiler-rt/test/profile/instrprof-exit-on-signal.c @@ -0,0 +1,16 @@ +// RUN: %clang_profgen -o %t %s +// RUN: %run LLVM_PROFILE_FILE="%15x%t.profraw" %t +// RUN: llvm-profdata show --all-functions %t.profraw | FileCheck %s +// CHECK: Total functions: 1 + +#include +#include + +int main() { + kill(getpid(), SIGTERM); + + while (1) { + /* loop forever */ + } + return 1; +} From f8862167e93f65f3ea47e244892efa8a581c2925 Mon Sep 17 00:00:00 2001 From: Vedant Kumar Date: Tue, 13 Dec 2016 17:44:32 -0800 Subject: [PATCH 126/585] [profile] Fix bug in exit-on-signal specifier parsing The parsing logic for the '%Nx' specifier had a bug which made it impossible to specify handling of two-digit signals where the second digit is 0. Fix it and add a test. rdar://problem/24098975 apple-llvm-split-commit: 29107a0e2baacf42e4f4baa0a83d557a3fc7aa88 apple-llvm-split-dir: compiler-rt/ --- compiler-rt/lib/profile/InstrProfilingFile.c | 4 +++- .../test/profile/instrprof-exit-on-signal.c | 20 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/compiler-rt/lib/profile/InstrProfilingFile.c b/compiler-rt/lib/profile/InstrProfilingFile.c index 65393c74690ec..9a11a10c36d94 100644 --- a/compiler-rt/lib/profile/InstrProfilingFile.c +++ b/compiler-rt/lib/profile/InstrProfilingFile.c @@ -286,6 +286,8 @@ static void resetFilenameToDefault(void) { lprofCurFilename.PNS = PNS_default; } +static int isDigit(char C) { return C >= '0' && C <= '9'; } + static int isNonZeroDigit(char C) { return C >= '1' && C <= '9'; } static int containsMergeSpecifier(const char *FilenamePat, int I) { @@ -300,7 +302,7 @@ static int containsExitOnSignalSpecifier(const char *FilenamePat, int I) { if (!isNonZeroDigit(FilenamePat[I])) return 0; return (FilenamePat[I + 1] == 'x') || - (isNonZeroDigit(FilenamePat[I + 1]) && FilenamePat[I + 2] == 'x'); + (isDigit(FilenamePat[I + 1]) && FilenamePat[I + 2] == 'x'); } /* Parses the pattern string \p FilenamePat and stores the result to diff --git a/compiler-rt/test/profile/instrprof-exit-on-signal.c b/compiler-rt/test/profile/instrprof-exit-on-signal.c index cf23b68003b80..efe1013f4ba28 100644 --- a/compiler-rt/test/profile/instrprof-exit-on-signal.c +++ b/compiler-rt/test/profile/instrprof-exit-on-signal.c @@ -1,13 +1,23 @@ // RUN: %clang_profgen -o %t %s -// RUN: %run LLVM_PROFILE_FILE="%15x%t.profraw" %t -// RUN: llvm-profdata show --all-functions %t.profraw | FileCheck %s -// CHECK: Total functions: 1 +// +// Verify SIGTERM handling. +// RUN: %run LLVM_PROFILE_FILE="%15x%t.profraw" %t 15 +// RUN: llvm-profdata show %t.profraw | FileCheck %s +// +// Verify SIGUSR1 handling. +// RUN: %run LLVM_PROFILE_FILE="%30x%t.profraw" %t 30 +// RUN: llvm-profdata show %t.profraw | FileCheck %s +#include #include #include -int main() { - kill(getpid(), SIGTERM); +// CHECK: Total functions: 1 +int main(int argc, char **argv) { + (void)argc; + + int sig = atoi(argv[1]); + kill(getpid(), sig); while (1) { /* loop forever */ From 57046a8edb49919a05a53c19a33a489ac60e7ce7 Mon Sep 17 00:00:00 2001 From: Hugh Bellamy Date: Thu, 15 Dec 2016 21:11:25 +0000 Subject: [PATCH 127/585] Fix warnings building Swift with swift-llvm with MSVC (#39) MSVC warns: '>=' unsafe use of type 'bool' in operation. apple-llvm-split-commit: facb9960a193c19cd155ca9ec63151cc5bae0453 apple-llvm-split-dir: llvm/ --- llvm/include/llvm/Bitcode/RecordLayout.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/llvm/include/llvm/Bitcode/RecordLayout.h b/llvm/include/llvm/Bitcode/RecordLayout.h index ce6c7f8bd869d..2b694753d3513 100644 --- a/llvm/include/llvm/Bitcode/RecordLayout.h +++ b/llvm/include/llvm/Bitcode/RecordLayout.h @@ -97,6 +97,11 @@ class BCFixed : public impl::BCField<> { abbrev.Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, Width)); } + static void assertValid(const bool &data) { + assert(llvm::isUInt(data) && + "data value does not fit in the given bit width"); + } + template static void assertValid(const T &data) { assert(data >= 0 && "cannot encode signed integers"); From a270e653f7debed0940b880811d002ddc0ed586c Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Tue, 20 Dec 2016 17:51:02 -0800 Subject: [PATCH 128/585] Re-revert "ObjectiveC Generics: Start using ObjCTypeParamType." (#56) Reverts 765e626, originally c7c63c5. The logic isn't quite correct around __kindof types. See rdar://problem/24619481 and rdar://problem/28824900. apple-llvm-split-commit: af52d9b3ae6296e34992cb563d18264e1a102cc7 apple-llvm-split-dir: clang/ --- clang/lib/AST/ASTContext.cpp | 5 --- clang/lib/AST/DeclObjC.cpp | 8 ++--- clang/lib/AST/Type.cpp | 19 +++-------- clang/lib/Sema/SemaType.cpp | 21 ------------ .../Checkers/DynamicTypePropagation.cpp | 2 +- clang/test/SemaObjC/kindof.m | 10 +----- .../SemaObjC/parameterized_classes_subst.m | 33 ------------------- 7 files changed, 8 insertions(+), 90 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 4abca4ce5d1a8..79a0ed9302ffe 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -4038,11 +4038,6 @@ ASTContext::applyObjCProtocolQualifiers(QualType type, bool allowOnPointerType) const { hasError = false; - if (const ObjCTypeParamType *objT = - dyn_cast(type.getTypePtr())) { - return getObjCTypeParamType(objT->getDecl(), protocols); - } - // Apply protocol qualifiers to ObjCObjectPointerType. if (allowOnPointerType) { if (const ObjCObjectPointerType *objPtr = diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp index 60d05f682e6e0..5d894c5ebc60c 100644 --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -1329,12 +1329,8 @@ ObjCTypeParamDecl *ObjCTypeParamDecl::Create(ASTContext &ctx, DeclContext *dc, IdentifierInfo *name, SourceLocation colonLoc, TypeSourceInfo *boundInfo) { - auto *TPDecl = - new (ctx, dc) ObjCTypeParamDecl(ctx, dc, variance, varianceLoc, index, - nameLoc, name, colonLoc, boundInfo); - QualType TPType = ctx.getObjCTypeParamType(TPDecl, {}); - TPDecl->setTypeForDecl(TPType.getTypePtr()); - return TPDecl; + return new (ctx, dc) ObjCTypeParamDecl(ctx, dc, variance, varianceLoc, index, + nameLoc, name, colonLoc, boundInfo); } ObjCTypeParamDecl *ObjCTypeParamDecl::CreateDeserialized(ASTContext &ctx, diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 0d0cd2e305be2..f3724aa85ed87 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -1081,24 +1081,13 @@ QualType QualType::substObjCTypeArgs( // Replace an Objective-C type parameter reference with the corresponding // type argument. - if (const auto *OTPTy = dyn_cast(splitType.Ty)) { - if (auto *typeParam = dyn_cast(OTPTy->getDecl())) { + if (const auto *typedefTy = dyn_cast(splitType.Ty)) { + if (auto *typeParam = dyn_cast(typedefTy->getDecl())) { // If we have type arguments, use them. if (!typeArgs.empty()) { + // FIXME: Introduce SubstObjCTypeParamType ? QualType argType = typeArgs[typeParam->getIndex()]; - if (OTPTy->qual_empty()) - return ctx.getQualifiedType(argType, splitType.Quals); - - // Apply protocol lists if exists. - bool hasError; - SmallVector protocolsVec; - protocolsVec.append(OTPTy->qual_begin(), - OTPTy->qual_end()); - ArrayRef protocolsToApply = protocolsVec; - QualType resultTy = ctx.applyObjCProtocolQualifiers(argType, - protocolsToApply, hasError, true/*allowOnPointerType*/); - - return ctx.getQualifiedType(resultTy, splitType.Quals); + return ctx.getQualifiedType(argType, splitType.Quals); } switch (context) { diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index aedbe39afbffb..9ee6c7c4cff32 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -1159,20 +1159,6 @@ TypeResult Sema::actOnObjCTypeArgsAndProtocolQualifiers( ResultTL = ObjCObjectPointerTL.getPointeeLoc(); } - if (auto OTPTL = ResultTL.getAs()) { - // Protocol qualifier information. - if (OTPTL.getNumProtocols() > 0) { - assert(OTPTL.getNumProtocols() == Protocols.size()); - OTPTL.setProtocolLAngleLoc(ProtocolLAngleLoc); - OTPTL.setProtocolRAngleLoc(ProtocolRAngleLoc); - for (unsigned i = 0, n = Protocols.size(); i != n; ++i) - OTPTL.setProtocolLoc(i, ProtocolLocs[i]); - } - - // We're done. Return the completed type to the parser. - return CreateParsedType(Result, ResultTInfo); - } - auto ObjCObjectTL = ResultTL.castAs(); // Type argument information. @@ -6119,13 +6105,6 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &type, } bool Sema::checkObjCKindOfType(QualType &type, SourceLocation loc) { - if (isa(type)) { - // Build the attributed type to record where __kindof occurred. - type = Context.getAttributedType(AttributedType::attr_objc_kindof, - type, type); - return false; - } - // Find out if it's an Objective-C object or object pointer type; const ObjCObjectPointerType *ptrType = type->getAs(); const ObjCObjectType *objType = ptrType ? ptrType->getObjectType() diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index a418c82f5a017..301a5ce7d5829 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -626,7 +626,7 @@ static bool isObjCTypeParamDependent(QualType Type) { : public RecursiveASTVisitor { public: IsObjCTypeParamDependentTypeVisitor() : Result(false) {} - bool VisitObjCTypeParamType(const ObjCTypeParamType *Type) { + bool VisitTypedefType(const TypedefType *Type) { if (isa(Type->getDecl())) { Result = true; return false; diff --git a/clang/test/SemaObjC/kindof.m b/clang/test/SemaObjC/kindof.m index 9d758d3cfb2a6..63ba18fe89bc0 100644 --- a/clang/test/SemaObjC/kindof.m +++ b/clang/test/SemaObjC/kindof.m @@ -385,7 +385,7 @@ - (void)test:(id)T { @end @interface NSGeneric : NSObject -- (void)test:(__kindof ObjectType)T; // expected-note{{passing argument to parameter 'T' here}} +- (void)test:(__kindof ObjectType)T; - (void)mapUsingBlock:(id (^)(__kindof ObjectType))block; @end @implementation NSGeneric @@ -395,14 +395,6 @@ - (void)mapUsingBlock:(id (^)(id))block { } @end -void testGeneric(NSGeneric *generic) { - NSObject *NSObject_obj; - // Assign from NSObject_obj to __kindof NSString*. - [generic test:NSObject_obj]; // expected-warning{{incompatible pointer types sending 'NSObject *' to parameter of type '__kindof NSString *'}} - NSString *NSString_str; - [generic test:NSString_str]; -} - // Check that clang doesn't crash when a type parameter is illegal. @interface Array1 : NSObject @end diff --git a/clang/test/SemaObjC/parameterized_classes_subst.m b/clang/test/SemaObjC/parameterized_classes_subst.m index da2d56f11bc80..f90ee9093592c 100644 --- a/clang/test/SemaObjC/parameterized_classes_subst.m +++ b/clang/test/SemaObjC/parameterized_classes_subst.m @@ -426,36 +426,3 @@ + (void)useSuperMethod { // warning about likely protocol/class name typos. // -------------------------------------------------------------------------- typedef NSArray ArrayOfNSObjectWarning; // expected-warning{{parameterized class 'NSArray' already conforms to the protocols listed; did you forget a '*'?}} - -// rdar://25060179 -@interface MyMutableDictionary : NSObject -- (void)setObject:(ObjectType)obj forKeyedSubscript:(KeyType )key; // expected-note{{passing argument to parameter 'obj' here}} \ - // expected-note{{passing argument to parameter 'key' here}} -@end - -void bar(MyMutableDictionary *stringsByString, - NSNumber *n1, NSNumber *n2) { - // We warn here when the key types do not match. - stringsByString[n1] = n2; // expected-warning{{incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'}} \ - // expected-warning{{incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'}} -} - -@interface MyTest : NSObject -- (V)test:(K)key; -- (V)test2:(K)key; // expected-note{{previous definition is here}} -- (void)mapUsingBlock:(id (^)(V))block; -- (void)mapUsingBlock2:(id (^)(V))block; // expected-note{{previous definition is here}} -@end - -@implementation MyTest -- (id)test:(id)key { - return key; -} -- (int)test2:(id)key{ // expected-warning{{conflicting return type in implementation}} - return 0; -} -- (void)mapUsingBlock:(id (^)(id))block { -} -- (void)mapUsingBlock2:(id)block { // expected-warning{{conflicting parameter types in implementation}} -} -@end From f2a711ea5503a17b143a04affb55159d970ab125 Mon Sep 17 00:00:00 2001 From: Frederic Riss Date: Fri, 23 Dec 2016 10:19:22 -0800 Subject: [PATCH 129/585] Fix previous merge. apple-llvm-split-commit: fd687d16d0bdc786f8d5495f7254a896dfe0a21b apple-llvm-split-dir: llvm/ --- llvm/lib/Bitcode/Reader/MetadataLoader.cpp | 50 +++++++--------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/llvm/lib/Bitcode/Reader/MetadataLoader.cpp b/llvm/lib/Bitcode/Reader/MetadataLoader.cpp index 75fc14c755769..501efec7e9e89 100644 --- a/llvm/lib/Bitcode/Reader/MetadataLoader.cpp +++ b/llvm/lib/Bitcode/Reader/MetadataLoader.cpp @@ -573,40 +573,6 @@ Error MetadataLoader::MetadataLoaderImpl::parseOneMetadata( return error("Invalid record"); NMD->addOperand(MD); } - case bitc::METADATA_GENERIC_DEBUG: { - if (Record.size() < 4) - return error("Invalid record"); - - IsDistinct = Record[0]; - unsigned Tag = Record[1]; - unsigned Version = Record[2]; - - if (Tag >= 1u << 16 || Version != 0) - return error("Invalid record"); - - // Deprecated internal hack to support serializing MDModule. - // This node has since been deleted. - // Upgrading this node is not officially supported. This code - // may be removed in the future. - if (Tag == dwarf::DW_TAG_module) { - if (Record.size() != 6) - return error("Invalid record"); - - MetadataList.assignValue( - GET_OR_DISTINCT(DIModule, (Context, getMDOrNull(Record[4]), - getMDString(Record[5]), nullptr, nullptr, - nullptr)), - NextMetadataNo++); - break; - } - - auto *Header = getMDString(Record[3]); - SmallVector DwarfOps; - for (unsigned I = 4, E = Record.size(); I != E; ++I) - DwarfOps.push_back(getMDOrNull(Record[I])); - MetadataList.assignValue( - GET_OR_DISTINCT(GenericDINode, (Context, Tag, Header, DwarfOps)), - NextMetadataNo++); break; } case bitc::METADATA_OLD_FN_NODE: { @@ -713,6 +679,22 @@ Error MetadataLoader::MetadataLoaderImpl::parseOneMetadata( if (Tag >= 1u << 16 || Version != 0) return error("Invalid record"); + // Deprecated internal hack to support serializing MDModule. + // This node has since been deleted. + // Upgrading this node is not officially supported. This code + // may be removed in the future. + if (Tag == dwarf::DW_TAG_module) { + if (Record.size() != 6) + return error("Invalid record"); + + MetadataList.assignValue( + GET_OR_DISTINCT(DIModule, (Context, getMDOrNull(Record[4]), + getMDString(Record[5]), nullptr, nullptr, + nullptr)), + NextMetadataNo++); + break; + } + auto *Header = getMDString(Record[3]); SmallVector DwarfOps; for (unsigned I = 4, E = Record.size(); I != E; ++I) From a2306672d2694cbc1ba35e53add37b285133916b Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Thu, 5 Jan 2017 14:04:00 +0000 Subject: [PATCH 130/585] Fix for LLVM Bitcode API change (r291016) This change is swift-llvm specific since RecordLayout.h isn't upstreamed. apple-llvm-split-commit: 6a7a3c612f8601f0e18d5bf1e0689ffd74cdfbf1 apple-llvm-split-dir: llvm/ --- llvm/include/llvm/Bitcode/RecordLayout.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llvm/include/llvm/Bitcode/RecordLayout.h b/llvm/include/llvm/Bitcode/RecordLayout.h index 2b694753d3513..c415eeb9d750a 100644 --- a/llvm/include/llvm/Bitcode/RecordLayout.h +++ b/llvm/include/llvm/Bitcode/RecordLayout.h @@ -427,9 +427,9 @@ class BCGenericRecordLayout { /// /// \returns The abbreviation code for the newly-registered record type. static unsigned emitAbbrev(llvm::BitstreamWriter &out) { - auto *abbrev = new llvm::BitCodeAbbrev(); - impl::emitOps(*abbrev); - return out.EmitAbbrev(abbrev); + auto Abbrev = std::make_shared(); + impl::emitOps(*Abbrev); + return out.EmitAbbrev(std::move(Abbrev)); } /// Emit a record identified by \p abbrCode to bitstream reader \p out, using From bca93adf92feaba3538aa4d985f307cc4cd34253 Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Mon, 9 Jan 2017 10:04:03 -0800 Subject: [PATCH 131/585] [index] Add Swift symbol language and Swift-specific symbol subkinds. apple-llvm-split-commit: a7af76c80be0a412ae3b136c065d04bd50a3efba apple-llvm-split-dir: clang/ --- clang/include/clang/Index/IndexSymbol.h | 23 ++++++++++++++++++++ clang/lib/Index/IndexSymbol.cpp | 17 +++++++++++++++ clang/tools/libclang/CXIndexDataConsumer.cpp | 1 + 3 files changed, 41 insertions(+) diff --git a/clang/include/clang/Index/IndexSymbol.h b/clang/include/clang/Index/IndexSymbol.h index 559b212b92668..92121759feaf7 100644 --- a/clang/include/clang/Index/IndexSymbol.h +++ b/clang/include/clang/Index/IndexSymbol.h @@ -57,6 +57,7 @@ enum class SymbolLanguage { C, ObjC, CXX, + Swift, }; /// Language specific sub-kinds. @@ -64,6 +65,28 @@ enum class SymbolSubKind { None, CXXCopyConstructor, CXXMoveConstructor, + + // Swift sub-kinds + + SwiftAccessorGetter, + SwiftAccessorSetter, + SwiftAccessorWillSet, + SwiftAccessorDidSet, + SwiftAccessorAddressor, + SwiftAccessorMutableAddressor, + + SwiftExtensionOfStruct, + SwiftExtensionOfClass, + SwiftExtensionOfEnum, + SwiftExtensionOfProtocol, + + SwiftPrefixOperator, + SwiftPostfixOperator, + SwiftInfixOperator, + + SwiftSubscript, + SwiftAssociatedType, + SwiftGenericTypeParam, }; /// Set of properties that provide additional info about a symbol. diff --git a/clang/lib/Index/IndexSymbol.cpp b/clang/lib/Index/IndexSymbol.cpp index be847e762091f..e60cfc4435aee 100644 --- a/clang/lib/Index/IndexSymbol.cpp +++ b/clang/lib/Index/IndexSymbol.cpp @@ -375,6 +375,22 @@ StringRef index::getSymbolSubKindString(SymbolSubKind K) { case SymbolSubKind::None: return ""; case SymbolSubKind::CXXCopyConstructor: return "cxx-copy-ctor"; case SymbolSubKind::CXXMoveConstructor: return "cxx-move-ctor"; + case SymbolSubKind::SwiftAccessorGetter: return "acc-get"; + case SymbolSubKind::SwiftAccessorSetter: return "acc-set"; + case SymbolSubKind::SwiftAccessorWillSet: return "acc-willset"; + case SymbolSubKind::SwiftAccessorDidSet: return "acc-didset"; + case SymbolSubKind::SwiftAccessorAddressor: return "acc-addr"; + case SymbolSubKind::SwiftAccessorMutableAddressor: return "acc-mutaddr"; + case SymbolSubKind::SwiftExtensionOfStruct: return "ext-struct"; + case SymbolSubKind::SwiftExtensionOfClass: return "ext-class"; + case SymbolSubKind::SwiftExtensionOfEnum: return "ext-enum"; + case SymbolSubKind::SwiftExtensionOfProtocol: return "ext-protocol"; + case SymbolSubKind::SwiftPrefixOperator: return "prefix-operator"; + case SymbolSubKind::SwiftPostfixOperator: return "postfix-operator"; + case SymbolSubKind::SwiftInfixOperator: return "infix-operator"; + case SymbolSubKind::SwiftSubscript: return "subscript"; + case SymbolSubKind::SwiftAssociatedType: return "associated-type"; + case SymbolSubKind::SwiftGenericTypeParam: return "generic-type-param"; } llvm_unreachable("invalid symbol subkind"); } @@ -384,6 +400,7 @@ StringRef index::getSymbolLanguageString(SymbolLanguage K) { case SymbolLanguage::C: return "C"; case SymbolLanguage::ObjC: return "ObjC"; case SymbolLanguage::CXX: return "C++"; + case SymbolLanguage::Swift: return "Swift"; } llvm_unreachable("invalid symbol language kind"); } diff --git a/clang/tools/libclang/CXIndexDataConsumer.cpp b/clang/tools/libclang/CXIndexDataConsumer.cpp index 1981cabbbe4c1..77a697bdb7c44 100644 --- a/clang/tools/libclang/CXIndexDataConsumer.cpp +++ b/clang/tools/libclang/CXIndexDataConsumer.cpp @@ -1313,6 +1313,7 @@ static CXIdxEntityLanguage getEntityLangFromSymbolLang(SymbolLanguage L) { case SymbolLanguage::C: return CXIdxEntityLang_C; case SymbolLanguage::ObjC: return CXIdxEntityLang_ObjC; case SymbolLanguage::CXX: return CXIdxEntityLang_CXX; + case SymbolLanguage::Swift: llvm_unreachable("unexpected Swift symbol language");; } llvm_unreachable("invalid symbol language"); } From 4653f82a995911fdeb15bc440917f4db2a447c04 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 11 Jan 2017 15:02:46 -0800 Subject: [PATCH 132/585] [APINotes] Preserve attributes from inactive versions. (#53) ...by wrapping them in another (new) attribute, SwiftVersionedAttr, or by noting their deletion in a SwiftVersionedRemovalAttr. This doesn't support other kinds of changes that can be made via API notes, such as nullability or wholesale type changes, but it's a place to start, and possibly sufficient for our goals. Part of rdar://problem/28618121. apple-llvm-split-commit: f8c16c2a4e80fedd9df549da57af8b4f8f09aef0 apple-llvm-split-dir: clang/ --- clang/include/clang/AST/Attr.h | 1 + clang/include/clang/Basic/Attr.td | 26 +++ clang/lib/Sema/SemaAPINotes.cpp | 233 ++++++++++++++-------- clang/test/APINotes/properties.m | 22 +- clang/test/APINotes/versioned.m | 13 +- clang/utils/TableGen/ClangAttrEmitter.cpp | 28 +++ 6 files changed, 231 insertions(+), 92 deletions(-) diff --git a/clang/include/clang/AST/Attr.h b/clang/include/clang/AST/Attr.h index bbe320c28a3b5..58566db60b292 100644 --- a/clang/include/clang/AST/Attr.h +++ b/clang/include/clang/AST/Attr.h @@ -24,6 +24,7 @@ #include "clang/Basic/Sanitizers.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/VersionTuple.h" +#include "llvm/ADT/PointerEmbeddedInt.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 2416ed6ac043e..a4a88d0fc5957 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -195,6 +195,9 @@ class VariadicEnumArgument values, list Enums = enums; } +// Represents an attribute wrapped by another attribute. +class AttrArgument : Argument; + // This handles one spelling of an attribute. class Spelling { string Name = name; @@ -1500,6 +1503,29 @@ def SwiftImportPropertyAsAccessors : InheritableAttr { let Documentation = [Undocumented]; } +def SwiftVersioned : Attr { + // This attribute has no spellings as it is only ever created implicitly + // from API notes. + let Spellings = []; + let Args = [VersionArgument<"Version">, AttrArgument<"AttrToAdd">]; + let SemaHandler = 0; + let Documentation = [Undocumented]; +} + +def SwiftVersionedRemoval : Attr { + // This attribute has no spellings as it is only ever created implicitly + // from API notes. + let Spellings = []; + let Args = [VersionArgument<"Version">, UnsignedArgument<"RawKind">]; + let SemaHandler = 0; + let Documentation = [Undocumented]; + let AdditionalMembers = [{ + attr::Kind getAttrKindToRemove() const { + return static_cast(getRawKind()); + } + }]; +} + def ReqdWorkGroupSize : InheritableAttr { let Spellings = [GNU<"reqd_work_group_size">]; let Args = [UnsignedArgument<"XDim">, UnsignedArgument<"YDim">, diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 6173661d4ec19..078445e48ad87 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -17,6 +17,21 @@ using namespace clang; using clang::api_notes::VersionedInfoRole; +namespace { + struct VersionedInfoMetadata { + VersionTuple Version; + VersionedInfoRole Role; + + /*implicit*/ VersionedInfoMetadata(VersionedInfoRole role) : Role(role) { + assert(role != VersionedInfoRole::Versioned && + "explicit version required"); + } + + /*implicit*/ VersionedInfoMetadata(VersionTuple version) + : Version(version), Role(VersionedInfoRole::Versioned) {} + }; +} // end anonymous namespace + /// Determine whether this is a multi-level pointer type. static bool isMultiLevelPointerType(QualType type) { QualType pointee = type->getPointeeType(); @@ -29,9 +44,9 @@ static bool isMultiLevelPointerType(QualType type) { // Apply nullability to the given declaration. static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { bool overrideExisting; - switch (role) { + switch (metadata.Role) { case VersionedInfoRole::AugmentSource: overrideExisting = false; break; @@ -119,20 +134,27 @@ static StringRef CopyString(ASTContext &ctx, StringRef string) { } namespace { + template + struct AttrKindFor {}; + +#define ATTR(X) \ + template <> struct AttrKindFor { \ + static const attr::Kind value = attr::X; \ + }; +#include "clang/Basic/AttrList.inc" + /// Handle an attribute introduced by API notes. /// /// \param shouldAddAttribute Whether we should add a new attribute /// (otherwise, we might remove an existing attribute). /// \param createAttr Create the new attribute to be added. - /// \param getExistingAttr Get an existing, matching attribute on the given - /// declaration. template void handleAPINotedAttribute( Sema &S, Decl *D, bool shouldAddAttribute, - VersionedInfoRole role, + VersionedInfoMetadata metadata, llvm::function_ref createAttr, - llvm::function_ref(Decl *)> getExistingAttr) { - switch (role) { + llvm::function_ref(Decl*)> getExistingAttr) { + switch (metadata.Role) { case VersionedInfoRole::AugmentSource: // If we're not adding an attribute, there's nothing to do. if (!shouldAddAttribute) return; @@ -149,8 +171,14 @@ namespace { auto end = D->specific_attr_end(); auto existing = getExistingAttr(D); if (existing != end) { - // Remove the existing attribute. + // Remove the existing attribute, and treat it as a superseded + // non-versioned attribute. + auto *versioned = + SwiftVersionedAttr::CreateImplicit(S.Context, clang::VersionTuple(), + *existing); + D->getAttrs().erase(existing.getCurrent()); + D->addAttr(versioned); } // If we're supposed to add a new attribute, do so. @@ -163,7 +191,24 @@ namespace { } case VersionedInfoRole::Versioned: - // FIXME: Retain versioned attributes separately. + // FIXME: Include the actual version instead of making one up. + if (shouldAddAttribute) { + if (auto attr = createAttr()) { + auto *versioned = + SwiftVersionedAttr::CreateImplicit(S.Context, metadata.Version, + attr); + D->addAttr(versioned); + } + } else { + // FIXME: This isn't preserving enough information for things like + // availability, where we're trying to remove a /specific/ kind of + // attribute. + auto *versioned = + SwiftVersionedRemovalAttr::CreateImplicit(S.Context, + metadata.Version, + AttrKindFor::value); + D->addAttr(versioned); + } break; } } @@ -171,9 +216,9 @@ namespace { template void handleAPINotedAttribute( Sema &S, Decl *D, bool shouldAddAttribute, - VersionedInfoRole role, + VersionedInfoMetadata metadata, llvm::function_ref createAttr) { - handleAPINotedAttribute(S, D, shouldAddAttribute, role, createAttr, + handleAPINotedAttribute(S, D, shouldAddAttribute, metadata, createAttr, [](Decl *decl) { return decl->specific_attr_begin(); }); @@ -182,10 +227,10 @@ namespace { static void ProcessAPINotes(Sema &S, Decl *D, const api_notes::CommonEntityInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Availability if (info.Unavailable) { - handleAPINotedAttribute(S, D, true, role, + handleAPINotedAttribute(S, D, true, metadata, [&] { return UnavailableAttr::CreateImplicit(S.Context, CopyString(S.Context, @@ -194,7 +239,7 @@ static void ProcessAPINotes(Sema &S, Decl *D, } if (info.UnavailableInSwift) { - handleAPINotedAttribute(S, D, true, role, [&] { + handleAPINotedAttribute(S, D, true, metadata, [&] { return AvailabilityAttr::CreateImplicit( S.Context, &S.Context.Idents.get("swift"), @@ -224,14 +269,15 @@ static void ProcessAPINotes(Sema &S, Decl *D, // swift_private if (auto swiftPrivate = info.isSwiftPrivate()) { - handleAPINotedAttribute(S, D, *swiftPrivate, role, [&] { + handleAPINotedAttribute(S, D, *swiftPrivate, metadata, + [&] { return SwiftPrivateAttr::CreateImplicit(S.Context); }); } // swift_name if (!info.SwiftName.empty()) { - handleAPINotedAttribute(S, D, true, role, + handleAPINotedAttribute(S, D, true, metadata, [&]() -> SwiftNameAttr * { auto &APINoteName = S.getASTContext().Idents.get("SwiftName API Note"); @@ -249,11 +295,11 @@ static void ProcessAPINotes(Sema &S, Decl *D, static void ProcessAPINotes(Sema &S, Decl *D, const api_notes::CommonTypeInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // swift_bridge if (auto swiftBridge = info.getSwiftBridge()) { - handleAPINotedAttribute(S, D, !swiftBridge->empty(), role, - [&] { + handleAPINotedAttribute(S, D, !swiftBridge->empty(), + metadata, [&] { return SwiftBridgeAttr::CreateImplicit(S.Context, CopyString(S.Context, *swiftBridge)); @@ -263,7 +309,7 @@ static void ProcessAPINotes(Sema &S, Decl *D, // ns_error_domain if (auto nsErrorDomain = info.getNSErrorDomain()) { handleAPINotedAttribute(S, D, !nsErrorDomain->empty(), - role, [&] { + metadata, [&] { return NSErrorDomainAttr::CreateImplicit( S.Context, &S.Context.Idents.get(*nsErrorDomain)); @@ -271,7 +317,7 @@ static void ProcessAPINotes(Sema &S, Decl *D, } ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Check that the replacement type provided by API notes is reasonable. @@ -293,9 +339,9 @@ static bool checkAPINotesReplacementType(Sema &S, SourceLocation loc, /// Process API notes for a variable or property. static void ProcessAPINotes(Sema &S, Decl *D, const api_notes::VariableInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Type override. - if (role != VersionedInfoRole::Versioned && + if (metadata.Role != VersionedInfoRole::Versioned && !info.getType().empty() && S.ParseTypeFromStringCallback) { auto parsedType = S.ParseTypeFromStringCallback(info.getType(), "", @@ -332,50 +378,50 @@ static void ProcessAPINotes(Sema &S, Decl *D, // Nullability. if (auto Nullability = info.getNullability()) { - applyNullability(S, D, *Nullability, role); + applyNullability(S, D, *Nullability, metadata); } // Handle common entity information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Process API notes for a parameter. static void ProcessAPINotes(Sema &S, ParmVarDecl *D, const api_notes::ParamInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // noescape if (auto noescape = info.isNoEscape()) { - handleAPINotedAttribute(S, D, *noescape, role, [&] { + handleAPINotedAttribute(S, D, *noescape, metadata, [&] { return NoEscapeAttr::CreateImplicit(S.Context); }); } // Handle common entity information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Process API notes for a global variable. static void ProcessAPINotes(Sema &S, VarDecl *D, const api_notes::GlobalVariableInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Handle common entity information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Process API notes for an Objective-C property. static void ProcessAPINotes(Sema &S, ObjCPropertyDecl *D, const api_notes::ObjCPropertyInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Handle common entity information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); if (auto asAccessors = info.getSwiftImportAsAccessors()) { handleAPINotedAttribute(S, D, *asAccessors, - role, [&] { + metadata, [&] { return SwiftImportPropertyAsAccessorsAttr::CreateImplicit(S.Context); }); } @@ -388,7 +434,7 @@ namespace { /// Process API notes for a function or method. static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, const api_notes::FunctionInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Find the declaration itself. FunctionDecl *FD = AnyFunc.dyn_cast(); Decl *D = FD; @@ -400,7 +446,7 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, // Nullability of return type. if (info.NullabilityAudited) { - applyNullability(S, D, info.getReturnTypeInfo(), role); + applyNullability(S, D, info.getReturnTypeInfo(), metadata); } // Parameters. @@ -421,12 +467,12 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, QualType paramTypeBefore = Param->getType(); if (I < info.Params.size()) { - ProcessAPINotes(S, Param, info.Params[I], role); + ProcessAPINotes(S, Param, info.Params[I], metadata); } // Nullability. if (info.NullabilityAudited) - applyNullability(S, Param, info.getParamTypeInfo(I), role); + applyNullability(S, Param, info.getParamTypeInfo(I), metadata); if (paramTypeBefore.getAsOpaquePtr() != Param->getType().getAsOpaquePtr()) anyTypeChanged = true; @@ -434,8 +480,8 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, // Result type override. QualType overriddenResultType; - if (role != VersionedInfoRole::Versioned && !info.ResultType.empty() && - S.ParseTypeFromStringCallback) { + if (metadata.Role != VersionedInfoRole::Versioned && + !info.ResultType.empty() && S.ParseTypeFromStringCallback) { auto parsedType = S.ParseTypeFromStringCallback(info.ResultType, "", D->getLocation()); @@ -483,37 +529,38 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, // Handle common entity information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Process API notes for a global function. static void ProcessAPINotes(Sema &S, FunctionDecl *D, const api_notes::GlobalFunctionInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Handle common function information. ProcessAPINotes(S, FunctionOrMethod(D), - static_cast(info), role); + static_cast(info), metadata); } /// Process API notes for an enumerator. static void ProcessAPINotes(Sema &S, EnumConstantDecl *D, const api_notes::EnumConstantInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Handle common information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Process API notes for an Objective-C method. static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, const api_notes::ObjCMethodInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Designated initializers. if (info.DesignatedInit) { - handleAPINotedAttribute(S, D, true, role, [&] { + handleAPINotedAttribute(S, D, true, metadata, + [&] { if (ObjCInterfaceDecl *IFace = D->getClassInterface()) { IFace->setHasDesignatedInitializers(); } @@ -522,7 +569,7 @@ static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, } // FIXME: This doesn't work well with versioned API notes. - if (role == VersionedInfoRole::AugmentSource && + if (metadata.Role == VersionedInfoRole::AugmentSource && info.getFactoryAsInitKind() == api_notes::FactoryAsInitKind::AsClassMethod && !D->getAttr()) { @@ -531,28 +578,28 @@ static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, // Handle common function information. ProcessAPINotes(S, FunctionOrMethod(D), - static_cast(info), role); + static_cast(info), metadata); } /// Process API notes for a tag. static void ProcessAPINotes(Sema &S, TagDecl *D, const api_notes::TagInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Handle common type information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Process API notes for a typedef. static void ProcessAPINotes(Sema &S, TypedefNameDecl *D, const api_notes::TypedefInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // swift_wrapper using SwiftWrapperKind = api_notes::SwiftWrapperKind; if (auto swiftWrapper = info.SwiftWrapper) { handleAPINotedAttribute(S, D, - *swiftWrapper != SwiftWrapperKind::None, role, + *swiftWrapper != SwiftWrapperKind::None, metadata, [&] { SwiftNewtypeAttr::NewtypeKind kind; switch (*swiftWrapper) { @@ -576,25 +623,47 @@ static void ProcessAPINotes(Sema &S, TypedefNameDecl *D, // Handle common type information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Process API notes for an Objective-C class or protocol. static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, const api_notes::ObjCContextInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Handle common type information. ProcessAPINotes(S, D, static_cast(info), - role); + metadata); } /// Process API notes for an Objective-C class. static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, const api_notes::ObjCContextInfo &info, - VersionedInfoRole role) { + VersionedInfoMetadata metadata) { // Handle information common to Objective-C classes and protocols. - ProcessAPINotes(S, static_cast(D), info, role); + ProcessAPINotes(S, static_cast(D), info, + metadata); +} + +/// Processes all versions of versioned API notes. +/// +/// Just dispatches to the various ProcessAPINotes functions in this file. +template +static void ProcessVersionedAPINotes( + Sema &S, SpecificDecl *D, + const api_notes::APINotesReader::VersionedInfo Info) { + unsigned Selected = Info.getSelected().getValueOr(Info.size()); + + VersionTuple Version; + SpecificInfo InfoSlice; + for (unsigned i = 0, e = Info.size(); i != e; ++i) { + std::tie(Version, InfoSlice) = Info[i]; + if (i != Selected) { + ProcessAPINotes(S, D, InfoSlice, Version); + } else { + ProcessAPINotes(S, D, InfoSlice, Info.getSelectedRole()); + } + } } /// Process API notes that are associated with this declaration, mapping them @@ -608,9 +677,8 @@ void Sema::ProcessAPINotes(Decl *D) { // Global variables. if (auto VD = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupGlobalVariable(VD->getName())) { - ::ProcessAPINotes(*this, VD, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupGlobalVariable(VD->getName()); + ProcessVersionedAPINotes(*this, VD, Info); } return; @@ -620,9 +688,8 @@ void Sema::ProcessAPINotes(Decl *D) { if (auto FD = dyn_cast(D)) { if (FD->getDeclName().isIdentifier()) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupGlobalFunction(FD->getName())) { - ::ProcessAPINotes(*this, FD, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupGlobalFunction(FD->getName()); + ProcessVersionedAPINotes(*this, FD, Info); } } @@ -632,9 +699,8 @@ void Sema::ProcessAPINotes(Decl *D) { // Objective-C classes. if (auto Class = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupObjCClassInfo(Class->getName())) { - ::ProcessAPINotes(*this, Class, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupObjCClassInfo(Class->getName()); + ProcessVersionedAPINotes(*this, Class, Info); } return; @@ -643,9 +709,8 @@ void Sema::ProcessAPINotes(Decl *D) { // Objective-C protocols. if (auto Protocol = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupObjCProtocolInfo(Protocol->getName())) { - ::ProcessAPINotes(*this, Protocol, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupObjCProtocolInfo(Protocol->getName()); + ProcessVersionedAPINotes(*this, Protocol, Info); } return; @@ -654,9 +719,8 @@ void Sema::ProcessAPINotes(Decl *D) { // Tags if (auto Tag = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupTag(Tag->getName())) { - ::ProcessAPINotes(*this, Tag, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupTag(Tag->getName()); + ProcessVersionedAPINotes(*this, Tag, Info); } return; @@ -665,9 +729,8 @@ void Sema::ProcessAPINotes(Decl *D) { // Typedefs if (auto Typedef = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupTypedef(Typedef->getName())) { - ::ProcessAPINotes(*this, Typedef, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupTypedef(Typedef->getName()); + ProcessVersionedAPINotes(*this, Typedef, Info); } return; @@ -680,9 +743,8 @@ void Sema::ProcessAPINotes(Decl *D) { if (D->getDeclContext()->getRedeclContext()->isFileContext()) { if (auto EnumConstant = dyn_cast(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { - if (auto Info = Reader->lookupEnumConstant(EnumConstant->getName())) { - ::ProcessAPINotes(*this, EnumConstant, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupEnumConstant(EnumConstant->getName()); + ProcessVersionedAPINotes(*this, EnumConstant, Info); } return; @@ -750,10 +812,9 @@ void Sema::ProcessAPINotes(Decl *D) { SelectorRef.NumPieces = Sel.getNumArgs(); SelectorRef.Identifiers = SelPieces; - if (auto Info = Reader->lookupObjCMethod(*Context, SelectorRef, - Method->isInstanceMethod())){ - ::ProcessAPINotes(*this, Method, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupObjCMethod(*Context, SelectorRef, + Method->isInstanceMethod()); + ProcessVersionedAPINotes(*this, Method, Info); } } } @@ -765,11 +826,9 @@ void Sema::ProcessAPINotes(Decl *D) { bool isInstanceProperty = (Property->getPropertyAttributesAsWritten() & ObjCPropertyDecl::OBJC_PR_class) == 0; - if (auto Info = Reader->lookupObjCProperty(*Context, - Property->getName(), - isInstanceProperty)) { - ::ProcessAPINotes(*this, Property, *Info, Info.getSelectedRole()); - } + auto Info = Reader->lookupObjCProperty(*Context, Property->getName(), + isInstanceProperty); + ProcessVersionedAPINotes(*this, Property, Info); } } diff --git a/clang/test/APINotes/properties.m b/clang/test/APINotes/properties.m index a4c925b67449a..b1559b9002c81 100644 --- a/clang/test/APINotes/properties.m +++ b/clang/test/APINotes/properties.m @@ -10,22 +10,36 @@ // CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnly 'id' // CHECK-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit +// CHECK-NOT: Attr // CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyForClass 'id' // CHECK-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit +// CHECK-NOT: Attr // CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyInVersion3 'id' // CHECK-3-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit -// CHECK-4-NEXT: {{^$}} +// CHECK-4-NEXT: SwiftVersionedAttr {{.+}} 3.0 +// CHECK-4-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit +// CHECK-NOT: Attr // CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyForClassInVersion3 'id' // CHECK-3-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit -// CHECK-4-NEXT: {{^$}} +// CHECK-4-NEXT: SwiftVersionedAttr {{.+}} 3.0 +// CHECK-4-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit +// CHECK-NOT: Attr // CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyExceptInVersion3 'id' -// CHECK-3-NEXT: {{^$}} +// CHECK-3-NEXT: SwiftVersionedAttr {{.+}} 0{{$}} +// CHECK-3-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit // CHECK-4-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit +// CHECK-4-NEXT: SwiftVersionedRemovalAttr {{.+}} Implicit 3.0 {{[0-9]+}} +// CHECK-NOT: Attr // CHECK-LABEL: ObjCPropertyDecl {{.+}} accessorsOnlyForClassExceptInVersion3 'id' -// CHECK-3-NEXT: {{^$}} +// CHECK-3-NEXT: SwiftVersionedAttr {{.+}} 0{{$}} +// CHECK-3-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit // CHECK-4-NEXT: SwiftImportPropertyAsAccessorsAttr {{.+}} Implicit +// CHECK-4-NEXT: SwiftVersionedRemovalAttr {{.+}} Implicit 3.0 {{[0-9]+}} +// CHECK-NOT: Attr + +// CHECK-LABEL: Decl diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index d761620b80eb8..38690fe18be86 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -3,16 +3,27 @@ // Build and check the unversioned module file. // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Unversioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: %clang_cc1 -ast-print %t/ModulesCache/Unversioned/VersionedKit.pcm | FileCheck -check-prefix=CHECK-UNVERSIONED %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Unversioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'moveToPoint' | FileCheck -check-prefix=CHECK-DUMP -check-prefix=CHECK-UNVERSIONED-DUMP %s // Build and check the versioned module file. -// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3.0 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: %clang_cc1 -ast-print %t/ModulesCache/Versioned/VersionedKit.pcm | FileCheck -check-prefix=CHECK-VERSIONED %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'moveToPoint' | FileCheck -check-prefix=CHECK-DUMP -check-prefix=CHECK-VERSIONED-DUMP %s #import // CHECK-UNVERSIONED: void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); // CHECK-VERSIONED: void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(a:b:)"))); +// CHECK-DUMP-LABEL: Dumping moveToPoint +// CHECK-VERSIONED-DUMP: SwiftVersionedAttr {{.+}} Implicit 0 +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "moveTo(x:y:)" +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} Implicit "moveTo(a:b:)" +// CHECK-UNVERSIONED-DUMP: SwiftNameAttr {{.+}} "moveTo(x:y:)" +// CHECK-UNVERSIONED-DUMP-NEXT: SwiftVersionedAttr {{.+}} Implicit 3.0 +// CHECK-UNVERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} Implicit "moveTo(a:b:)" +// CHECK-DUMP-NOT: Dumping + // CHECK-UNVERSIONED: void acceptClosure(void (^block)(void) __attribute__((noescape))); // CHECK-VERSIONED: void acceptClosure(void (^block)(void)); diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index 27ab34c1309d0..8ba2ebc487f63 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -1160,6 +1160,32 @@ namespace { } }; + class AttrArgument : public SimpleArgument { + public: + AttrArgument(const Record &Arg, StringRef Attr) + : SimpleArgument(Arg, Attr, "Attr *") + {} + + void writePCHReadDecls(raw_ostream &OS) const override { + OS << " AttrVec vec;\n" + " ReadAttributes(F, vec, Record, Idx);\n" + " assert(vec.size() == 1);\n" + " Attr *" << getLowerName() << " = vec.front();"; + } + + void writePCHWrite(raw_ostream &OS) const override { + OS << " AddAttributes(SA->get" << getUpperName() << "());"; + } + + void writeDump(raw_ostream &OS) const override {} + + void writeDumpChildren(raw_ostream &OS) const override { + OS << " dumpAttr(SA->get" << getUpperName() << "());\n"; + } + + void writeHasChildren(raw_ostream &OS) const override { OS << "true"; } + }; + } // end anonymous namespace static std::unique_ptr @@ -1207,6 +1233,8 @@ createArgument(const Record &Arg, StringRef Attr, Ptr = llvm::make_unique(Arg, Attr); else if (ArgName == "VersionArgument") Ptr = llvm::make_unique(Arg, Attr); + else if (ArgName == "AttrArgument") + Ptr = llvm::make_unique(Arg, Attr); if (!Ptr) { // Search in reverse order so that the most-derived type is handled first. From 3c1a9d7bd8601a3ca84da38e4458b38f2a6272ff Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Wed, 11 Jan 2017 22:28:37 -0800 Subject: [PATCH 133/585] [index] Remove SwiftAccessorGetter/Setter symbol subkinds. Got superseded by the more general ones. apple-llvm-split-commit: 289cf572c9587176be1131e1d55983ef58ffa0ae apple-llvm-split-dir: clang/ --- clang/include/clang/Index/IndexSymbol.h | 2 -- clang/lib/Index/IndexSymbol.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/clang/include/clang/Index/IndexSymbol.h b/clang/include/clang/Index/IndexSymbol.h index 92a4bfcb73c58..16c3026eba8fe 100644 --- a/clang/include/clang/Index/IndexSymbol.h +++ b/clang/include/clang/Index/IndexSymbol.h @@ -70,8 +70,6 @@ enum class SymbolSubKind { // Swift sub-kinds - SwiftAccessorGetter, - SwiftAccessorSetter, SwiftAccessorWillSet, SwiftAccessorDidSet, SwiftAccessorAddressor, diff --git a/clang/lib/Index/IndexSymbol.cpp b/clang/lib/Index/IndexSymbol.cpp index 6f8cd0831ff60..f3de472675641 100644 --- a/clang/lib/Index/IndexSymbol.cpp +++ b/clang/lib/Index/IndexSymbol.cpp @@ -389,8 +389,6 @@ StringRef index::getSymbolSubKindString(SymbolSubKind K) { case SymbolSubKind::CXXMoveConstructor: return "cxx-move-ctor"; case SymbolSubKind::AccessorGetter: return "acc-get"; case SymbolSubKind::AccessorSetter: return "acc-set"; - case SymbolSubKind::SwiftAccessorGetter: return "acc-get"; - case SymbolSubKind::SwiftAccessorSetter: return "acc-set"; case SymbolSubKind::SwiftAccessorWillSet: return "acc-willset"; case SymbolSubKind::SwiftAccessorDidSet: return "acc-didset"; case SymbolSubKind::SwiftAccessorAddressor: return "acc-addr"; From ce08f6bb8fe46f73b4e19ed184391ea51bf29962 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Thu, 12 Jan 2017 12:21:14 -0800 Subject: [PATCH 134/585] [ASTReader] Add a DeserializationListener callback for IMPORTED_MODULES apple-llvm-split-commit: 19b4e7d2d33b2f01b9c137a578b0b9c6ef943e58 apple-llvm-split-dir: clang/ --- .../include/clang/Serialization/ASTDeserializationListener.h | 4 ++++ clang/lib/Serialization/ASTReader.cpp | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Serialization/ASTDeserializationListener.h b/clang/include/clang/Serialization/ASTDeserializationListener.h index 4b10c39d8fb22..c26f3e0b425e4 100644 --- a/clang/include/clang/Serialization/ASTDeserializationListener.h +++ b/clang/include/clang/Serialization/ASTDeserializationListener.h @@ -26,6 +26,7 @@ class QualType; class MacroDefinitionRecord; class MacroInfo; class Module; +class SourceLocation; class ASTDeserializationListener { public: @@ -52,6 +53,9 @@ class ASTDeserializationListener { MacroDefinitionRecord *MD) {} /// \brief A module definition was read from the AST file. virtual void ModuleRead(serialization::SubmoduleID ID, Module *Mod) {} + /// \brief A module import was read from the AST file. + virtual void ModuleImportRead(serialization::SubmoduleID ID, + SourceLocation ImportLoc) {} }; } diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index aa0b6b22a0b50..6e16980c78448 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -3245,8 +3245,11 @@ ASTReader::ReadASTBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { for (unsigned I = 0, N = Record.size(); I != N; /**/) { unsigned GlobalID = getGlobalSubmoduleID(F, Record[I++]); SourceLocation Loc = ReadSourceLocation(F, Record, I); - if (GlobalID) + if (GlobalID) { ImportedModules.push_back(ImportedSubmodule(GlobalID, Loc)); + if (DeserializationListener) + DeserializationListener->ModuleImportRead(GlobalID, Loc); + } } } break; From 847e8814275dd20285da0c9953c027894369c976 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 18 Jan 2017 09:50:43 -0800 Subject: [PATCH 135/585] [API Notes] Consult API notes for implicitly-declared property getter/setter. Fixes rdar://problem/30066544. apple-llvm-split-commit: e74f86c4e3732bd594854bb9898b1537478028c4 apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaObjCProperty.cpp | 5 ++++ .../Headers/SomeKit.apinotes | 24 +++++++++++++++++++ .../SomeKit.framework/Headers/SomeKit.h | 8 +++++++ clang/test/APINotes/availability.m | 12 ++++++++++ 4 files changed, 49 insertions(+) diff --git a/clang/lib/Sema/SemaObjCProperty.cpp b/clang/lib/Sema/SemaObjCProperty.cpp index a9c3889c53b79..eea44eb3bc5e6 100644 --- a/clang/lib/Sema/SemaObjCProperty.cpp +++ b/clang/lib/Sema/SemaObjCProperty.cpp @@ -2250,6 +2250,8 @@ void Sema::ProcessPropertyDecl(ObjCPropertyDecl *property) { SectionAttr::CreateImplicit(Context, SectionAttr::GNU_section, SA->getName(), Loc)); + ProcessAPINotes(GetterMethod); + if (getLangOpts().ObjCAutoRefCount) CheckARCMethodDecl(GetterMethod); } else @@ -2315,6 +2317,9 @@ void Sema::ProcessPropertyDecl(ObjCPropertyDecl *property) { SetterMethod->addAttr( SectionAttr::CreateImplicit(Context, SectionAttr::GNU_section, SA->getName(), Loc)); + + ProcessAPINotes(SetterMethod); + // It's possible for the user to have set a very odd custom // setter selector that causes it to have a method family. if (getLangOpts().ObjCAutoRefCount) diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes index d2d10037501d7..ff88fdbaeac83 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.apinotes @@ -10,6 +10,30 @@ Classes: MethodKind: Instance NullabilityOfRet: N Nullability: [ N, S ] + - Selector: "implicitGetOnlyInstance" + MethodKind: Instance + Availability: none + AvailabilityMsg: "getter gone" + - Selector: "implicitGetOnlyClass" + MethodKind: Class + Availability: none + AvailabilityMsg: "getter gone" + - Selector: "implicitGetSetInstance" + MethodKind: Instance + Availability: none + AvailabilityMsg: "getter gone" + - Selector: "implicitGetSetClass" + MethodKind: Class + Availability: none + AvailabilityMsg: "getter gone" + - Selector: "setImplicitGetSetInstance:" + MethodKind: Instance + Availability: none + AvailabilityMsg: "setter gone" + - Selector: "setImplicitGetSetClass:" + MethodKind: Class + Availability: none + AvailabilityMsg: "setter gone" Properties: - Name: intValue PropertyKind: Instance diff --git a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h index 42a9fbcc2b27b..1a192f5432fd1 100644 --- a/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/SomeKit.framework/Headers/SomeKit.h @@ -49,4 +49,12 @@ __attribute__((objc_root_class)) @property int *intPropertyToMangle; @end +@interface A(ImplicitGetterSetters) +@property (nonatomic, readonly, retain) A *implicitGetOnlyInstance; +@property (class, nonatomic, readonly, retain) A *implicitGetOnlyClass; + +@property (nonatomic, readwrite, retain) A *implicitGetSetInstance; +@property (class, nonatomic, readwrite, retain) A *implicitGetSetClass; +@end + #endif diff --git a/clang/test/APINotes/availability.m b/clang/test/APINotes/availability.m index 6a3034c0ceb50..f9bee1a000012 100644 --- a/clang/test/APINotes/availability.m +++ b/clang/test/APINotes/availability.m @@ -31,6 +31,18 @@ int main() { [a transform:a]; // expected-error{{'transform:' is unavailable: anything but this}} // expected-note@SomeKit/SomeKit.h:6{{'transform:' has been explicitly marked unavailable here}} + [a implicitGetOnlyInstance]; // expected-error{{'implicitGetOnlyInstance' is unavailable: getter gone}} + // expected-note@SomeKit/SomeKit.h:53{{'implicitGetOnlyInstance' has been explicitly marked unavailable here}} + [A implicitGetOnlyClass]; // expected-error{{'implicitGetOnlyClass' is unavailable: getter gone}} + // expected-note@SomeKit/SomeKit.h:54{{'implicitGetOnlyClass' has been explicitly marked unavailable here}} + [a implicitGetSetInstance]; // expected-error{{'implicitGetSetInstance' is unavailable: getter gone}} + // expected-note@SomeKit/SomeKit.h:56{{'implicitGetSetInstance' has been explicitly marked unavailable here}} + [a setImplicitGetSetInstance: a]; // expected-error{{'setImplicitGetSetInstance:' is unavailable: setter gone}} + // expected-note@SomeKit/SomeKit.h:56{{'setImplicitGetSetInstance:' has been explicitly marked unavailable here}} + [A implicitGetSetClass]; // expected-error{{'implicitGetSetClass' is unavailable: getter gone}} + // expected-note@SomeKit/SomeKit.h:57{{'implicitGetSetClass' has been explicitly marked unavailable here}} + [A setImplicitGetSetClass: a]; // expected-error{{'setImplicitGetSetClass:' is unavailable: setter gone}} + // expected-note@SomeKit/SomeKit.h:57{{'setImplicitGetSetClass:' has been explicitly marked unavailable here}} return 0; } From 654e4a76ec35c10c907f9d66c7b4e6e471a504e1 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 18 Jan 2017 13:42:23 -0800 Subject: [PATCH 136/585] [APINotes] Remove FactoryAsInit (superseded by SwiftName) This still leaves behind parsing (to emit an error message) as well as the attribute that used to get generated. Once Swift is no longer checking for the attribute we can take that out as well. apple-llvm-split-commit: 0474bbbe939c82e06933c732975d8664f9d88e47 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 23 ----------- clang/lib/APINotes/APINotesReader.cpp | 2 - clang/lib/APINotes/APINotesWriter.cpp | 2 +- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 39 ++++++++++--------- clang/lib/APINotes/Types.cpp | 2 +- clang/lib/Sema/SemaAPINotes.cpp | 8 ---- clang/test/APINotes/Inputs/roundtrip.apinotes | 1 - 7 files changed, 23 insertions(+), 54 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 6fbd5d809dad6..3f7ab86cf15ea 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -38,16 +38,6 @@ using llvm::StringRef; using llvm::Optional; using llvm::None; -/// Describes whether to classify a factory method as an initializer. -enum class FactoryAsInitKind { - /// Infer based on name and type (the default). - Infer, - /// Treat as a class method. - AsClassMethod, - /// Treat as an initializer. - AsInitializer -}; - /// Opaque context ID used to refer to an Objective-C class or protocol. class ContextID { public: @@ -556,30 +546,17 @@ class ObjCMethodInfo : public FunctionInfo { /// Whether this is a designated initializer of its class. unsigned DesignatedInit : 1; - /// Whether to treat this method as a factory or initializer. - unsigned FactoryAsInit : 2; - /// Whether this is a required initializer. unsigned Required : 1; ObjCMethodInfo() : FunctionInfo(), DesignatedInit(false), - FactoryAsInit(static_cast(FactoryAsInitKind::Infer)), Required(false) { } - FactoryAsInitKind getFactoryAsInitKind() const { - return static_cast(FactoryAsInit); - } - - void setFactoryAsInitKind(FactoryAsInitKind kind) { - FactoryAsInit = static_cast(kind); - } - friend bool operator==(const ObjCMethodInfo &lhs, const ObjCMethodInfo &rhs) { return static_cast(lhs) == rhs && lhs.DesignatedInit == rhs.DesignatedInit && - lhs.FactoryAsInit == rhs.FactoryAsInit && lhs.Required == rhs.Required; } diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 8fbe2a184541e..b20bb4373b833 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -353,8 +353,6 @@ namespace { payload >>= 1; info.DesignatedInit = payload & 0x01; payload >>= 1; - info.FactoryAsInit = payload & 0x03; - payload >>= 2; readFunctionInfo(data, info); return info; diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 86b6abd2a36e6..116d23d82b1d0 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -766,7 +766,7 @@ namespace { } void emitUnversionedInfo(raw_ostream &out, const ObjCMethodInfo &info) { - uint8_t payload = info.FactoryAsInit; + uint8_t payload = 0; payload = (payload << 1) | info.DesignatedInit; payload = (payload << 1) | info.Required; endian::Writer writer(out); diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 0ac0b6d65b767..3621b72fefc93 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -35,11 +35,6 @@ parameter has a nullability value. For 'audited' APIs, we assume the default nullability for any underspecified type. - FactoryAsInit can have the following values: - C - Treat as class method. - I - Treat as initializer. - A - Automatically infer based on the name and type (default). - --- Name: AppKit # The name of the framework @@ -98,8 +93,6 @@ AvailabilityMsg: "" - FactoryAsInit: C # Optional: Specifies if this method is a - # factory initializer (false/true) DesignatedInit: false # Optional: Specifies if this method is a # designated initializer (false/true) @@ -158,6 +151,16 @@ namespace { Instance, }; + /// Old attribute deprecated in favor of SwiftName. + enum class FactoryAsInitKind { + /// Infer based on name and type (the default). + Infer, + /// Treat as a class method. + AsClassMethod, + /// Treat as an initializer. + AsInitializer + }; + struct AvailabilityItem { APIAvailability Mode = APIAvailability::Available; StringRef Msg; @@ -186,8 +189,7 @@ namespace { AvailabilityItem Availability; Optional SwiftPrivate; StringRef SwiftName; - api_notes::FactoryAsInitKind FactoryAsInit - = api_notes::FactoryAsInitKind::Infer; + FactoryAsInitKind FactoryAsInit = FactoryAsInitKind::Infer; bool DesignatedInit = false; bool Required = false; StringRef ResultType; @@ -330,11 +332,11 @@ namespace llvm { }; template <> - struct ScalarEnumerationTraits { - static void enumeration(IO &io, api_notes::FactoryAsInitKind &value) { - io.enumCase(value, "A", api_notes::FactoryAsInitKind::Infer); - io.enumCase(value, "C", api_notes::FactoryAsInitKind::AsClassMethod); - io.enumCase(value, "I", api_notes::FactoryAsInitKind::AsInitializer); + struct ScalarEnumerationTraits { + static void enumeration(IO &io, FactoryAsInitKind &value) { + io.enumCase(value, "A", FactoryAsInitKind::Infer); + io.enumCase(value, "C", FactoryAsInitKind::AsClassMethod); + io.enumCase(value, "I", FactoryAsInitKind::AsInitializer); } }; @@ -425,7 +427,7 @@ namespace llvm { io.mapOptional("SwiftPrivate", m.SwiftPrivate); io.mapOptional("SwiftName", m.SwiftName); io.mapOptional("FactoryAsInit", m.FactoryAsInit, - api_notes::FactoryAsInitKind::Infer); + FactoryAsInitKind::Infer); io.mapOptional("DesignatedInit", m.DesignatedInit, false); io.mapOptional("Required", m.Required, false); io.mapOptional("ResultType", m.ResultType, StringRef("")); @@ -721,8 +723,10 @@ namespace { // Translate the initializer info. mInfo.DesignatedInit = meth.DesignatedInit; mInfo.Required = meth.Required; - if (meth.FactoryAsInit != FactoryAsInitKind::Infer) - mInfo.setFactoryAsInitKind(meth.FactoryAsInit); + if (meth.FactoryAsInit != FactoryAsInitKind::Infer) { + emitError("'FactoryAsInit' is no longer valid; " + "use 'SwiftName' instead"); + } mInfo.ResultType = meth.ResultType; // Translate parameter information. @@ -1193,7 +1197,6 @@ namespace { handleParameters(method.Params, info); handleNullability(method.Nullability, method.NullabilityOfRet, info, selector.count(':')); - method.FactoryAsInit = info.getFactoryAsInitKind(); method.DesignatedInit = info.DesignatedInit; method.Required = info.Required; method.ResultType = copyString(info.ResultType); diff --git a/clang/lib/APINotes/Types.cpp b/clang/lib/APINotes/Types.cpp index 963780fb6f32c..4bbb4a86851cd 100644 --- a/clang/lib/APINotes/Types.cpp +++ b/clang/lib/APINotes/Types.cpp @@ -14,7 +14,7 @@ #include "llvm/Support/raw_ostream.h" void clang::api_notes::ObjCMethodInfo::dump(llvm::raw_ostream &os) { - os << DesignatedInit << " " << FactoryAsInit << " " << Unavailable << " " + os << DesignatedInit << " " << Unavailable << " " << NullabilityAudited << " " << NumAdjustedNullable << " " << NullabilityPayload << " " << UnavailableMsg << "\n"; } diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 078445e48ad87..a7b77bc1795c2 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -568,14 +568,6 @@ static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, }); } - // FIXME: This doesn't work well with versioned API notes. - if (metadata.Role == VersionedInfoRole::AugmentSource && - info.getFactoryAsInitKind() - == api_notes::FactoryAsInitKind::AsClassMethod && - !D->getAttr()) { - D->addAttr(SwiftSuppressFactoryAsInitAttr::CreateImplicit(S.Context)); - } - // Handle common function information. ProcessAPINotes(S, FunctionOrMethod(D), static_cast(info), metadata); diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 05599a772d5d6..2937fa6e07bd0 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -16,7 +16,6 @@ Classes: AvailabilityMsg: '' SwiftPrivate: false SwiftName: '' - FactoryAsInit: C ResultType: id - Selector: init MethodKind: Instance From 75d0e02ee2791f81bb8735cd34f94b19ac4fa8b5 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 18 Jan 2017 17:45:46 -0800 Subject: [PATCH 137/585] Remove the attribute used by the FactoryAsInit API note. apple-llvm-split-commit: 7652b982a1b6a2660078cba5a6858680a04a02a2 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Attr.td | 8 -------- 1 file changed, 8 deletions(-) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index a88fabb6ac838..40175db3ac632 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1487,14 +1487,6 @@ def SwiftPrivate : InheritableAttr { let Documentation = [Undocumented]; } -def SwiftSuppressFactoryAsInit : InheritableAttr { - // This attribute has no spellings as it is only ever created implicitly - // from API notes. - let Spellings = []; - let SemaHandler = 0; - let Documentation = [Undocumented]; -} - def SwiftImportPropertyAsAccessors : InheritableAttr { // This attribute has no spellings as it is only ever created implicitly // from API notes. From 5ee3434bac9f3493d5bd5b8e22d4f3c3cf4c4a4d Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 19 Jan 2017 11:11:53 -0800 Subject: [PATCH 138/585] [APINotes] Remove VersionedInfoRole. When API notes were first added, they would defer to the header when something was explicitly specified---for example, if the API notes had one set of nullability annotations from before the header was audited. This behavior didn't make sense for versioned API notes, however, where annotations needed for Swift 3 compatibility were intended to override the unversioned information in both the API notes and the header itself. Now that the versioning of API notes is well-established, simplify things by having the API notes always "win"; that is, an annotation in API notes will always override what's in the headers. apple-llvm-split-commit: b0755dd83ebfd484b843b3f63d87161af8c169e0 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/APINotesReader.h | 22 ------ clang/lib/APINotes/APINotesReader.cpp | 7 +- clang/lib/Sema/SemaAPINotes.cpp | 70 +++++-------------- 3 files changed, 20 insertions(+), 79 deletions(-) diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 2b985c6ce73b5..a71c74da7b8a8 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -25,20 +25,6 @@ namespace clang { namespace api_notes { -/// Describes the role of a specific bit of versioned information. -enum class VersionedInfoRole : unsigned { - /// Augment the AST, but do not override information explicitly specified - /// in the source code. - AugmentSource, - - /// Replace information that may have been explicitly specified in the source - /// code. - ReplaceSource, - - /// Describes an alternate version of this information. - Versioned, -}; - /// A class that reads API notes data from a binary file that was written by /// the \c APINotesWriter. class APINotesReader { @@ -94,9 +80,6 @@ class APINotesReader { /// Swift version, or \c Results.size() if nothing matched. unsigned Selected; - /// The role of the selected index. - VersionedInfoRole SelectedRole; - public: /// Form an empty set of versioned information. VersionedInfo(llvm::NoneType) : Selected(0) { } @@ -122,11 +105,6 @@ class APINotesReader { return Selected; } - /// Describes the role of the selected entity. - VersionedInfoRole getSelectedRole() const { - return SelectedRole; - } - /// Return the number of versioned results we know about. unsigned size() const { return Results.size(); } diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index b20bb4373b833..295e5017c3a8f 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -1471,14 +1471,10 @@ APINotesReader::VersionedInfo::VersionedInfo( // Look for an exact version match. Optional unversioned; Selected = Results.size(); - SelectedRole = VersionedInfoRole::Versioned; for (unsigned i = 0, n = Results.size(); i != n; ++i) { if (Results[i].first == version) { Selected = i; - - if (version) SelectedRole = VersionedInfoRole::ReplaceSource; - else SelectedRole = VersionedInfoRole::AugmentSource; break; } @@ -1492,9 +1488,8 @@ APINotesReader::VersionedInfo::VersionedInfo( // unversioned result. if (Selected == Results.size() && unversioned) { Selected = *unversioned; - SelectedRole = VersionedInfoRole::AugmentSource; - } } +} auto APINotesReader::lookupObjCClassID(StringRef name) -> Optional { if (!Impl.ObjCContextIDTable) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index a7b77bc1795c2..f11f803899ccf 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -15,20 +15,20 @@ #include "clang/AST/DeclObjC.h" #include "clang/APINotes/APINotesReader.h" using namespace clang; -using clang::api_notes::VersionedInfoRole; namespace { + enum IsActive_t : bool { + IsNotActive, + IsActive + }; + struct VersionedInfoMetadata { + /// An empty version refers to unversioned metadata. VersionTuple Version; - VersionedInfoRole Role; - - /*implicit*/ VersionedInfoMetadata(VersionedInfoRole role) : Role(role) { - assert(role != VersionedInfoRole::Versioned && - "explicit version required"); - } + bool IsActive; - /*implicit*/ VersionedInfoMetadata(VersionTuple version) - : Version(version), Role(VersionedInfoRole::Versioned) {} + VersionedInfoMetadata(VersionTuple version, IsActive_t active) + : Version(version), IsActive(active == IsActive_t::IsActive) {} }; } // end anonymous namespace @@ -45,20 +45,8 @@ static bool isMultiLevelPointerType(QualType type) { // Apply nullability to the given declaration. static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability, VersionedInfoMetadata metadata) { - bool overrideExisting; - switch (metadata.Role) { - case VersionedInfoRole::AugmentSource: - overrideExisting = false; - break; - - case VersionedInfoRole::ReplaceSource: - overrideExisting = true; - break; - - case VersionedInfoRole::Versioned: - // FIXME: Record versioned info? + if (!metadata.IsActive) return; - } QualType type; @@ -80,7 +68,7 @@ static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability, S.checkNullabilityTypeSpecifier(type, nullability, decl->getLocation(), /*isContextSensitive=*/false, isa(decl), /*implicit=*/true, - overrideExisting); + /*overrideExisting=*/true); if (type.getTypePtr() == origType.getTypePtr()) return; @@ -154,20 +142,7 @@ namespace { VersionedInfoMetadata metadata, llvm::function_ref createAttr, llvm::function_ref(Decl*)> getExistingAttr) { - switch (metadata.Role) { - case VersionedInfoRole::AugmentSource: - // If we're not adding an attribute, there's nothing to do. - if (!shouldAddAttribute) return; - - // If the attribute is already present, we're done. - if (getExistingAttr(D) != D->specific_attr_end()) return; - - // Add the attribute. - if (auto attr = createAttr()) - D->addAttr(attr); - break; - - case VersionedInfoRole::ReplaceSource: { + if (metadata.IsActive) { auto end = D->specific_attr_end(); auto existing = getExistingAttr(D); if (existing != end) { @@ -187,11 +162,8 @@ namespace { D->addAttr(attr); } } - break; - } - case VersionedInfoRole::Versioned: - // FIXME: Include the actual version instead of making one up. + } else { if (shouldAddAttribute) { if (auto attr = createAttr()) { auto *versioned = @@ -209,7 +181,6 @@ namespace { AttrKindFor::value); D->addAttr(versioned); } - break; } } @@ -341,8 +312,8 @@ static void ProcessAPINotes(Sema &S, Decl *D, const api_notes::VariableInfo &info, VersionedInfoMetadata metadata) { // Type override. - if (metadata.Role != VersionedInfoRole::Versioned && - !info.getType().empty() && S.ParseTypeFromStringCallback) { + if (metadata.IsActive && !info.getType().empty() && + S.ParseTypeFromStringCallback) { auto parsedType = S.ParseTypeFromStringCallback(info.getType(), "", D->getLocation()); @@ -480,8 +451,8 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, // Result type override. QualType overriddenResultType; - if (metadata.Role != VersionedInfoRole::Versioned && - !info.ResultType.empty() && S.ParseTypeFromStringCallback) { + if (metadata.IsActive && !info.ResultType.empty() && + S.ParseTypeFromStringCallback) { auto parsedType = S.ParseTypeFromStringCallback(info.ResultType, "", D->getLocation()); @@ -650,11 +621,8 @@ static void ProcessVersionedAPINotes( SpecificInfo InfoSlice; for (unsigned i = 0, e = Info.size(); i != e; ++i) { std::tie(Version, InfoSlice) = Info[i]; - if (i != Selected) { - ProcessAPINotes(S, D, InfoSlice, Version); - } else { - ProcessAPINotes(S, D, InfoSlice, Info.getSelectedRole()); - } + auto Active = (i == Selected) ? IsActive : IsNotActive; + ProcessAPINotes(S, D, InfoSlice, VersionedInfoMetadata(Version, Active)); } } From 585faeeb56212423dcfccb3bf6e1d2537b787eb1 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 23 Jan 2017 10:48:39 -0800 Subject: [PATCH 139/585] [APINotes] Add a 'SwiftImportAsNonGeneric' entry for ObjC classes (#70) If true, Swift will ignore any 'lightweight generics' parameters when importing the class, substituting any uses of them with their bounds. At least, it will once this lands and the Swift side gets implemented. Finishes rdar://problem/28455962. apple-llvm-split-commit: 63e3e8227d22f710f16bb6be398176beffab6b81 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 40 +++++++++++++++++-- clang/include/clang/Basic/Attr.td | 8 ++++ clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 4 ++ clang/lib/APINotes/APINotesWriter.cpp | 6 ++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 5 +++ clang/lib/Sema/SemaAPINotes.cpp | 7 ++++ .../Headers/VersionedKit.apinotes | 4 +- .../Headers/VersionedKit.h | 9 ++++- clang/test/APINotes/Inputs/roundtrip.apinotes | 1 + clang/test/APINotes/versioned.m | 16 +++++--- 11 files changed, 89 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 3f7ab86cf15ea..b81d273e9521d 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -212,12 +212,17 @@ class ObjCContextInfo : public CommonTypeInfo { /// Whether this class has designated initializers recorded. unsigned HasDesignatedInits : 1; + unsigned SwiftImportAsNonGenericSpecified : 1; + unsigned SwiftImportAsNonGeneric : 1; + public: ObjCContextInfo() : CommonTypeInfo(), HasDefaultNullability(0), DefaultNullability(0), - HasDesignatedInits(0) + HasDesignatedInits(0), + SwiftImportAsNonGenericSpecified(false), + SwiftImportAsNonGeneric(false) { } /// Determine the default nullability for properties and methods of this @@ -240,6 +245,21 @@ class ObjCContextInfo : public CommonTypeInfo { bool hasDesignatedInits() const { return HasDesignatedInits; } void setHasDesignatedInits(bool value) { HasDesignatedInits = value; } + Optional getSwiftImportAsNonGeneric() const { + if (SwiftImportAsNonGenericSpecified) + return SwiftImportAsNonGeneric; + return None; + } + void setSwiftImportAsNonGeneric(Optional value) { + if (value.hasValue()) { + SwiftImportAsNonGenericSpecified = true; + SwiftImportAsNonGeneric = value.getValue(); + } else { + SwiftImportAsNonGenericSpecified = false; + SwiftImportAsNonGeneric = false; + } + } + /// Strip off any information within the class information structure that is /// module-local, such as 'audited' flags. void stripModuleLocalInfo() { @@ -249,9 +269,9 @@ class ObjCContextInfo : public CommonTypeInfo { friend bool operator==(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { return static_cast(lhs) == rhs && - lhs.HasDefaultNullability == rhs.HasDefaultNullability && - lhs.DefaultNullability == rhs.DefaultNullability && - lhs.HasDesignatedInits == rhs.HasDesignatedInits; + lhs.getDefaultNullability() == rhs.getDefaultNullability() && + lhs.HasDesignatedInits == rhs.HasDesignatedInits && + lhs.getSwiftImportAsNonGeneric() == rhs.getSwiftImportAsNonGeneric(); } friend bool operator!=(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { @@ -270,6 +290,12 @@ class ObjCContextInfo : public CommonTypeInfo { } } + if (!lhs.SwiftImportAsNonGenericSpecified && + rhs.SwiftImportAsNonGenericSpecified) { + lhs.SwiftImportAsNonGenericSpecified = true; + lhs.SwiftImportAsNonGeneric = rhs.SwiftImportAsNonGeneric; + } + lhs.HasDesignatedInits |= rhs.HasDesignatedInits; return lhs; @@ -383,6 +409,12 @@ class ObjCPropertyInfo : public VariableInfo { } return lhs; } + + friend bool operator==(const ObjCPropertyInfo &lhs, + const ObjCPropertyInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.getSwiftImportAsAccessors() == rhs.getSwiftImportAsAccessors(); + } }; /// Describes a function or method parameter. diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 40175db3ac632..0560f17ca72bd 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1487,6 +1487,14 @@ def SwiftPrivate : InheritableAttr { let Documentation = [Undocumented]; } +def SwiftImportAsNonGeneric : InheritableAttr { + // This attribute has no spellings as it is only ever created implicitly + // from API notes. + let Spellings = []; + let SemaHandler = 0; + let Documentation = [Undocumented]; +} + def SwiftImportPropertyAsAccessors : InheritableAttr { // This attribute has no spellings as it is only ever created implicitly // from API notes. diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index ef3ef73ab0cdd..1e257f3ada3a5 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 21; // Override types +const uint16_t VERSION_MINOR = 22; // SwiftImportAsNonGeneric using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 295e5017c3a8f..587903fccfb7b 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -253,6 +253,10 @@ namespace { if (payload & 0x4) info.setDefaultNullability(static_cast(payload&0x03)); + payload >>= 3; + + if (payload & (1 << 1)) + info.setSwiftImportAsNonGeneric(payload & 1); return info; } diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 116d23d82b1d0..65edaf9a5946c 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -584,8 +584,12 @@ namespace { emitCommonTypeInfo(out, info); uint8_t payload = 0; + if (auto swiftImportAsNonGeneric = info.getSwiftImportAsNonGeneric()) { + payload |= (0x01 << 1) | swiftImportAsNonGeneric.getValue(); + } + payload <<= 3; if (auto nullable = info.getDefaultNullability()) { - payload = (0x01 << 2) | static_cast(*nullable); + payload |= (0x01 << 2) | static_cast(*nullable); } payload = (payload << 1) | (info.hasDesignatedInits() ? 1 : 0); out << payload; diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 3621b72fefc93..c96764617062c 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -216,6 +216,7 @@ namespace { StringRef SwiftName; Optional SwiftBridge; Optional NSErrorDomain; + Optional SwiftImportAsNonGeneric; MethodsSeq Methods; PropertiesSeq Properties; }; @@ -445,6 +446,7 @@ namespace llvm { io.mapOptional("SwiftName", c.SwiftName); io.mapOptional("SwiftBridge", c.SwiftBridge); io.mapOptional("NSErrorDomain", c.NSErrorDomain); + io.mapOptional("SwiftImportAsNonGeneric", c.SwiftImportAsNonGeneric); io.mapOptional("Methods", c.Methods); io.mapOptional("Properties", c.Properties); } @@ -752,6 +754,8 @@ namespace { if (cl.AuditedForNullability) cInfo.setDefaultNullability(*DefaultNullability); + if (cl.SwiftImportAsNonGeneric) + cInfo.setSwiftImportAsNonGeneric(*cl.SwiftImportAsNonGeneric); ContextID clID = Writer->addObjCContext(cl.Name, isClass, cInfo, swiftVersion); @@ -1101,6 +1105,7 @@ namespace { record.Name = name; handleCommonType(record, info); + record.SwiftImportAsNonGeneric = info.getSwiftImportAsNonGeneric(); if (info.getDefaultNullability()) { record.AuditedForNullability = true; diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index f11f803899ccf..f58691181f1cd 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -603,6 +603,13 @@ static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, const api_notes::ObjCContextInfo &info, VersionedInfoMetadata metadata) { + if (auto asNonGeneric = info.getSwiftImportAsNonGeneric()) { + handleAPINotedAttribute(S, D, *asNonGeneric, + metadata, [&] { + return SwiftImportAsNonGenericAttr::CreateImplicit(S.Context); + }); + } + // Handle information common to Objective-C classes and protocols. ProcessAPINotes(S, static_cast(D), info, metadata); diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index c3b70b6e9520b..bd09953dcda96 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -19,6 +19,8 @@ SwiftVersions: Classes: - Name: MyReferenceType SwiftBridge: '' + - Name: TestGenericDUMP + SwiftImportAsNonGeneric: true - Name: TestProperties Properties: - Name: accessorsOnlyInVersion3 @@ -34,7 +36,7 @@ SwiftVersions: PropertyKind: Class SwiftImportAsAccessors: false Functions: - - Name: moveToPoint + - Name: moveToPointDUMP SwiftName: 'moveTo(a:b:)' - Name: acceptClosure Parameters: diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h index ffbc7df7229d2..8301bd1f10a1c 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -1,4 +1,4 @@ -void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); +void moveToPointDUMP(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); void acceptClosure(void (^ __attribute__((noescape)) block)(void)); @@ -28,3 +28,10 @@ typedef double MyDoubleWrapper __attribute__((swift_wrapper(struct))); @property (nonatomic, readwrite, retain) id accessorsOnlyExceptInVersion3; @property (nonatomic, readwrite, retain, class) id accessorsOnlyForClassExceptInVersion3; @end + +@interface Base +@end + +@interface TestGenericDUMP : Base +- (Element)element; +@end diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 2937fa6e07bd0..4895060813798 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -9,6 +9,7 @@ Classes: AvailabilityMsg: '' SwiftPrivate: false SwiftName: '' + SwiftImportAsNonGeneric: true Methods: - Selector: 'cellWithImage:' MethodKind: Class diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index 38690fe18be86..29800b623c198 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -3,25 +3,31 @@ // Build and check the unversioned module file. // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Unversioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: %clang_cc1 -ast-print %t/ModulesCache/Unversioned/VersionedKit.pcm | FileCheck -check-prefix=CHECK-UNVERSIONED %s -// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Unversioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'moveToPoint' | FileCheck -check-prefix=CHECK-DUMP -check-prefix=CHECK-UNVERSIONED-DUMP %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Unversioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'DUMP' | FileCheck -check-prefix=CHECK-DUMP -check-prefix=CHECK-UNVERSIONED-DUMP %s // Build and check the versioned module file. // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s // RUN: %clang_cc1 -ast-print %t/ModulesCache/Versioned/VersionedKit.pcm | FileCheck -check-prefix=CHECK-VERSIONED %s -// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'moveToPoint' | FileCheck -check-prefix=CHECK-DUMP -check-prefix=CHECK-VERSIONED-DUMP %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fapinotes-swift-version=3 -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'DUMP' | FileCheck -check-prefix=CHECK-DUMP -check-prefix=CHECK-VERSIONED-DUMP %s #import -// CHECK-UNVERSIONED: void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); -// CHECK-VERSIONED: void moveToPoint(double x, double y) __attribute__((swift_name("moveTo(a:b:)"))); +// CHECK-UNVERSIONED: void moveToPointDUMP(double x, double y) __attribute__((swift_name("moveTo(x:y:)"))); +// CHECK-VERSIONED: void moveToPointDUMP(double x, double y) __attribute__((swift_name("moveTo(a:b:)"))); -// CHECK-DUMP-LABEL: Dumping moveToPoint +// CHECK-DUMP-LABEL: Dumping moveToPointDUMP // CHECK-VERSIONED-DUMP: SwiftVersionedAttr {{.+}} Implicit 0 // CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "moveTo(x:y:)" // CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} Implicit "moveTo(a:b:)" // CHECK-UNVERSIONED-DUMP: SwiftNameAttr {{.+}} "moveTo(x:y:)" // CHECK-UNVERSIONED-DUMP-NEXT: SwiftVersionedAttr {{.+}} Implicit 3.0 // CHECK-UNVERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} Implicit "moveTo(a:b:)" + +// CHECK-DUMP-LABEL: Dumping TestGenericDUMP +// CHECK-VERSIONED-DUMP: SwiftImportAsNonGenericAttr {{.+}} Implicit +// CHECK-UNVERSIONED-DUMP: SwiftVersionedAttr {{.+}} Implicit 3.0 +// CHECK-UNVERSIONED-DUMP-NEXT: SwiftImportAsNonGenericAttr {{.+}} Implicit + // CHECK-DUMP-NOT: Dumping // CHECK-UNVERSIONED: void acceptClosure(void (^block)(void) __attribute__((noescape))); From e5c8915c8cb2ecd05f4a28cc031605a7a2d1b33e Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 24 Jan 2017 11:19:17 +0000 Subject: [PATCH 140/585] [TableGen] Fix the attr argument for SwiftVersioned attribute following the changes in r292868. apple-llvm-split-commit: 647ba8e8cc8ff1e6c8ad448f62e339457c2d1266 apple-llvm-split-dir: clang/ --- clang/utils/TableGen/ClangAttrEmitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index 2912ba3b8539b..44daf05c0bd59 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -1168,7 +1168,7 @@ namespace { void writePCHReadDecls(raw_ostream &OS) const override { OS << " AttrVec vec;\n" - " ReadAttributes(F, vec, Record, Idx);\n" + " ReadAttributes(Record, vec);\n" " assert(vec.size() == 1);\n" " Attr *" << getLowerName() << " = vec.front();"; } From 5c7d1f9a385d8ecc116fd6a3e49a418c9616228d Mon Sep 17 00:00:00 2001 From: Hugh Bellamy Date: Sat, 28 Jan 2017 15:30:15 +0000 Subject: [PATCH 141/585] Port swift specific compiler-rt code to Windows https://github.com/apple/swift-compiler-rt/pull/5 apple-llvm-split-commit: ac64ee693098615eaeceb290bc59a83f4455b167 apple-llvm-split-dir: compiler-rt/ --- compiler-rt/lib/profile/InstrProfilingFile.c | 11 ++--------- compiler-rt/lib/profile/InstrProfilingUtil.c | 19 +++++++++++++++++++ compiler-rt/lib/profile/InstrProfilingUtil.h | 2 ++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/compiler-rt/lib/profile/InstrProfilingFile.c b/compiler-rt/lib/profile/InstrProfilingFile.c index 2c3add347d576..7c01e8d2fc475 100644 --- a/compiler-rt/lib/profile/InstrProfilingFile.c +++ b/compiler-rt/lib/profile/InstrProfilingFile.c @@ -269,16 +269,9 @@ static void exitSignalHandler(int sig) { static void installExitSignalHandlers(void) { unsigned I; - struct sigaction sigact; - int err; for (I = 0; I < lprofCurFilename.NumExitSignals; ++I) { - memset(&sigact, 0, sizeof(sigact)); - sigact.sa_handler = exitSignalHandler; - err = sigaction(lprofCurFilename.ExitOnSignals[I], &sigact, NULL); - if (err) - PROF_WARN( - "Unable to install an exit signal handler for %d (errno = %d).\n", - lprofCurFilename.ExitOnSignals[I], err); + lprofInstallSignalHandler(lprofCurFilename.ExitOnSignals[I], + exitSignalHandler); } } diff --git a/compiler-rt/lib/profile/InstrProfilingUtil.c b/compiler-rt/lib/profile/InstrProfilingUtil.c index 321c7192cc60f..f891b041c20fb 100644 --- a/compiler-rt/lib/profile/InstrProfilingUtil.c +++ b/compiler-rt/lib/profile/InstrProfilingUtil.c @@ -26,6 +26,7 @@ #include #endif +#include #include #include @@ -219,3 +220,21 @@ COMPILER_RT_VISIBILITY const char *lprofFindLastDirSeparator(const char *Path) { #endif return Sep; } + +COMPILER_RT_VISIBILITY void lprofInstallSignalHandler(int sig, + void (*handler)(int)) { +#ifdef _WIN32 + void (*err)(int) = signal(sig, handler); + if (err == SIG_ERR) + PROF_WARN("Unable to install an exit signal handler for %d (errno = %d).\n", + sig, errno); +#else + struct sigaction sigact; + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_handler = handler; + int err = sigaction(sig, &sigact, NULL); + if (err) + PROF_WARN("Unable to install an exit signal handler for %d (errno = %d).\n", + sig, err); +#endif +} diff --git a/compiler-rt/lib/profile/InstrProfilingUtil.h b/compiler-rt/lib/profile/InstrProfilingUtil.h index a80fde77e16a2..679e55243dcdb 100644 --- a/compiler-rt/lib/profile/InstrProfilingUtil.h +++ b/compiler-rt/lib/profile/InstrProfilingUtil.h @@ -51,4 +51,6 @@ int lprofGetHostName(char *Name, int Len); unsigned lprofBoolCmpXchg(void **Ptr, void *OldV, void *NewV); void *lprofPtrFetchAdd(void **Mem, long ByteIncr); +void lprofInstallSignalHandler(int sig, void(*handler)(int)); + #endif /* PROFILE_INSTRPROFILINGUTIL_H */ From 3ec8379f35272dd3500e372d69c05ae83fd7d397 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Fri, 24 Mar 2017 17:17:14 -0700 Subject: [PATCH 142/585] [APINotes] Don't apply API notes to non-definitions of structs. (#69) There are two reasons for this: - It doesn't really seem appropriate for forward-declared structs to pick up attributes. - In a proper definition `struct X { ... }`, the first `struct X` counts as a declaration, which means the API note attributes can get added ahead of time. Later on, when we try to add them /again/, they'll replace the attribute added at the first point rather than whatever the user wrote in source after the close brace. This also applies if there are any forward declarations before the definition. There is a downside: API notes now can't affect tag types that are /never/ defined (for example, structs that are forward-declared for use in pointers). Any annotations one might want to apply to such a type must be written in source. rdar://problem/29981732 apple-llvm-split-commit: 911df2f93842d956d2848d8c10366b742cc4eef2 apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaDecl.cpp | 1 - .../SimpleKit.framework/Headers/SimpleKit.apinotes | 6 ++++++ .../Frameworks/SimpleKit.framework/Headers/SimpleKit.h | 7 +++++++ .../SimpleKit.framework/Modules/module.modulemap | 5 +++++ clang/test/APINotes/types.m | 7 ++++++- 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Headers/SimpleKit.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Headers/SimpleKit.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Modules/module.modulemap diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index a8369e794e873..90721e5ddc535 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -13649,7 +13649,6 @@ Decl *Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, if (Attr) ProcessDeclAttributeList(S, New, Attr); - ProcessAPINotes(New); // If this has an identifier, add it to the scope stack. if (TUK == TUK_Friend) { diff --git a/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Headers/SimpleKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Headers/SimpleKit.apinotes new file mode 100644 index 0000000000000..336f1685703c6 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Headers/SimpleKit.apinotes @@ -0,0 +1,6 @@ +Name: SimpleKit +Tags: +- Name: RenamedAgainInAPINotesA + SwiftName: SuccessfullyRenamedA +- Name: RenamedAgainInAPINotesB + SwiftName: SuccessfullyRenamedB diff --git a/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Headers/SimpleKit.h b/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Headers/SimpleKit.h new file mode 100644 index 0000000000000..c30a1e75641b8 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Headers/SimpleKit.h @@ -0,0 +1,7 @@ +struct RenamedAgainInAPINotesA { + int field; +} __attribute__((swift_name("bad"))); + +struct __attribute__((swift_name("bad"))) RenamedAgainInAPINotesB { + int field; +}; diff --git a/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Modules/module.modulemap b/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Modules/module.modulemap new file mode 100644 index 0000000000000..2d07e76c0a142 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/SimpleKit.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module SimpleKit { + umbrella header "SimpleKit.h" + export * + module * { export * } +} diff --git a/clang/test/APINotes/types.m b/clang/test/APINotes/types.m index fccff8092cf8e..9f8b970cde06f 100644 --- a/clang/test/APINotes/types.m +++ b/clang/test/APINotes/types.m @@ -1,7 +1,12 @@ // RUN: rm -rf %t && mkdir -p %t -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fapinotes-cache-path=%t/APINotesCache -fdisable-module-hash -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -verify +// RUN: %clang_cc1 -ast-print %t/ModulesCache/SimpleKit.pcm | FileCheck %s #import +#import + +// CHECK: struct __attribute__((swift_name("SuccessfullyRenamedA"))) RenamedAgainInAPINotesA { +// CHECK: struct __attribute__((swift_name("SuccessfullyRenamedB"))) RenamedAgainInAPINotesB { void test(OverriddenTypes *overridden) { int *ip1 = global_int_ptr; // expected-warning{{incompatible pointer types initializing 'int *' with an expression of type 'double (*)(int, int)'}} From 3234acd649918f5c5c1544568bf9244d096d9f76 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 3 Apr 2017 10:15:52 -0700 Subject: [PATCH 143/585] [Swift SE-0160] Add swift_objc_members attribute for Objective-C classes. This attribute maps directly to Swift's new @objcMembers attribute; it is unused in Objective-C. Attribute part of rdar://problem/31371251. apple-llvm-split-commit: 6d10ac1c433dff0be863a3be68e5f993c3f831b3 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Attr.td | 6 ++++++ clang/include/clang/Basic/AttrDocs.td | 7 +++++++ clang/lib/Sema/SemaDeclAttr.cpp | 3 +++ clang/test/SemaObjC/attr-swift_objc_members.m | 17 +++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 clang/test/SemaObjC/attr-swift_objc_members.m diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 6e4a4d6d2bbc1..0bb2e33d14df2 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1509,6 +1509,12 @@ def SwiftBridge : Attr { let Documentation = [SwiftBridgeDocs]; } +def SwiftObjCMembers : Attr { + let Spellings = [GNU<"swift_objc_members">]; + let Subjects = SubjectList<[ObjCInterface], ErrorDiag>; + let Documentation = [SwiftObjCMembersDocs]; +} + def SwiftError : InheritableAttr { let Spellings = [GCC<"swift_error">]; let Args = [EnumArgument<"Convention", "ConventionKind", diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index dd1f4e91d16f9..e1e292b6c2deb 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -2505,6 +2505,13 @@ The ``swift_bridge`` attribute indicates that the type to which the attribute ap }]; } +def SwiftObjCMembersDocs : Documentation { + let Category = SwiftDocs; + let Content = [{ +The ``swift_objc_members`` attribute maps to the Swift ``@objcMembers`` attribute, which indicates that Swift members of this class, its subclasses, and all of the extensions thereof, will implicitly be exposed back to Objective-C. + }]; +} + def SwiftErrorDocs : Documentation { let Category = SwiftDocs; let Heading = "swift_error"; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 9624b9f8edb6b..a5d72c5ce72c6 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6950,6 +6950,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_SwiftBridge: handleSwiftBridgeAttr(S, D, Attr); break; + case AttributeList::AT_SwiftObjCMembers: + handleSimpleAttribute(S, D, Attr); + break; case AttributeList::AT_SwiftNewtype: handleSwiftNewtypeAttr(S, D, Attr); break; diff --git a/clang/test/SemaObjC/attr-swift_objc_members.m b/clang/test/SemaObjC/attr-swift_objc_members.m new file mode 100644 index 0000000000000..c8781f876da38 --- /dev/null +++ b/clang/test/SemaObjC/attr-swift_objc_members.m @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -verify -fsyntax-only %s + +#if !__has_attribute(swift_objc_members) +# error Cannot query presence of swift_objc_members attribute. +#endif + +__attribute__((swift_objc_members)) +__attribute__((objc_root_class)) +@interface A +@end + +__attribute__((swift_objc_members)) // expected-error{{'swift_objc_members' attribute only applies to Objective-C interfaces}} +@protocol P +@end + +__attribute__((swift_objc_members)) // expected-error{{'swift_objc_members' attribute only applies to Objective-C interfaces}} +extern void foo(void); From 8a4d5cc26490602152d9aa6d8d04c0e611f95f48 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 3 Apr 2017 10:54:38 -0700 Subject: [PATCH 144/585] [API Notes] Add support for swift_objc_members attribute as SwiftObjCMembers. Finishes rdar://problem/31371251. apple-llvm-split-commit: 83bd45deb00d73d9b888461bc6cdc2c6af5d1f23 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 26 +++++++++++++++++-- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 4 +++ clang/lib/APINotes/APINotesWriter.cpp | 4 +++ clang/lib/APINotes/APINotesYAMLCompiler.cpp | 5 ++++ clang/lib/Sema/SemaAPINotes.cpp | 7 +++++ .../Headers/VersionedKit.apinotes | 2 ++ clang/test/APINotes/Inputs/roundtrip.apinotes | 2 ++ clang/test/APINotes/versioned.m | 5 ++++ 9 files changed, 54 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index b81d273e9521d..725711ea2e112 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -215,6 +215,9 @@ class ObjCContextInfo : public CommonTypeInfo { unsigned SwiftImportAsNonGenericSpecified : 1; unsigned SwiftImportAsNonGeneric : 1; + unsigned SwiftObjCMembersSpecified : 1; + unsigned SwiftObjCMembers : 1; + public: ObjCContextInfo() : CommonTypeInfo(), @@ -222,7 +225,9 @@ class ObjCContextInfo : public CommonTypeInfo { DefaultNullability(0), HasDesignatedInits(0), SwiftImportAsNonGenericSpecified(false), - SwiftImportAsNonGeneric(false) + SwiftImportAsNonGeneric(false), + SwiftObjCMembersSpecified(false), + SwiftObjCMembers(false) { } /// Determine the default nullability for properties and methods of this @@ -260,6 +265,16 @@ class ObjCContextInfo : public CommonTypeInfo { } } + Optional getSwiftObjCMembers() const { + if (SwiftObjCMembersSpecified) + return SwiftObjCMembers; + return None; + } + void setSwiftObjCMembers(Optional value) { + SwiftObjCMembersSpecified = value.hasValue(); + SwiftObjCMembers = value.hasValue() ? *value : false; + } + /// Strip off any information within the class information structure that is /// module-local, such as 'audited' flags. void stripModuleLocalInfo() { @@ -271,7 +286,9 @@ class ObjCContextInfo : public CommonTypeInfo { return static_cast(lhs) == rhs && lhs.getDefaultNullability() == rhs.getDefaultNullability() && lhs.HasDesignatedInits == rhs.HasDesignatedInits && - lhs.getSwiftImportAsNonGeneric() == rhs.getSwiftImportAsNonGeneric(); + lhs.getSwiftImportAsNonGeneric() == + rhs.getSwiftImportAsNonGeneric() && + lhs.getSwiftObjCMembers() == rhs.getSwiftObjCMembers(); } friend bool operator!=(const ObjCContextInfo &lhs, const ObjCContextInfo &rhs) { @@ -296,6 +313,11 @@ class ObjCContextInfo : public CommonTypeInfo { lhs.SwiftImportAsNonGeneric = rhs.SwiftImportAsNonGeneric; } + if (!lhs.SwiftObjCMembersSpecified && rhs.SwiftObjCMembersSpecified) { + lhs.SwiftObjCMembersSpecified = true; + lhs.SwiftObjCMembers = rhs.SwiftObjCMembers; + } + lhs.HasDesignatedInits |= rhs.HasDesignatedInits; return lhs; diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 1e257f3ada3a5..5a0cdbdd050b7 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 22; // SwiftImportAsNonGeneric +const uint16_t VERSION_MINOR = 23; // SwiftObjCMembers using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 587903fccfb7b..d9a4297d9139b 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -255,6 +255,10 @@ namespace { info.setDefaultNullability(static_cast(payload&0x03)); payload >>= 3; + if (payload & (1 << 1)) + info.setSwiftObjCMembers(payload & 1); + payload >>= 2; + if (payload & (1 << 1)) info.setSwiftImportAsNonGeneric(payload & 1); diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 65edaf9a5946c..2331cd305f123 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -587,6 +587,10 @@ namespace { if (auto swiftImportAsNonGeneric = info.getSwiftImportAsNonGeneric()) { payload |= (0x01 << 1) | swiftImportAsNonGeneric.getValue(); } + payload <<= 2; + if (auto swiftObjCMembers = info.getSwiftObjCMembers()) { + payload |= (0x01 << 1) | swiftObjCMembers.getValue(); + } payload <<= 3; if (auto nullable = info.getDefaultNullability()) { payload |= (0x01 << 2) | static_cast(*nullable); diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index c96764617062c..02b2ba1e86694 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -217,6 +217,7 @@ namespace { Optional SwiftBridge; Optional NSErrorDomain; Optional SwiftImportAsNonGeneric; + Optional SwiftObjCMembers; MethodsSeq Methods; PropertiesSeq Properties; }; @@ -447,6 +448,7 @@ namespace llvm { io.mapOptional("SwiftBridge", c.SwiftBridge); io.mapOptional("NSErrorDomain", c.NSErrorDomain); io.mapOptional("SwiftImportAsNonGeneric", c.SwiftImportAsNonGeneric); + io.mapOptional("SwiftObjCMembers", c.SwiftObjCMembers); io.mapOptional("Methods", c.Methods); io.mapOptional("Properties", c.Properties); } @@ -756,6 +758,8 @@ namespace { cInfo.setDefaultNullability(*DefaultNullability); if (cl.SwiftImportAsNonGeneric) cInfo.setSwiftImportAsNonGeneric(*cl.SwiftImportAsNonGeneric); + if (cl.SwiftObjCMembers) + cInfo.setSwiftObjCMembers(*cl.SwiftObjCMembers); ContextID clID = Writer->addObjCContext(cl.Name, isClass, cInfo, swiftVersion); @@ -1106,6 +1110,7 @@ namespace { handleCommonType(record, info); record.SwiftImportAsNonGeneric = info.getSwiftImportAsNonGeneric(); + record.SwiftObjCMembers = info.getSwiftObjCMembers(); if (info.getDefaultNullability()) { record.AuditedForNullability = true; diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index f58691181f1cd..59016284861a0 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -610,6 +610,13 @@ static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, }); } + if (auto objcMembers = info.getSwiftObjCMembers()) { + handleAPINotedAttribute(S, D, *objcMembers, + metadata, [&] { + return SwiftObjCMembersAttr::CreateImplicit(S.Context); + }); + } + // Handle information common to Objective-C classes and protocols. ProcessAPINotes(S, static_cast(D), info, metadata); diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index bd09953dcda96..394b15689fe47 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -1,6 +1,7 @@ Name: VersionedKit Classes: - Name: TestProperties + SwiftObjCMembers: true Properties: - Name: accessorsOnly PropertyKind: Instance @@ -22,6 +23,7 @@ SwiftVersions: - Name: TestGenericDUMP SwiftImportAsNonGeneric: true - Name: TestProperties + SwiftObjCMembers: false Properties: - Name: accessorsOnlyInVersion3 PropertyKind: Instance diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 4895060813798..3b493e1ba046d 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -10,6 +10,7 @@ Classes: SwiftPrivate: false SwiftName: '' SwiftImportAsNonGeneric: true + SwiftObjCMembers: false Methods: - Selector: 'cellWithImage:' MethodKind: Class @@ -61,6 +62,7 @@ Classes: SwiftPrivate: false SwiftName: '' SwiftBridge: View + SwiftObjCMembers: true Methods: - Selector: 'addSubview:' MethodKind: Instance diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index 29800b623c198..21a783e180233 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -54,3 +54,8 @@ // CHECK-VERSIONED: void privateFunc(); // CHECK-VERSIONED: typedef double MyDoubleWrapper; + +// CHECK-UNVERSIONED: __attribute__((swift_objc_members) +// CHECK-UNVERSIONED-NEXT: @interface TestProperties +// CHECK-VERSIONED-NOT: __attribute__((swift_objc_members) +// CHECK-VERSIONED: @interface TestProperties From bff373f65cb3f76336d1ad22c9375b390db62b80 Mon Sep 17 00:00:00 2001 From: Ahmed Bougacha Date: Tue, 18 Apr 2017 15:03:22 -0700 Subject: [PATCH 145/585] Update pragma-attribute-supported-attributes-list test for swift attributes. This was added in r300556. Update the test to also check swift-specific attributes. apple-llvm-split-commit: 2b4f2133d3353410da12c969a100b054df66b772 apple-llvm-split-dir: clang/ --- .../Misc/pragma-attribute-supported-attributes-list.test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index b1f2e66ab3397..1e1c4ce7bdd6e 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -2,7 +2,7 @@ // The number of supported attributes should never go down! -// CHECK: #pragma clang attribute supports 57 attributes: +// CHECK: #pragma clang attribute supports 60 attributes: // CHECK-NEXT: AMDGPUFlatWorkGroupSize (SubjectMatchRule_function) // CHECK-NEXT: AMDGPUNumSGPR (SubjectMatchRule_function) // CHECK-NEXT: AMDGPUNumVGPR (SubjectMatchRule_function) @@ -52,8 +52,11 @@ // CHECK-NEXT: Section (SubjectMatchRule_function, SubjectMatchRule_variable_is_global, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property) // CHECK-NEXT: SetTypestate (SubjectMatchRule_function_is_member) // CHECK-NEXT: SwiftContext (SubjectMatchRule_variable_is_parameter) +// CHECK-NEXT: SwiftError (SubjectMatchRule_objc_method, SubjectMatchRule_function) // CHECK-NEXT: SwiftErrorResult (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: SwiftIndirectResult (SubjectMatchRule_variable_is_parameter) +// CHECK-NEXT: SwiftNewtype (SubjectMatchRule_type_alias) +// CHECK-NEXT: SwiftObjCMembers (SubjectMatchRule_objc_interface) // CHECK-NEXT: TLSModel (SubjectMatchRule_variable_is_thread_local) // CHECK-NEXT: Target (SubjectMatchRule_function) // CHECK-NEXT: TestTypestate (SubjectMatchRule_function_is_member) From 373cda05981dcb3bb4803eeaea55b724d0e68f67 Mon Sep 17 00:00:00 2001 From: Ahmed Bougacha Date: Tue, 18 Apr 2017 15:46:32 -0700 Subject: [PATCH 146/585] Undo duplicated call after bad merge (82c834e628). The fact that this was *removed* on one side but was still present in the common ancestor (which is pretty unusual) confused me =/ rdar://31692170 apple-llvm-split-commit: 43cb0052daef91970c242a7ff6e5a774926c6227 apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaDeclObjC.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 6349c7d700e6b..f7c502946ceaf 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -995,8 +995,6 @@ ActOnStartClassInterface(Scope *S, SourceLocation AtInterfaceLoc, } } - if (AttrList) - ProcessDeclAttributeList(TUScope, IDecl, AttrList); AddPragmaAttributes(TUScope, IDecl); PushOnScopeChains(IDecl, TUScope); From 45050445b4b1681d559659280c8eaaacee3fdeb7 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 13 Apr 2017 20:12:37 -0700 Subject: [PATCH 147/585] [APINotes] Add 'EnumExtensibility' and 'FlagEnum' API notes. These map to the enum_extensibility and flag_enum attributes. EnumExtensibility has to cover three states of "open", "closed", and "none" because the presence of the attribute (with either an "open" or "closed" argument) indicates auditing of the enum. rdar://problem/29201690 apple-llvm-split-commit: 7c7f69ae64654547806e2b925987d82e4d76ce61 apple-llvm-split-dir: clang/ --- clang/include/clang/APINotes/Types.h | 58 +++++++++++++++- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 11 +++ clang/lib/APINotes/APINotesWriter.cpp | 27 +++++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 17 +++++ clang/lib/Sema/SemaAPINotes.cpp | 27 ++++++++ .../Headers/VersionedKit.apinotes | 17 +++++ .../Headers/VersionedKit.h | 45 ++++++++++++ clang/test/APINotes/Inputs/roundtrip.apinotes | 2 + clang/test/APINotes/versioned.m | 69 +++++++++++++++++++ 10 files changed, 272 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 725711ea2e112..2213bad29a4d1 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -657,10 +657,54 @@ class EnumConstantInfo : public CommonEntityInfo { EnumConstantInfo() : CommonEntityInfo() { } }; +/// The payload for an enum_extensibility attribute. This is a tri-state rather +/// than just a boolean because the presence of the attribute indicates +/// auditing. +enum class EnumExtensibilityKind { + None, + Open, + Closed, +}; + /// Describes API notes data for a tag. class TagInfo : public CommonTypeInfo { + unsigned HasFlagEnum : 1; + unsigned IsFlagEnum : 1; public: - TagInfo() : CommonTypeInfo() { } + Optional EnumExtensibility; + + Optional isFlagEnum() const { + if (HasFlagEnum) + return IsFlagEnum; + return None; + } + void setFlagEnum(Optional Value) { + if (Value.hasValue()) { + HasFlagEnum = true; + IsFlagEnum = Value.getValue(); + } else { + HasFlagEnum = false; + } + } + + TagInfo() : CommonTypeInfo(), HasFlagEnum(0), IsFlagEnum(0) { } + + friend TagInfo &operator|=(TagInfo &lhs, const TagInfo &rhs) { + lhs |= static_cast(rhs); + if (!lhs.HasFlagEnum && rhs.HasFlagEnum) { + lhs.HasFlagEnum = true; + lhs.IsFlagEnum = rhs.IsFlagEnum; + } + if (!lhs.EnumExtensibility.hasValue() && rhs.EnumExtensibility.hasValue()) + lhs.EnumExtensibility = rhs.EnumExtensibility; + return lhs; + } + + friend bool operator==(const TagInfo &lhs, const TagInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.isFlagEnum() == rhs.isFlagEnum() && + lhs.EnumExtensibility == rhs.EnumExtensibility; + } }; /// The kind of a swift_wrapper/swift_newtype. @@ -676,6 +720,18 @@ class TypedefInfo : public CommonTypeInfo { Optional SwiftWrapper; TypedefInfo() : CommonTypeInfo() { } + + friend TypedefInfo &operator|=(TypedefInfo &lhs, const TypedefInfo &rhs) { + lhs |= static_cast(rhs); + if (!lhs.SwiftWrapper.hasValue() && rhs.SwiftWrapper.hasValue()) + lhs.SwiftWrapper = rhs.SwiftWrapper; + return lhs; + } + + friend bool operator==(const TypedefInfo &lhs, const TypedefInfo &rhs) { + return static_cast(lhs) == rhs && + lhs.SwiftWrapper == rhs.SwiftWrapper; + } }; /// Descripts a series of options for a module diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 5a0cdbdd050b7..3e1b2d56ec8de 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -36,7 +36,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 23; // SwiftObjCMembers +const uint16_t VERSION_MINOR = 24; // EnumExtensibility+FlagEnum using IdentifierID = PointerEmbeddedInt; using IdentifierIDField = BCVBR<16>; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index d9a4297d9139b..0b802b487e585 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -482,6 +482,17 @@ namespace { static TagInfo readUnversioned(internal_key_type key, const uint8_t *&data) { TagInfo info; + + uint8_t payload = *data++; + if (payload & 1) { + info.setFlagEnum(payload & 2); + } + payload >>= 2; + if (payload > 0) { + info.EnumExtensibility = + static_cast((payload & 0x3) - 1); + } + readCommonTypeInfo(data, info); return info; } diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 2331cd305f123..285a35efe205a 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -1056,7 +1056,32 @@ namespace { }; /// Used to serialize the on-disk tag table. - class TagTableInfo : public CommonTypeTableInfo { }; + class TagTableInfo : public CommonTypeTableInfo { + public: + unsigned getUnversionedInfoSize(const TagInfo &info) { + return 1 + getCommonTypeInfoSize(info); + } + + void emitUnversionedInfo(raw_ostream &out, const TagInfo &info) { + endian::Writer writer(out); + + uint8_t payload = 0; + if (auto enumExtensibility = info.EnumExtensibility) { + payload |= static_cast(enumExtensibility.getValue()) + 1; + assert((payload < (1 << 2)) && "must fit in two bits"); + } + + payload <<= 2; + if (Optional value = info.isFlagEnum()) { + payload |= 1 << 0; + payload |= value.getValue() << 1; + } + + writer.write(payload); + + emitCommonTypeInfo(out, info); + } + }; } // end anonymous namespace diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 02b2ba1e86694..db71f7f3a0cb7 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -261,6 +261,8 @@ namespace { Optional SwiftPrivate; Optional SwiftBridge; Optional NSErrorDomain; + Optional EnumExtensibility; + Optional FlagEnum; }; typedef std::vector TagsSeq; @@ -370,6 +372,15 @@ namespace llvm { } }; + template<> + struct ScalarEnumerationTraits { + static void enumeration(IO &io, api_notes::EnumExtensibilityKind &value) { + io.enumCase(value, "none", api_notes::EnumExtensibilityKind::None); + io.enumCase(value, "open", api_notes::EnumExtensibilityKind::Open); + io.enumCase(value, "closed", api_notes::EnumExtensibilityKind::Closed); + } + }; + template <> struct ScalarTraits { static void output(const VersionTuple &value, void*, @@ -505,6 +516,8 @@ namespace llvm { io.mapOptional("SwiftName", t.SwiftName); io.mapOptional("SwiftBridge", t.SwiftBridge); io.mapOptional("NSErrorDomain", t.NSErrorDomain); + io.mapOptional("EnumExtensibility", t.EnumExtensibility); + io.mapOptional("FlagEnum", t.FlagEnum); } }; @@ -928,6 +941,8 @@ namespace { TagInfo tagInfo; if (convertCommonType(t, tagInfo, t.Name)) continue; + tagInfo.EnumExtensibility = t.EnumExtensibility; + tagInfo.setFlagEnum(t.FlagEnum); Writer->addTag(t.Name, tagInfo, swiftVersion); } @@ -1286,6 +1301,8 @@ namespace { Tag tag; tag.Name = name; handleCommonType(tag, info); + tag.EnumExtensibility = info.EnumExtensibility; + tag.FlagEnum = info.isFlagEnum(); auto &items = getTopLevelItems(swiftVersion); items.Tags.push_back(tag); } diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 59016284861a0..5c171ef027f0a 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -548,6 +548,33 @@ static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, static void ProcessAPINotes(Sema &S, TagDecl *D, const api_notes::TagInfo &info, VersionedInfoMetadata metadata) { + if (auto extensibility = info.EnumExtensibility) { + using api_notes::EnumExtensibilityKind; + bool shouldAddAttribute = (*extensibility != EnumExtensibilityKind::None); + handleAPINotedAttribute(S, D, shouldAddAttribute, + metadata, [&] { + EnumExtensibilityAttr::Kind kind; + switch (extensibility.getValue()) { + case EnumExtensibilityKind::None: + llvm_unreachable("remove only"); + case EnumExtensibilityKind::Open: + kind = EnumExtensibilityAttr::Open; + break; + case EnumExtensibilityKind::Closed: + kind = EnumExtensibilityAttr::Closed; + break; + } + return EnumExtensibilityAttr::CreateImplicit(S.Context, kind); + }); + } + + if (auto flagEnum = info.isFlagEnum()) { + handleAPINotedAttribute(S, D, flagEnum.getValue(), metadata, + [&] { + return FlagEnumAttr::CreateImplicit(S.Context); + }); + } + // Handle common type information. ProcessAPINotes(S, D, static_cast(info), metadata); diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index 394b15689fe47..3aa7b992059a5 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -15,6 +15,13 @@ Classes: - Name: accessorsOnlyForClassExceptInVersion3 PropertyKind: Class SwiftImportAsAccessors: true +Tags: + - Name: APINotedFlagEnum + FlagEnum: true + - Name: APINotedOpenEnum + EnumExtensibility: open + - Name: APINotedClosedEnum + EnumExtensibility: closed SwiftVersions: - Version: 3.0 Classes: @@ -49,6 +56,16 @@ SwiftVersions: Tags: - Name: MyErrorCode NSErrorDomain: '' + - Name: NewlyFlagEnum + FlagEnum: false + - Name: OpenToClosedEnum + EnumExtensibility: open + - Name: ClosedToOpenEnum + EnumExtensibility: closed + - Name: NewlyClosedEnum + EnumExtensibility: none + - Name: NewlyOpenEnum + EnumExtensibility: none Typedefs: - Name: MyDoubleWrapper SwiftWrapper: none diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h index 8301bd1f10a1c..634ffddad9769 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -35,3 +35,48 @@ typedef double MyDoubleWrapper __attribute__((swift_wrapper(struct))); @interface TestGenericDUMP : Base - (Element)element; @end + + +enum __attribute__((flag_enum)) FlagEnum { + FlagEnumA = 1, + FlagEnumB = 2 +}; + +enum __attribute__((flag_enum)) NewlyFlagEnum { + NewlyFlagEnumA = 1, + NewlyFlagEnumB = 2 +}; + +enum APINotedFlagEnum { + APINotedFlagEnumA = 1, + APINotedFlagEnumB = 2 +}; + + +enum __attribute__((enum_extensibility(open))) OpenEnum { + OpenEnumA = 1, +}; + +enum __attribute__((enum_extensibility(open))) NewlyOpenEnum { + NewlyOpenEnumA = 1, +}; + +enum __attribute__((enum_extensibility(closed))) NewlyClosedEnum { + NewlyClosedEnumA = 1, +}; + +enum __attribute__((enum_extensibility(open))) ClosedToOpenEnum { + ClosedToOpenEnumA = 1, +}; + +enum __attribute__((enum_extensibility(closed))) OpenToClosedEnum { + OpenToClosedEnumA = 1, +}; + +enum APINotedOpenEnum { + APINotedOpenEnumA = 1, +}; + +enum APINotedClosedEnum { + APINotedClosedEnumA = 1, +}; diff --git a/clang/test/APINotes/Inputs/roundtrip.apinotes b/clang/test/APINotes/Inputs/roundtrip.apinotes index 3b493e1ba046d..65e39283cd8d5 100644 --- a/clang/test/APINotes/Inputs/roundtrip.apinotes +++ b/clang/test/APINotes/Inputs/roundtrip.apinotes @@ -138,6 +138,8 @@ Tags: SwiftPrivate: false SwiftName: SomeEnum NSErrorDomain: some_error_domain + EnumExtensibility: closed + FlagEnum: false - Name: NSSomeStruct Availability: available AvailabilityMsg: '' diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index 21a783e180233..fe79ca4887753 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -59,3 +59,72 @@ // CHECK-UNVERSIONED-NEXT: @interface TestProperties // CHECK-VERSIONED-NOT: __attribute__((swift_objc_members) // CHECK-VERSIONED: @interface TestProperties + +// CHECK-UNVERSIONED-LABEL: enum FlagEnum { +// CHECK-UNVERSIONED-NEXT: FlagEnumA = 1, +// CHECK-UNVERSIONED-NEXT: FlagEnumB = 2 +// CHECK-UNVERSIONED-NEXT: } __attribute__((flag_enum)); +// CHECK-UNVERSIONED-LABEL: enum NewlyFlagEnum { +// CHECK-UNVERSIONED-NEXT: NewlyFlagEnumA = 1, +// CHECK-UNVERSIONED-NEXT: NewlyFlagEnumB = 2 +// CHECK-UNVERSIONED-NEXT: } __attribute__((flag_enum)); +// CHECK-UNVERSIONED-LABEL: enum APINotedFlagEnum { +// CHECK-UNVERSIONED-NEXT: APINotedFlagEnumA = 1, +// CHECK-UNVERSIONED-NEXT: APINotedFlagEnumB = 2 +// CHECK-UNVERSIONED-NEXT: } __attribute__((flag_enum)); +// CHECK-UNVERSIONED-LABEL: enum OpenEnum { +// CHECK-UNVERSIONED-NEXT: OpenEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-UNVERSIONED-LABEL: enum NewlyOpenEnum { +// CHECK-UNVERSIONED-NEXT: NewlyOpenEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-UNVERSIONED-LABEL: enum NewlyClosedEnum { +// CHECK-UNVERSIONED-NEXT: NewlyClosedEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("closed"))); +// CHECK-UNVERSIONED-LABEL: enum ClosedToOpenEnum { +// CHECK-UNVERSIONED-NEXT: ClosedToOpenEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-UNVERSIONED-LABEL: enum OpenToClosedEnum { +// CHECK-UNVERSIONED-NEXT: OpenToClosedEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("closed"))); +// CHECK-UNVERSIONED-LABEL: enum APINotedOpenEnum { +// CHECK-UNVERSIONED-NEXT: APINotedOpenEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-UNVERSIONED-LABEL: enum APINotedClosedEnum { +// CHECK-UNVERSIONED-NEXT: APINotedClosedEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("closed"))); + +// CHECK-VERSIONED-LABEL: enum FlagEnum { +// CHECK-VERSIONED-NEXT: FlagEnumA = 1, +// CHECK-VERSIONED-NEXT: FlagEnumB = 2 +// CHECK-VERSIONED-NEXT: } __attribute__((flag_enum)); +// CHECK-VERSIONED-LABEL: enum NewlyFlagEnum { +// CHECK-VERSIONED-NEXT: NewlyFlagEnumA = 1, +// CHECK-VERSIONED-NEXT: NewlyFlagEnumB = 2 +// CHECK-VERSIONED-NEXT: }; +// CHECK-VERSIONED-LABEL: enum APINotedFlagEnum { +// CHECK-VERSIONED-NEXT: APINotedFlagEnumA = 1, +// CHECK-VERSIONED-NEXT: APINotedFlagEnumB = 2 +// CHECK-VERSIONED-NEXT: } __attribute__((flag_enum)); +// CHECK-VERSIONED-LABEL: enum OpenEnum { +// CHECK-VERSIONED-NEXT: OpenEnumA = 1 +// CHECK-VERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-VERSIONED-LABEL: enum NewlyOpenEnum { +// CHECK-VERSIONED-NEXT: NewlyOpenEnumA = 1 +// CHECK-VERSIONED-NEXT: }; +// CHECK-VERSIONED-LABEL: enum NewlyClosedEnum { +// CHECK-VERSIONED-NEXT: NewlyClosedEnumA = 1 +// CHECK-VERSIONED-NEXT: }; +// CHECK-VERSIONED-LABEL: enum ClosedToOpenEnum { +// CHECK-VERSIONED-NEXT: ClosedToOpenEnumA = 1 +// CHECK-VERSIONED-NEXT: } __attribute__((enum_extensibility("closed"))); +// CHECK-VERSIONED-LABEL: enum OpenToClosedEnum { +// CHECK-VERSIONED-NEXT: OpenToClosedEnumA = 1 +// CHECK-VERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-VERSIONED-LABEL: enum APINotedOpenEnum { +// CHECK-VERSIONED-NEXT: APINotedOpenEnumA = 1 +// CHECK-VERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-VERSIONED-LABEL: enum APINotedClosedEnum { +// CHECK-VERSIONED-NEXT: APINotedClosedEnumA = 1 +// CHECK-VERSIONED-NEXT: } __attribute__((enum_extensibility("closed"))); + From 5a375f34070fd3afe081f7a7d6162352048293a0 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 19 Apr 2017 20:17:59 -0700 Subject: [PATCH 148/585] [APINotes] Add 'EnumKind' as syntactic sugar. These accept camel-cased versions of the macros in Foundation and Core Foundation (plus a hypothetical "NSClosedEnum") as syntactic sugar for the settings FlagEnum and EnumExtensibility, matching the intent behind each macro. More rdar://problem/29201690 apple-llvm-split-commit: a9696c41f62007171574185d98e6ebc047479272 apple-llvm-split-dir: clang/ --- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 66 ++++++++++++++++++- .../Headers/VersionedKit.apinotes | 14 ++++ .../Headers/VersionedKit.h | 23 +++++++ clang/test/APINotes/versioned.m | 22 +++++++ clang/test/APINotes/yaml-reader-errors.c | 21 ++++++ 5 files changed, 144 insertions(+), 2 deletions(-) diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index db71f7f3a0cb7..f0f6cb8f95898 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -160,6 +160,18 @@ namespace { /// Treat as an initializer. AsInitializer }; + + /// Syntactic sugar for EnumExtensibility and FlagEnum + enum class EnumConvenienceAliasKind { + /// EnumExtensibility: none, FlagEnum: false + None, + /// EnumExtensibility: open, FlagEnum: false + CFEnum, + /// EnumExtensibility: open, FlagEnum: true + CFOptions, + /// EnumExtensibility: closed, FlagEnum: false + CFClosedEnum + }; struct AvailabilityItem { APIAvailability Mode = APIAvailability::Available; @@ -263,6 +275,7 @@ namespace { Optional NSErrorDomain; Optional EnumExtensibility; Optional FlagEnum; + Optional EnumConvenienceKind; }; typedef std::vector TagsSeq; @@ -381,6 +394,21 @@ namespace llvm { } }; + template<> + struct ScalarEnumerationTraits { + static void enumeration(IO &io, EnumConvenienceAliasKind &value) { + io.enumCase(value, "none", EnumConvenienceAliasKind::None); + io.enumCase(value, "CFEnum", EnumConvenienceAliasKind::CFEnum); + io.enumCase(value, "NSEnum", EnumConvenienceAliasKind::CFEnum); + io.enumCase(value, "CFOptions", EnumConvenienceAliasKind::CFOptions); + io.enumCase(value, "NSOptions", EnumConvenienceAliasKind::CFOptions); + io.enumCase(value, "CFClosedEnum", + EnumConvenienceAliasKind::CFClosedEnum); + io.enumCase(value, "NSClosedEnum", + EnumConvenienceAliasKind::CFClosedEnum); + } + }; + template <> struct ScalarTraits { static void output(const VersionTuple &value, void*, @@ -518,6 +546,7 @@ namespace llvm { io.mapOptional("NSErrorDomain", t.NSErrorDomain); io.mapOptional("EnumExtensibility", t.EnumExtensibility); io.mapOptional("FlagEnum", t.FlagEnum); + io.mapOptional("EnumKind", t.EnumConvenienceKind); } }; @@ -941,8 +970,41 @@ namespace { TagInfo tagInfo; if (convertCommonType(t, tagInfo, t.Name)) continue; - tagInfo.EnumExtensibility = t.EnumExtensibility; - tagInfo.setFlagEnum(t.FlagEnum); + + if (t.EnumConvenienceKind) { + if (t.EnumExtensibility) { + emitError(llvm::Twine( + "cannot mix EnumKind and EnumExtensibility (for ") + t.Name + + ")"); + continue; + } + if (t.FlagEnum) { + emitError(llvm::Twine("cannot mix EnumKind and FlagEnum (for ") + + t.Name + ")"); + continue; + } + switch (t.EnumConvenienceKind.getValue()) { + case EnumConvenienceAliasKind::None: + tagInfo.EnumExtensibility = EnumExtensibilityKind::None; + tagInfo.setFlagEnum(false); + break; + case EnumConvenienceAliasKind::CFEnum: + tagInfo.EnumExtensibility = EnumExtensibilityKind::Open; + tagInfo.setFlagEnum(false); + break; + case EnumConvenienceAliasKind::CFOptions: + tagInfo.EnumExtensibility = EnumExtensibilityKind::Open; + tagInfo.setFlagEnum(true); + break; + case EnumConvenienceAliasKind::CFClosedEnum: + tagInfo.EnumExtensibility = EnumExtensibilityKind::Closed; + tagInfo.setFlagEnum(false); + break; + } + } else { + tagInfo.EnumExtensibility = t.EnumExtensibility; + tagInfo.setFlagEnum(t.FlagEnum); + } Writer->addTag(t.Name, tagInfo, swiftVersion); } diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index 3aa7b992059a5..502e32ee87c9c 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -22,6 +22,20 @@ Tags: EnumExtensibility: open - Name: APINotedClosedEnum EnumExtensibility: closed + - Name: SoonToBeCFEnum + EnumKind: CFEnum + - Name: SoonToBeNSEnum + EnumKind: NSEnum + - Name: SoonToBeCFOptions + EnumKind: CFOptions + - Name: SoonToBeNSOptions + EnumKind: NSOptions + - Name: SoonToBeCFClosedEnum + EnumKind: CFClosedEnum + - Name: SoonToBeNSClosedEnum + EnumKind: NSClosedEnum + - Name: UndoAllThatHasBeenDoneToMe + EnumKind: none SwiftVersions: - Version: 3.0 Classes: diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h index 634ffddad9769..ef094516673af 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -80,3 +80,26 @@ enum APINotedOpenEnum { enum APINotedClosedEnum { APINotedClosedEnumA = 1, }; + + +enum SoonToBeCFEnum { + SoonToBeCFEnumA = 1 +}; +enum SoonToBeNSEnum { + SoonToBeNSEnumA = 1 +}; +enum SoonToBeCFOptions { + SoonToBeCFOptionsA = 1 +}; +enum SoonToBeNSOptions { + SoonToBeNSOptionsA = 1 +}; +enum SoonToBeCFClosedEnum { + SoonToBeCFClosedEnumA = 1 +}; +enum SoonToBeNSClosedEnum { + SoonToBeNSClosedEnumA = 1 +}; +enum UndoAllThatHasBeenDoneToMe { + UndoAllThatHasBeenDoneToMeA = 1 +} __attribute__((flag_enum)) __attribute__((enum_extensibility(closed))); diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index fe79ca4887753..87363e7f20fd3 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -128,3 +128,25 @@ // CHECK-VERSIONED-NEXT: APINotedClosedEnumA = 1 // CHECK-VERSIONED-NEXT: } __attribute__((enum_extensibility("closed"))); +// These don't actually have versioned information, so we just check them once. +// CHECK-UNVERSIONED-LABEL: enum SoonToBeCFEnum { +// CHECK-UNVERSIONED-NEXT: SoonToBeCFEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-UNVERSIONED-LABEL: enum SoonToBeNSEnum { +// CHECK-UNVERSIONED-NEXT: SoonToBeNSEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("open"))); +// CHECK-UNVERSIONED-LABEL: enum SoonToBeCFOptions { +// CHECK-UNVERSIONED-NEXT: SoonToBeCFOptionsA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("open"))) __attribute__((flag_enum)); +// CHECK-UNVERSIONED-LABEL: enum SoonToBeNSOptions { +// CHECK-UNVERSIONED-NEXT: SoonToBeNSOptionsA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("open"))) __attribute__((flag_enum)); +// CHECK-UNVERSIONED-LABEL: enum SoonToBeCFClosedEnum { +// CHECK-UNVERSIONED-NEXT: SoonToBeCFClosedEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("closed"))); +// CHECK-UNVERSIONED-LABEL: enum SoonToBeNSClosedEnum { +// CHECK-UNVERSIONED-NEXT: SoonToBeNSClosedEnumA = 1 +// CHECK-UNVERSIONED-NEXT: } __attribute__((enum_extensibility("closed"))); +// CHECK-UNVERSIONED-LABEL: enum UndoAllThatHasBeenDoneToMe { +// CHECK-UNVERSIONED-NEXT: UndoAllThatHasBeenDoneToMeA = 1 +// CHECK-UNVERSIONED-NEXT: }; diff --git a/clang/test/APINotes/yaml-reader-errors.c b/clang/test/APINotes/yaml-reader-errors.c index 3e01eaaef03cb..c02260bcf2510 100644 --- a/clang/test/APINotes/yaml-reader-errors.c +++ b/clang/test/APINotes/yaml-reader-errors.c @@ -63,3 +63,24 @@ AvailabilityMsg: iOSOnly AvailabilityMsg: iOSOnly - Name: globalVar2 Nullability: O +Tags: +# CHECK: cannot mix EnumKind and FlagEnum (for FlagAndEnumKind) + - Name: FlagAndEnumKind + FlagEnum: true + EnumKind: CFOptions +# CHECK: cannot mix EnumKind and FlagEnum (for FlagAndEnumKind2) + - Name: FlagAndEnumKind2 + EnumKind: CFOptions + FlagEnum: false +# CHECK: cannot mix EnumKind and EnumExtensibility (for ExtensibilityAndEnumKind) + - Name: ExtensibilityAndEnumKind + EnumExtensibility: open + EnumKind: CFOptions +# CHECK: cannot mix EnumKind and EnumExtensibility (for ExtensibilityAndEnumKind2) + - Name: ExtensibilityAndEnumKind2 + EnumKind: CFOptions + EnumExtensibility: closed +# CHECK: cannot mix EnumKind and EnumExtensibility (for ExtensibilityAndEnumKind3) + - Name: ExtensibilityAndEnumKind3 + EnumKind: none + EnumExtensibility: none From 6e7169a2d3ee1ed51c1829e8bb601ba0b1672a63 Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Tue, 25 Apr 2017 11:23:46 -0700 Subject: [PATCH 149/585] [index] Introduce 'CommentTag' symbol kind This is only going to be used by the Swift compiler currently, for indexing '- Tag' entries in documentation comments. apple-llvm-split-commit: 1716048fc85a4e5035d03987c75bbaf665531692 apple-llvm-split-dir: clang/ --- clang/include/clang/Index/IndexSymbol.h | 1 + clang/lib/Index/IndexSymbol.cpp | 1 + clang/tools/libclang/CXIndexDataConsumer.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/clang/include/clang/Index/IndexSymbol.h b/clang/include/clang/Index/IndexSymbol.h index ea8864d9aa2f5..6989aa356552d 100644 --- a/clang/include/clang/Index/IndexSymbol.h +++ b/clang/include/clang/Index/IndexSymbol.h @@ -53,6 +53,7 @@ enum class SymbolKind : uint8_t { ConversionFunction, Parameter, + CommentTag, }; enum class SymbolLanguage { diff --git a/clang/lib/Index/IndexSymbol.cpp b/clang/lib/Index/IndexSymbol.cpp index 98dbc552042bd..524cb43efefd0 100644 --- a/clang/lib/Index/IndexSymbol.cpp +++ b/clang/lib/Index/IndexSymbol.cpp @@ -452,6 +452,7 @@ StringRef index::getSymbolKindString(SymbolKind K) { case SymbolKind::Destructor: return "destructor"; case SymbolKind::ConversionFunction: return "coversion-func"; case SymbolKind::Parameter: return "param"; + case SymbolKind::CommentTag: return "comment-tag"; } llvm_unreachable("invalid symbol kind"); } diff --git a/clang/tools/libclang/CXIndexDataConsumer.cpp b/clang/tools/libclang/CXIndexDataConsumer.cpp index 9cd5ff4f505f6..4a2957f334986 100644 --- a/clang/tools/libclang/CXIndexDataConsumer.cpp +++ b/clang/tools/libclang/CXIndexDataConsumer.cpp @@ -1256,6 +1256,7 @@ static CXIdxEntityKind getEntityKindFromSymbolKind(SymbolKind K, SymbolLanguage case SymbolKind::Module: case SymbolKind::Macro: case SymbolKind::ClassProperty: + case SymbolKind::CommentTag: return CXIdxEntity_Unexposed; case SymbolKind::Enum: return CXIdxEntity_Enum; From 64ad67b08af6e704c3bf0c586bbf121f0667269b Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 8 May 2017 20:39:57 -0700 Subject: [PATCH 150/585] [APINotes] Always provide an unversioned SwiftName of some kind. (#81) If the active API notes set a Swift name for a declaration, but no other versions do, it's assumed that that's the name in every version, because the AST is indistinguishable from just applying the attribute in the header. Fix this by explicitly adding an inactive, unversioned removal attribute for SwiftName in otherwise ambiguous cases. (This really relates to /all/ annotations in the "SwiftVersions" section, and particularly those that are usually not written in headers, but it's particularly bad for SwiftName because the Swift compiler actually looks at the inactive versions for that one. At some point it may be worth trying to generalize this, though.) rdar://problem/31826517 apple-llvm-split-commit: 325fb32cc8b60bafd8d30213a818f96a520436ab apple-llvm-split-dir: clang/ --- clang/lib/Sema/SemaAPINotes.cpp | 46 +++++++++++++++++++ .../Headers/VersionedKit.apinotes | 4 ++ .../Headers/VersionedKit.h | 7 +++ clang/test/APINotes/versioned.m | 20 ++++++++ 4 files changed, 77 insertions(+) diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 5c171ef027f0a..6145fef3de268 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -649,6 +649,49 @@ static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, metadata); } +/// If we're applying API notes with an active, non-default version, and the +/// versioned API notes have a SwiftName but the declaration normally wouldn't +/// have one, add a removal attribute to make it clear that the new SwiftName +/// attribute only applies to the active version of \p D, not to all versions. +/// +/// This must be run \em before processing API notes for \p D, because otherwise +/// any existing SwiftName attribute will have been packaged up in a +/// SwiftVersionedAttr. +template +static void maybeAttachUnversionedSwiftName( + Sema &S, Decl *D, + const api_notes::APINotesReader::VersionedInfo Info) { + if (D->hasAttr()) + return; + if (!Info.getSelected()) + return; + + // Is the active slice versioned, and does it set a Swift name? + VersionTuple SelectedVersion; + SpecificInfo SelectedInfoSlice; + std::tie(SelectedVersion, SelectedInfoSlice) = Info[*Info.getSelected()]; + if (SelectedVersion.empty()) + return; + if (SelectedInfoSlice.SwiftName.empty()) + return; + + // Does the unversioned slice /not/ set a Swift name? + for (const auto &VersionAndInfoSlice : Info) { + if (!VersionAndInfoSlice.first.empty()) + continue; + if (!VersionAndInfoSlice.second.SwiftName.empty()) + return; + } + + // Then explicitly call that out with a removal attribute. + VersionedInfoMetadata DummyFutureMetadata(VersionTuple(), IsNotActive); + handleAPINotedAttribute(S, D, /*add*/false, + DummyFutureMetadata, + []() -> SwiftNameAttr * { + llvm_unreachable("should not try to add an attribute here"); + }); +} + /// Processes all versions of versioned API notes. /// /// Just dispatches to the various ProcessAPINotes functions in this file. @@ -656,6 +699,9 @@ template static void ProcessVersionedAPINotes( Sema &S, SpecificDecl *D, const api_notes::APINotesReader::VersionedInfo Info) { + + maybeAttachUnversionedSwiftName(S, D, Info); + unsigned Selected = Info.getSelected().getValueOr(Info.size()); VersionTuple Version; diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes index 502e32ee87c9c..c02c6cbac9a17 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.apinotes @@ -58,6 +58,10 @@ SwiftVersions: - Name: accessorsOnlyForClassExceptInVersion3 PropertyKind: Class SwiftImportAsAccessors: false + - Name: Swift3RenamedOnlyDUMP + SwiftName: SpecialSwift3Name + - Name: Swift3RenamedAlsoDUMP + SwiftName: SpecialSwift3Also Functions: - Name: moveToPointDUMP SwiftName: 'moveTo(a:b:)' diff --git a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h index ef094516673af..61a10034503c7 100644 --- a/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h +++ b/clang/test/APINotes/Inputs/Frameworks/VersionedKit.framework/Headers/VersionedKit.h @@ -36,6 +36,13 @@ typedef double MyDoubleWrapper __attribute__((swift_wrapper(struct))); - (Element)element; @end +@interface Swift3RenamedOnlyDUMP +@end + +__attribute__((swift_name("Swift4Name"))) +@interface Swift3RenamedAlsoDUMP +@end + enum __attribute__((flag_enum)) FlagEnum { FlagEnumA = 1, diff --git a/clang/test/APINotes/versioned.m b/clang/test/APINotes/versioned.m index 87363e7f20fd3..5657ae17658db 100644 --- a/clang/test/APINotes/versioned.m +++ b/clang/test/APINotes/versioned.m @@ -22,11 +22,31 @@ // CHECK-UNVERSIONED-DUMP: SwiftNameAttr {{.+}} "moveTo(x:y:)" // CHECK-UNVERSIONED-DUMP-NEXT: SwiftVersionedAttr {{.+}} Implicit 3.0 // CHECK-UNVERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} Implicit "moveTo(a:b:)" +// CHECK-DUMP-NOT: Attr // CHECK-DUMP-LABEL: Dumping TestGenericDUMP // CHECK-VERSIONED-DUMP: SwiftImportAsNonGenericAttr {{.+}} Implicit // CHECK-UNVERSIONED-DUMP: SwiftVersionedAttr {{.+}} Implicit 3.0 // CHECK-UNVERSIONED-DUMP-NEXT: SwiftImportAsNonGenericAttr {{.+}} Implicit +// CHECK-DUMP-NOT: Attr + +// CHECK-DUMP-LABEL: Dumping Swift3RenamedOnlyDUMP +// CHECK-DUMP: in VersionedKit Swift3RenamedOnlyDUMP +// CHECK-VERSIONED-DUMP-NEXT: SwiftVersionedRemovalAttr {{.+}} Implicit 0 {{[0-9]+}} +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "SpecialSwift3Name" +// CHECK-UNVERSIONED-DUMP-NEXT: SwiftVersionedAttr {{.+}} Implicit 3.0 +// CHECK-UNVERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} Implicit "SpecialSwift3Name" +// CHECK-DUMP-NOT: Attr + +// CHECK-DUMP-LABEL: Dumping Swift3RenamedAlsoDUMP +// CHECK-DUMP: in VersionedKit Swift3RenamedAlsoDUMP +// CHECK-VERSIONED-DUMP-NEXT: SwiftVersionedAttr {{.+}} Implicit 0 +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "Swift4Name" +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "SpecialSwift3Also" +// CHECK-UNVERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "Swift4Name" +// CHECK-UNVERSIONED-DUMP-NEXT: SwiftVersionedAttr {{.+}} Implicit 3.0 +// CHECK-UNVERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} Implicit "SpecialSwift3Also" +// CHECK-DUMP-NOT: Attr // CHECK-DUMP-NOT: Dumping From 6486012b7d48be0464d0a01a38e01af778ff3df4 Mon Sep 17 00:00:00 2001 From: Bruno Cardoso Lopes Date: Wed, 26 Apr 2017 16:58:05 -0700 Subject: [PATCH 151/585] [Modules] Include error diagnostics to the module hash Add a CC1 option to include error diagnostics to the module hash: -fmodules-hash-error-diagnostics rdar://problem/31796394 apple-llvm-split-commit: 02d1fb6d217fdd18be00c55cd87ef390379c2269 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Driver/CC1Options.td | 2 + .../clang/Frontend/CompilerInvocation.h | 2 +- clang/lib/Frontend/CompilerInstance.cpp | 10 ++-- clang/lib/Frontend/CompilerInvocation.cpp | 30 ++++++++++- clang/test/Modules/hash-werror.m | 54 +++++++++++++++++++ 6 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 clang/test/Modules/hash-werror.m diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index b8bd25d8377dd..3aac98f5d0691 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -153,6 +153,7 @@ COMPATIBLE_LANGOPT(ModulesStrictDeclUse, 1, 0, "requiring declaration of module BENIGN_LANGOPT(ModulesErrorRecovery, 1, 1, "automatically importing modules as needed when performing error recovery") BENIGN_LANGOPT(ImplicitModules, 1, 1, "building modules that are not specified via -fmodule-file") COMPATIBLE_LANGOPT(ModulesLocalVisibility, 1, 0, "local submodule visibility") +COMPATIBLE_LANGOPT(ModulesHashErrorDiags, 1, 0, "hash out diagnostic errors as part of the module hash") COMPATIBLE_LANGOPT(Optimize , 1, 0, "__OPTIMIZE__ predefined macro") COMPATIBLE_LANGOPT(OptimizeSize , 1, 0, "__OPTIMIZE_SIZE__ predefined macro") COMPATIBLE_LANGOPT(Static , 1, 0, "__STATIC__ predefined macro (as opposed to __DYNAMIC__)") diff --git a/clang/include/clang/Driver/CC1Options.td b/clang/include/clang/Driver/CC1Options.td index 1132d1345f605..61127dd5e4961 100644 --- a/clang/include/clang/Driver/CC1Options.td +++ b/clang/include/clang/Driver/CC1Options.td @@ -458,6 +458,8 @@ def fmodules_debuginfo : def fmodule_format_EQ : Joined<["-"], "fmodule-format=">, HelpText<"Select the container format for clang modules and PCH. " "Supported options are 'raw' and 'obj'.">; +def fmodules_hash_error_diagnostics : Flag<["-"], "fmodules-hash-error-diagnostics">, + HelpText<"Use a separate module cache for modules compiled with conflicting -Werror options">; def ftest_module_file_extension_EQ : Joined<["-"], "ftest-module-file-extension=">, HelpText<"introduce a module file extension for testing purposes. " diff --git a/clang/include/clang/Frontend/CompilerInvocation.h b/clang/include/clang/Frontend/CompilerInvocation.h index 1b48f026606dc..4d3449ff551a8 100644 --- a/clang/include/clang/Frontend/CompilerInvocation.h +++ b/clang/include/clang/Frontend/CompilerInvocation.h @@ -174,7 +174,7 @@ class CompilerInvocation : public CompilerInvocationBase { /// \brief Retrieve a module hash string that is suitable for uniquely /// identifying the conditions under which the module was built. - std::string getModuleHash() const; + std::string getModuleHash(DiagnosticsEngine &Diags) const; /// @} /// @name Option Subgroups diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 3e390c96e11d9..023a321cb52c2 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -471,7 +471,7 @@ std::string CompilerInstance::getSpecificModuleCachePath() { SmallString<256> SpecificModuleCache(getHeaderSearchOpts().ModuleCachePath); if (!SpecificModuleCache.empty() && !getHeaderSearchOpts().DisableModuleHash) llvm::sys::path::append(SpecificModuleCache, - getInvocation().getModuleHash()); + getInvocation().getModuleHash(getDiagnostics())); return SpecificModuleCache.str(); } @@ -1092,9 +1092,11 @@ static bool compileModuleImpl(CompilerInstance &ImportingInstance, PPOpts.RetainRemappedFileBuffers = true; Invocation->getDiagnosticOpts().VerifyDiagnostics = 0; - assert(ImportingInstance.getInvocation().getModuleHash() == - Invocation->getModuleHash() && "Module hash mismatch!"); - + assert(ImportingInstance.getInvocation().getModuleHash( + ImportingInstance.getDiagnostics()) == + Invocation->getModuleHash(ImportingInstance.getDiagnostics()) && + "Module hash mismatch!"); + // Construct a compiler instance that will be used to actually create the // module. Since we're sharing a PCMCache, // CompilerInstance::CompilerInstance is responsible for finalizing the diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 0f2dcb1609cd7..777f060cc0c20 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -2076,6 +2076,7 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK, Args.hasArg(OPT_fmodules_local_submodule_visibility) || Opts.ModulesTS; Opts.ModulesCodegen = Args.hasArg(OPT_fmodules_codegen); Opts.ModulesDebugInfo = Args.hasArg(OPT_fmodules_debuginfo); + Opts.ModulesHashErrorDiags = Args.hasArg(OPT_fmodules_hash_error_diagnostics); Opts.ModulesSearchAll = Opts.Modules && !Args.hasArg(OPT_fno_modules_search_all) && Args.hasArg(OPT_fmodules_search_all); @@ -2656,7 +2657,16 @@ bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Res, return Success; } -std::string CompilerInvocation::getModuleHash() const { +// Some extension diagnostics aren't explicitly mapped and require custom +// logic in the dianognostic engine to be used, track -pedantic-errors +static bool isExtHandlingFromDiagsError(DiagnosticsEngine &Diags) { + diag::Severity Ext = Diags.getExtensionHandlingBehavior(); + if (Ext == diag::Severity::Warning && Diags.getWarningsAsErrors()) + return true; + return Ext >= diag::Severity::Error; +} + +std::string CompilerInvocation::getModuleHash(DiagnosticsEngine &Diags) const { // Note: For QoI reasons, the things we use as a hash here should all be // dumped via the -module-info flag. using llvm::hash_code; @@ -2747,6 +2757,24 @@ std::string CompilerInvocation::getModuleHash() const { if (!SanHash.empty()) code = hash_combine(code, SanHash.Mask); + // Check for a couple things (see checkDiagnosticMappings in ASTReader.cpp): + // -Werror: consider all warnings into the hash + // -Werror=something: consider only the specified into the hash + // -pedantic-error + if (getLangOpts()->ModulesHashErrorDiags) { + bool ConsiderAllWarningsAsErrors = Diags.getWarningsAsErrors(); + code = hash_combine(code, isExtHandlingFromDiagsError(Diags)); + for (auto DiagIDMappingPair : Diags.getDiagnosticMappings()) { + diag::kind DiagID = DiagIDMappingPair.first; + auto CurLevel = Diags.getDiagnosticLevel(DiagID, SourceLocation()); + if (CurLevel < DiagnosticsEngine::Error && !ConsiderAllWarningsAsErrors) + continue; // not significant + code = hash_combine( + code, + Diags.getDiagnosticIDs()->getWarningOptionForDiag(DiagID).str()); + } + } + return llvm::APInt(64, code).toString(36, /*Signed=*/false); } diff --git a/clang/test/Modules/hash-werror.m b/clang/test/Modules/hash-werror.m new file mode 100644 index 0000000000000..85446ce49eb4d --- /dev/null +++ b/clang/test/Modules/hash-werror.m @@ -0,0 +1,54 @@ +// RUN: rm -rf %t +// RUN: mkdir %t + +// (1) Test -Werror +// RUN: echo 'int foo() { return fn(); }' > %t/foo.h +// RUN: echo 'module foo { header "foo.h" export * }' > %t/module.modulemap +// +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps %s -fsyntax-only \ +// RUN: -I%t -fmodules-cache-path=%t/cache -Rmodule-build \ +// RUN: -fmodules-hash-error-diagnostics 2>%t/out +// +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps %s -fsyntax-only \ +// RUN: -I%t -Werror -fmodules-cache-path=%t/cache -Rmodule-build \ +// RUN: -Wno-implicit-function-declaration \ +// RUN: -fmodules-hash-error-diagnostics 2>>%t/out + +// RUN: FileCheck --check-prefix=CHECKWERROR %s -input-file %t/out +// CHECKWERROR: remark: building module 'foo' as '/[[PATH:.*]]/foo- +// CHECKWERROR-NOT: remark: building module 'foo' as '/[[PATH]]/foo- + +// (2) Test -Werror= +// RUN: rm -rf %t/out %t/cache +// +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps %s -fsyntax-only \ +// RUN: -I%t -Werror -fmodules-cache-path=%t/cache -Rmodule-build \ +// RUN: -fmodules-hash-error-diagnostics 2>%t/out +// +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps %s -fsyntax-only \ +// RUN: -I%t -fmodules-cache-path=%t/cache -Rmodule-build \ +// RUN: -Werror=implicit-function-declaration \ +// RUN: -fmodules-hash-error-diagnostics 2>>%t/out + +// RUN: FileCheck --check-prefix=CHECKWERROREQUALS %s -input-file %t/out +// CHECKWERROREQUALS: remark: building module 'foo' as '/[[PATH:.*]]/foo- +// CHECKWERROREQUALS-NOT: remark: building module 'foo' as '/[[PATH]]/foo- + +// (3) Test -pedantic-errors +// RUN: rm -rf %t/out %t/cache +// RUN: echo '#ifdef foo' > %t/foo.h +// RUN: echo '#endif bad // extension!' >> %t/foo.h + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps %s -fsyntax-only \ +// RUN: -I%t -fmodules-cache-path=%t/cache -Rmodule-build -x c \ +// RUN: -fmodules-hash-error-diagnostics 2>%t/out +// +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps %s -fsyntax-only \ +// RUN: -I%t -pedantic-errors -fmodules-cache-path=%t/cache -Rmodule-build -x c \ +// RUN: -fmodules-hash-error-diagnostics 2>>%t/out + +// RUN: FileCheck --check-prefix=CHECKPEDANTICERROR %s -input-file %t/out +// CHECKPEDANTICERROR: remark: building module 'foo' as '/[[PATH:.*]]/foo- +// CHECKPEDANTICERROR-NOT: remark: building module 'foo' as '/[[PATH]]/foo- + +#include "foo.h" From 0361eaf9e53ef3e09ff34b8d2e1bba87ad74939f Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Sat, 10 Jun 2017 17:08:40 -0700 Subject: [PATCH 152/585] Restore changes dropped in bad merge (b76dafda5a) apple-llvm-split-commit: c8ddb417cd12fcbb3854c2cb158543a17ab7064a apple-llvm-split-dir: clang/ --- clang/lib/Frontend/CompilerInstance.cpp | 22 ++++++++++++++++++++++ clang/lib/Frontend/FrontendAction.cpp | 7 +++++++ clang/lib/Lex/PPDirectives.cpp | 8 +++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index f68f9464b9f7f..70e99dff9d614 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "clang/Frontend/CompilerInstance.h" +#include "clang/APINotes/APINotesReader.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" @@ -631,6 +632,27 @@ void CompilerInstance::createSema(TranslationUnitKind TUKind, CodeCompleteConsumer *CompletionConsumer) { TheSema.reset(new Sema(getPreprocessor(), getASTContext(), getASTConsumer(), TUKind, CompletionConsumer)); + + // Set up API notes. + TheSema->APINotes.setSwiftVersion(getAPINotesOpts().SwiftVersion); + + // If we're building a module and are supposed to load API notes, + // notify the API notes manager. + if (auto currentModule = getPreprocessor().getCurrentModule()) { + (void)TheSema->APINotes.loadCurrentModuleAPINotes( + currentModule, + getLangOpts().APINotesModules, + getAPINotesOpts().ModuleSearchPaths); + // Check for any attributes we should add to the module + for (auto reader : TheSema->APINotes.getCurrentModuleReaders()) { + // swift_infer_import_as_member + if (reader->getModuleOptions().SwiftInferImportAsMember) { + currentModule->IsSwiftInferImportAsMember = true; + break; + } + } + } + // Attach the external sema source if there is any. if (ExternalSemaSrc) { TheSema->addExternalSource(ExternalSemaSrc.get()); diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp index 7c0b854648bdd..66d5688ffcb04 100644 --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/clang/lib/Frontend/FrontendAction.cpp @@ -855,6 +855,13 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI, CI.getDiagnostics().Report(diag::err_module_map_not_found) << Filename; } + // Add a module declaration scope so that modules from -fmodule-map-file + // arguments may shadow modules found implicitly in search paths. + CI.getPreprocessor() + .getHeaderSearchInfo() + .getModuleMap() + .finishModuleDeclarationScope(); + // If we were asked to load any module files, do so now. for (const auto &ModuleFile : CI.getFrontendOpts().ModuleFiles) if (!CI.loadModuleFile(ModuleFile)) diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index 89c2ebd00a683..e95686a397523 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -1660,12 +1660,18 @@ bool Preprocessor::checkModuleIsAvailable(const LangOptions &LangOpts, DiagnosticsEngine &Diags, Module *M) { Module::Requirement Requirement; Module::UnresolvedHeaderDirective MissingHeader; - if (M->isAvailable(LangOpts, TargetInfo, Requirement, MissingHeader)) + Module *ShadowingModule = nullptr; + if (M->isAvailable(LangOpts, TargetInfo, Requirement, MissingHeader, + ShadowingModule)) return false; if (MissingHeader.FileNameLoc.isValid()) { Diags.Report(MissingHeader.FileNameLoc, diag::err_module_header_missing) << MissingHeader.IsUmbrella << MissingHeader.FileName; + } else if (ShadowingModule) { + Diags.Report(M->DefinitionLoc, diag::err_module_shadowed) << M->Name; + Diags.Report(ShadowingModule->DefinitionLoc, + diag::note_previous_definition); } else { // FIXME: Track the location at which the requirement was specified, and // use it here. From 35bd78f32f4e769e392df08be5e71bd19ada5d20 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Sat, 10 Jun 2017 17:09:02 -0700 Subject: [PATCH 153/585] Temporary hack to allow making progress on master-next build failures. apple-llvm-split-commit: d1cd5fdf4f31d14fb20e40b4d3565fc865076e7b apple-llvm-split-dir: clang/ --- clang/include/clang/Lex/MacroInfo.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clang/include/clang/Lex/MacroInfo.h b/clang/include/clang/Lex/MacroInfo.h index 7da1e7b41ab8d..e5ca15f2e7170 100644 --- a/clang/include/clang/Lex/MacroInfo.h +++ b/clang/include/clang/Lex/MacroInfo.h @@ -266,6 +266,11 @@ class MacroInfo { void setUsedForHeaderGuard(bool Val) { UsedForHeaderGuard = Val; } + // FIXME: hack to get past build failures + unsigned getOwningModuleID() const { + return 0; + } + void dump() const; private: From c51d996fbcd805d53576a7b75b726774df5991c3 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Sat, 10 Jun 2017 22:36:31 -0700 Subject: [PATCH 154/585] NFC: Fix whitespace to use spaces instead of tabs. apple-llvm-split-commit: 740ea3231c74495c6de2eedbd1f503e4b6b4b543 apple-llvm-split-dir: clang/ --- clang/lib/Lex/PPDirectives.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index e95686a397523..183f67759c193 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -1662,7 +1662,7 @@ bool Preprocessor::checkModuleIsAvailable(const LangOptions &LangOpts, Module::UnresolvedHeaderDirective MissingHeader; Module *ShadowingModule = nullptr; if (M->isAvailable(LangOpts, TargetInfo, Requirement, MissingHeader, - ShadowingModule)) + ShadowingModule)) return false; if (MissingHeader.FileNameLoc.isValid()) { From 24209b95dc59025a6f529037371e9f84c340c778 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 12 Jun 2017 13:41:59 -0700 Subject: [PATCH 155/585] Move getRequiredQualification to lib/AST to access it from the refactoring engine Preparation commit for the refactoring engine apple-llvm-split-commit: 98936f53f7c58443f6bf70dce856ff7c23ba9ae4 apple-llvm-split-dir: clang/ --- clang/include/clang/AST/NestedNameSpecifier.h | 17 ++++++ clang/lib/AST/NestedNameSpecifier.cpp | 31 ++++++++++ clang/lib/Sema/SemaCodeComplete.cpp | 60 ++----------------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/clang/include/clang/AST/NestedNameSpecifier.h b/clang/include/clang/AST/NestedNameSpecifier.h index b1ff9bdff5891..3a6a3c3694cec 100644 --- a/clang/include/clang/AST/NestedNameSpecifier.h +++ b/clang/include/clang/AST/NestedNameSpecifier.h @@ -219,6 +219,23 @@ class NestedNameSpecifier : public llvm::FoldingSetNode { /// in debugging. void dump(const LangOptions &LO) const; void dump() const; + + /// \brief Compute the qualification required to get from the current context + /// (\p CurContext) to the target context (\p TargetContext). + /// + /// \param Context the AST context in which the qualification will be used. + /// + /// \param CurContext the context where an entity is being named, which is + /// typically based on the current scope. + /// + /// \param TargetContext the context in which the named entity actually + /// resides. + /// + /// \returns a nested name specifier that refers into the target context, or + /// NULL if no qualification is needed. + static NestedNameSpecifier * + getRequiredQualification(ASTContext &Context, const DeclContext *CurContext, + const DeclContext *TargetContext); }; /// \brief A C++ nested-name-specifier augmented with source location diff --git a/clang/lib/AST/NestedNameSpecifier.cpp b/clang/lib/AST/NestedNameSpecifier.cpp index e2e0dbeec0dd3..88ee526851ce0 100644 --- a/clang/lib/AST/NestedNameSpecifier.cpp +++ b/clang/lib/AST/NestedNameSpecifier.cpp @@ -691,3 +691,34 @@ NestedNameSpecifierLocBuilder::getWithLocInContext(ASTContext &Context) const { memcpy(Mem, Buffer, BufferSize); return NestedNameSpecifierLoc(Representation, Mem); } + +NestedNameSpecifier *NestedNameSpecifier::getRequiredQualification( + ASTContext &Context, const DeclContext *CurContext, + const DeclContext *TargetContext) { + SmallVector TargetParents; + + for (const DeclContext *CommonAncestor = TargetContext; + CommonAncestor && !CommonAncestor->Encloses(CurContext); + CommonAncestor = CommonAncestor->getLookupParent()) { + if (CommonAncestor->isTransparentContext() || + CommonAncestor->isFunctionOrMethod()) + continue; + + TargetParents.push_back(CommonAncestor); + } + + NestedNameSpecifier *Result = nullptr; + while (!TargetParents.empty()) { + const DeclContext *Parent = TargetParents.pop_back_val(); + + if (const NamespaceDecl *Namespace = dyn_cast(Parent)) { + if (!Namespace->getIdentifier()) + continue; + + Result = NestedNameSpecifier::Create(Context, Result, Namespace); + } else if (const TagDecl *TD = dyn_cast(Parent)) + Result = NestedNameSpecifier::Create( + Context, Result, false, Context.getTypeDeclType(TD).getTypePtr()); + } + return Result; +} diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index b9349dc06bff3..523fc69937b96 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -432,53 +432,6 @@ ResultBuilder::ShadowMapEntry::end() const { return iterator(DeclOrVector.get()->end()); } -/// \brief Compute the qualification required to get from the current context -/// (\p CurContext) to the target context (\p TargetContext). -/// -/// \param Context the AST context in which the qualification will be used. -/// -/// \param CurContext the context where an entity is being named, which is -/// typically based on the current scope. -/// -/// \param TargetContext the context in which the named entity actually -/// resides. -/// -/// \returns a nested name specifier that refers into the target context, or -/// NULL if no qualification is needed. -static NestedNameSpecifier * -getRequiredQualification(ASTContext &Context, - const DeclContext *CurContext, - const DeclContext *TargetContext) { - SmallVector TargetParents; - - for (const DeclContext *CommonAncestor = TargetContext; - CommonAncestor && !CommonAncestor->Encloses(CurContext); - CommonAncestor = CommonAncestor->getLookupParent()) { - if (CommonAncestor->isTransparentContext() || - CommonAncestor->isFunctionOrMethod()) - continue; - - TargetParents.push_back(CommonAncestor); - } - - NestedNameSpecifier *Result = nullptr; - while (!TargetParents.empty()) { - const DeclContext *Parent = TargetParents.pop_back_val(); - - if (const NamespaceDecl *Namespace = dyn_cast(Parent)) { - if (!Namespace->getIdentifier()) - continue; - - Result = NestedNameSpecifier::Create(Context, Result, Namespace); - } - else if (const TagDecl *TD = dyn_cast(Parent)) - Result = NestedNameSpecifier::Create(Context, Result, - false, - Context.getTypeDeclType(TD).getTypePtr()); - } - return Result; -} - /// Determine whether \p Id is a name reserved for the implementation (C99 /// 7.1.3, C++ [lib.global.names]). static bool isReservedName(const IdentifierInfo *Id, @@ -590,9 +543,8 @@ bool ResultBuilder::CheckHiddenResult(Result &R, DeclContext *CurContext, R.QualifierIsInformative = false; if (!R.Qualifier) - R.Qualifier = getRequiredQualification(SemaRef.Context, - CurContext, - R.Declaration->getDeclContext()); + R.Qualifier = NestedNameSpecifier::getRequiredQualification( + SemaRef.Context, CurContext, R.Declaration->getDeclContext()); return false; } @@ -3332,9 +3284,8 @@ static void MaybeAddOverrideCalls(Sema &S, DeclContext *InContext, // If we need a nested-name-specifier, add one now. if (!InContext) { - NestedNameSpecifier *NNS - = getRequiredQualification(S.Context, CurContext, - Overridden->getDeclContext()); + NestedNameSpecifier *NNS = NestedNameSpecifier::getRequiredQualification( + S.Context, CurContext, Overridden->getDeclContext()); if (NNS) { std::string Str; llvm::raw_string_ostream OS(Str); @@ -4196,7 +4147,8 @@ void Sema::CodeCompleteCase(Scope *S) { // If there are no prior enumerators in C++, check whether we have to // qualify the names of the enumerators that we suggest, because they // may not be visible in this scope. - Qualifier = getRequiredQualification(Context, CurContext, Enum); + Qualifier = NestedNameSpecifier::getRequiredQualification(Context, + CurContext, Enum); } // Add any enumerators that have not yet been mentioned. From 5b8aa9250aaf7cd8ef69fcff0d3313a882117e74 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Thu, 15 Jun 2017 16:41:58 -0700 Subject: [PATCH 156/585] Add support for refactoring to Clang & libclang This refactoring engine is used in Xcode 9. The following actions are supported: - Rename - Extract function/method. - Extract duplicate expression. - Add missing switch cases. It is also available as a fixit. - [C++] Fill-in method stubs from abstract base classes. - Convert if to switch. - [ObjC] Fill in protocol stubs. It is also available as a fixit. - [ObjC] Wrap in NSLocalizedString - Generate Missing Function Definitions apple-llvm-split-commit: 9890adfbee8f854732d0093bc8b2a32be1be8844 apple-llvm-split-dir: clang/ --- clang/include/clang-c/CXErrorCode.h | 20 +- clang/include/clang-c/Refactor.h | 1288 +++++++++++ clang/include/clang/AST/DeclObjC.h | 42 +- clang/include/clang/AST/DeclTemplate.h | 3 + clang/include/clang/AST/DependentASTVisitor.h | 91 + clang/include/clang/AST/PrettyPrinter.h | 12 +- clang/include/clang/Basic/AllDiagnostics.h | 1 + clang/include/clang/Basic/CMakeLists.txt | 1 + clang/include/clang/Basic/Diagnostic.td | 2 +- clang/include/clang/Basic/DiagnosticIDs.h.rej | 18 + .../clang/Basic/DiagnosticRefactoringKinds.td | 27 + .../clang/Basic/DiagnosticSemaKinds.td | 7 + clang/include/clang/Edit/RefactoringFixits.h | 66 + clang/include/clang/Lex/Lexer.h | 8 + .../Tooling/Core/RefactoringDiagnostic.h | 29 + .../clang/Tooling/Refactor/IndexerQuery.h | 274 +++ .../Refactor/RefactoringActionFinder.h | 60 + .../Tooling/Refactor/RefactoringActions.def | 60 + .../Tooling/Refactor/RefactoringActions.h | 34 + .../Tooling/Refactor/RefactoringOperation.h | 160 ++ .../Refactor/RefactoringOperationState.h | 66 + .../Tooling/Refactor/RefactoringOptionSet.h | 80 + .../Tooling/Refactor/RefactoringOptions.h | 59 + .../Tooling/Refactor/RefactoringReplacement.h | 85 + .../Tooling/Refactor/RenameIndexedFile.h | 83 + .../clang/Tooling/Refactor/RenamedSymbol.h | 125 ++ .../Tooling/Refactor/RenamingOperation.h | 43 + .../clang/Tooling/Refactor/SymbolName.h | 66 + .../Tooling/Refactor/SymbolOccurrenceFinder.h | 37 + .../clang/Tooling/Refactor/SymbolOperation.h | 91 + .../clang/Tooling/Refactor/USRFinder.h | 85 + clang/include/clang/module.modulemap | 5 +- clang/lib/AST/ASTDumper.cpp | 2 + clang/lib/AST/DeclObjC.cpp | 17 +- clang/lib/AST/DeclPrinter.cpp | 23 +- clang/lib/AST/TypePrinter.cpp | 20 +- clang/lib/Basic/DiagnosticIDs.cpp | 4 +- clang/lib/Edit/CMakeLists.txt | 2 + clang/lib/Edit/FillInMissingProtocolStubs.cpp | 461 ++++ .../lib/Edit/FillInMissingSwitchEnumCases.cpp | 189 ++ clang/lib/Lex/Lexer.cpp | 29 + clang/lib/Sema/SemaDeclObjC.cpp | 71 +- clang/lib/Sema/SemaStmt.cpp | 29 +- clang/lib/Serialization/ASTReaderDecl.cpp | 2 + clang/lib/Serialization/ASTWriterDecl.cpp | 2 + clang/lib/Tooling/CMakeLists.txt | 3 + clang/lib/Tooling/CompilationDatabase.cpp | 4 +- clang/lib/Tooling/Refactor/ASTSlice.cpp | 625 ++++++ clang/lib/Tooling/Refactor/ASTSlice.h | 182 ++ .../Refactor/ASTStateSerialization.cpp | 68 + clang/lib/Tooling/Refactor/CMakeLists.txt | 43 + clang/lib/Tooling/Refactor/Extract.cpp | 1916 ++++++++++++++++ .../ExtractRepeatedExpressionIntoVariable.cpp | 369 ++++ .../Refactor/FillInEnumSwitchCases.cpp | 110 + ...nMissingMethodStubsFromAbstractClasses.cpp | 293 +++ .../Refactor/FillInMissingProtocolStubs.cpp | 91 + .../Tooling/Refactor/IfSwitchConversion.cpp | 460 ++++ .../Refactor/ImplementDeclaredMethods.cpp | 414 ++++ clang/lib/Tooling/Refactor/IndexerQueries.cpp | 136 ++ .../Refactor/LocalizeObjCStringLiteral.cpp | 79 + .../Refactor/RefactoringActionFinder.cpp | 57 + .../Tooling/Refactor/RefactoringActions.cpp | 30 + .../Refactor/RefactoringContinuations.h | 351 +++ .../Tooling/Refactor/RefactoringOperation.cpp | 93 + .../Tooling/Refactor/RefactoringOperations.h | 37 + .../Tooling/Refactor/RefactoringOptions.cpp | 65 + .../Tooling/Refactor/RenameIndexedFile.cpp | 562 +++++ clang/lib/Tooling/Refactor/RenamedSymbol.cpp | 41 + .../Tooling/Refactor/RenamingOperation.cpp | 98 + .../Refactor/SourceLocationUtilities.cpp | 260 +++ .../Refactor/SourceLocationUtilities.h | 174 ++ clang/lib/Tooling/Refactor/StmtUtils.cpp | 54 + clang/lib/Tooling/Refactor/StmtUtils.h | 33 + clang/lib/Tooling/Refactor/SymbolName.cpp | 58 + .../Refactor/SymbolOccurrenceFinder.cpp | 412 ++++ .../lib/Tooling/Refactor/SymbolOperation.cpp | 210 ++ .../lib/Tooling/Refactor/SymbolUSRFinder.cpp | 206 ++ clang/lib/Tooling/Refactor/TypeUtils.cpp | 193 ++ clang/lib/Tooling/Refactor/TypeUtils.h | 35 + clang/lib/Tooling/Refactor/USRFinder.cpp | 698 ++++++ clang/lib/Tooling/Refactoring/CMakeLists.txt | 2 +- clang/test/Analysis/misc-ps.m | 2 +- clang/test/CMakeLists.txt | 3 +- .../fixit-fill-in-missing-switch-cases.cpp | 31 + .../fixit-fill-in-protocol-requirements.m | 54 + clang/test/Misc/ast-dump-decl.m | 3 + clang/test/Parser/switch-recovery.cpp | 2 +- .../Extract/captured-variable-block-types.m | 30 + .../captured-variable-function-types.cpp | 17 + .../Extract/captured-variable-lambda-type.cpp | 18 + .../Extract/captured-variable-typedef.cpp | 15 + .../Extract/captured-variable-types.cpp | 223 ++ .../Extract/captured-variable-types.m | 56 + .../Extract/disallowed-expressions.cpp | 50 + .../extract-address-of-captured-variable.cpp | 194 ++ .../extract-address-of-captured-variable.mm | 44 + .../Extract/extract-before-comments.cpp | 64 + .../extract-capture-instance-variable.cpp | 73 + .../Refactor/Extract/extract-capture-self.m | 56 + .../Extract/extract-capture-static-var.cpp | 12 + .../Refactor/Extract/extract-capture-super.m | 42 + .../Refactor/Extract/extract-capture-this.cpp | 66 + .../extract-capture-used-after-extraction.cpp | 214 ++ .../extract-capture-used-after-extraction.m | 25 + .../Refactor/Extract/extract-expression.cpp | 60 + .../Refactor/Extract/extract-from-class.cpp | 45 + .../Refactor/Extract/extract-header-inline.h | 7 + .../Refactor/Extract/extract-initiate.cpp | 150 ++ .../test/Refactor/Extract/extract-initiate.m | 14 + .../test/Refactor/Extract/extract-macros.cpp | 39 + .../test/Refactor/Extract/extract-method.cpp | 153 ++ clang/test/Refactor/Extract/extract-method.m | 57 + .../extract-mutation-of-captured-variable.cpp | 130 ++ .../Refactor/Extract/extract-objc-property.m | 47 + ...extract-reference-of-captured-variable.cpp | 287 +++ .../extract-reference-of-captured-variable.mm | 50 + .../Extract/extract-statement-macros.cpp | 47 + .../Refactor/Extract/extract-statements.cpp | 178 ++ .../Refactor/Extract/extract-statements.m | 57 + .../extract-whole-source-construct.cpp | 120 ++ .../Extract/extracted-declaration-name.mm | 50 + clang/test/Refactor/Extract/return-c-bool.c | 28 + .../Extract/return-correct-stl-type.cpp | 51 + .../test/Refactor/Extract/return-objc-bool.m | 89 + .../extract-repeated-expr-duplicates.cpp | 63 + .../extract-repeated-expr-duplicates.m | 32 + .../extract-repeated-expr-initiate.cpp | 109 + .../extract-repeated-expr-initiate.m | 94 + .../extract-repeated-expr-perform.cpp | 100 + .../extract-repeated-expr-perform.m | 80 + .../fill-in-cases-forward-decl.c | 26 + .../fill-in-cases-initiate.cpp | 150 ++ .../fill-in-cases-neatly-ordered.cpp | 157 ++ .../fill-in-cases-opaque-decl.cpp | 33 + .../fill-in-cases-perform.cpp | 80 + .../fill-in-cases-wrap-in-compound.cpp | 63 + .../initiate-on-enum-constant.c | 12 + ...l-in-missing-abstract-methods-initiate.cpp | 68 + ...missing-abstract-methods-no-attributes.cpp | 10 + ...ll-in-missing-abstract-methods-perform.cpp | 101 + ...n-missing-abstract-methods-with-bodies.cpp | 50 + ...-protocol-stubs-initiate-class-extension.m | 51 + ...in-protocol-stubs-initiate-required-only.m | 146 ++ ...-protocol-stubs-initiate-when-protocoled.m | 97 + .../fill-in-protocol-stubs-initiate.m | 100 + .../fill-in-protocol-stubs-no-attributes.m | 28 + .../fill-in-protocol-stubs-perform.m | 196 ++ .../if-switch-conversion-initiate.cpp | 566 +++++ .../if-switch-conversion-perform.cpp | 307 +++ .../ImplementDeclaredMethods/Inputs/class.cpp | 25 + .../Inputs/classInHeader.cpp | 69 + .../Inputs/classInHeader.h | 31 + .../ImplementDeclaredMethods/Inputs/empty.cpp | 1 + .../Inputs/objcClass.m | 13 + .../Inputs/objcHeader.h | 13 + .../implement-declared-methods-initiate.cpp | 64 + .../implement-declared-methods-initiate.m | 106 + .../implement-declared-methods.cpp | 164 ++ .../implement-declared-methods.m | 55 + .../ImplementDeclaredMethods/local-record.cpp | 30 + .../prohibited-methods.cpp | 19 + .../localize-objc-initiate.m | 26 + .../localize-objc-perform.m | 9 + .../Rename/CanonicalizeInstantiatedDecls.cpp | 86 + .../Rename/ClassAsTemplateArgument.cpp | 20 + .../Refactor/Rename/ClassSimpleRenaming.cpp | 38 + .../Rename/ComplexFunctionOverride.cpp | 76 + .../Refactor/Rename/ComplicatedClassType.cpp | 62 + clang/test/Refactor/Rename/Ctor.cpp | 29 + .../test/Refactor/Rename/CtorInitializer.cpp | 14 + clang/test/Refactor/Rename/DeclRefExpr.cpp | 21 + .../Refactor/Rename/DependentExpressions.cpp | 72 + clang/test/Refactor/Rename/Field.cpp | 12 + clang/test/Refactor/Rename/FunctionMacro.cpp | 17 + .../test/Refactor/Rename/FunctionOverride.cpp | 10 + .../Refactor/Rename/IndexedObjCMessageSend.mm | 882 ++++++++ .../test/Refactor/Rename/IndexedObjCMethod.m | 128 ++ .../Refactor/Rename/IndexedObjCMethodDecl.mm | 748 +++++++ .../Refactor/Rename/IndexedObjCProperty.m | 30 + .../Rename/Inputs/MultiFileTUHeader.h | 6 + ...jCImplementationTURequestsImplementation.m | 3 + .../Rename/Inputs/objc-system-header.h | 5 + .../Rename/Inputs/rename-indexed-file.cpp | 4 + .../Refactor/Rename/Inputs/system-header.h | 11 + clang/test/Refactor/Rename/LocalBlockSymbol.m | 58 + .../Refactor/Rename/LocalBlockSymbolCpp.mm | 24 + clang/test/Refactor/Rename/LocalSymbol.cpp | 232 ++ .../test/Refactor/Rename/MemberExprMacro.cpp | 19 + clang/test/Refactor/Rename/MultiFileTU.cpp | 9 + clang/test/Refactor/Rename/Namespace.cpp | 22 + clang/test/Refactor/Rename/NoNewName.cpp | 4 + clang/test/Refactor/Rename/ObjCClass.m | 95 + .../test/Refactor/Rename/ObjCClassProperty.m | 100 + .../Refactor/Rename/ObjCCompatibilityAlias.m | 44 + .../Rename/ObjCImplementationTURequests.m | 32 + .../Refactor/Rename/ObjCImplicitProperty.m | 31 + clang/test/Refactor/Rename/ObjCMethod.m | 135 ++ clang/test/Refactor/Rename/ObjCMethodMacro.m | 28 + clang/test/Refactor/Rename/ObjCProperty.m | 303 +++ .../Refactor/Rename/ObjCPropertyDynamic.m | 41 + ...ertyIVarInInterfaceWithoutImplementation.m | 28 + .../Refactor/Rename/ObjCPropertyInCategory.m | 45 + .../test/Refactor/Rename/ObjCPropertyMacro.m | 21 + .../Refactor/Rename/ObjCPropertySynthesize.m | 112 + clang/test/Refactor/Rename/ObjCProtocol.m | 67 + .../Rename/ProhibitedDeclarations.cpp | 72 + .../Refactor/Rename/ProhibitedDeclarations.m | 35 + .../Rename/TemplateClassInstantiation.cpp | 39 + .../Refactor/Rename/TemplateParameters.cpp | 25 + .../test/Refactor/Rename/TemplateTypename.cpp | 24 + .../Rename/TemplatedClassFunction.cpp | 19 + .../test/Refactor/Rename/TransparentTypedef.m | 40 + clang/test/Refactor/Rename/TypedefTag.cpp | 21 + clang/test/Refactor/Rename/USRForSymbols.m | 16 + .../Refactor/Rename/UserDefinedConversion.cpp | 23 + clang/test/Refactor/Rename/UsingDecl.cpp | 46 + clang/test/Refactor/Rename/Variable.cpp | 29 + clang/test/Refactor/Rename/VariableMacro.cpp | 69 + .../Refactor/Rename/invalid-indexed-name.m | 37 + clang/test/Refactor/Rename/invalid-name.cpp | 13 + clang/test/Refactor/Rename/invalid-name.m | 23 + .../Refactor/Rename/rename-indexed-file.cpp | 123 ++ .../Refactor/Rename/rename-initiate-usr.cpp | 20 + .../test/Refactor/Rename/rename-initiate.cpp | 27 + .../Refactor/list-refactoring-actions.cpp | 39 + clang/test/Sema/enum-attr.c | 12 +- clang/test/Sema/statements.c | 8 +- clang/test/Sema/switch.c | 10 +- clang/test/SemaCXX/array-bounds.cpp | 2 +- clang/test/SemaCXX/enum-attr.cpp | 12 +- clang/test/SemaCXX/scope-check.cpp | 2 +- clang/test/SemaCXX/typo-correction.cpp | 2 +- clang/tools/CMakeLists.txt | 3 + clang/tools/c-index-test/c-index-test.c | 4 + .../tools/clang-refactor-test/CMakeLists.txt | 23 + .../clang-refactor-test/ClangRefactorTest.cpp | 1371 ++++++++++++ clang/tools/diagtool/DiagnosticNames.cpp | 1 + clang/tools/libclang/CIndexDiagnostic.cpp | 20 +- clang/tools/libclang/CIndexDiagnostic.h | 4 + clang/tools/libclang/CMakeLists.txt | 1 + clang/tools/libclang/CRefactor.cpp | 1917 +++++++++++++++++ clang/tools/libclang/libclang.exports | 53 + clang/unittests/Tooling/RefactoringTest.cpp | 170 ++ clang/unittests/libclang/LibclangTest.cpp | 69 + 244 files changed, 26698 insertions(+), 88 deletions(-) create mode 100644 clang/include/clang-c/Refactor.h create mode 100644 clang/include/clang/AST/DependentASTVisitor.h create mode 100644 clang/include/clang/Basic/DiagnosticIDs.h.rej create mode 100644 clang/include/clang/Basic/DiagnosticRefactoringKinds.td create mode 100644 clang/include/clang/Edit/RefactoringFixits.h create mode 100644 clang/include/clang/Tooling/Core/RefactoringDiagnostic.h create mode 100644 clang/include/clang/Tooling/Refactor/IndexerQuery.h create mode 100644 clang/include/clang/Tooling/Refactor/RefactoringActionFinder.h create mode 100644 clang/include/clang/Tooling/Refactor/RefactoringActions.def create mode 100644 clang/include/clang/Tooling/Refactor/RefactoringActions.h create mode 100644 clang/include/clang/Tooling/Refactor/RefactoringOperation.h create mode 100644 clang/include/clang/Tooling/Refactor/RefactoringOperationState.h create mode 100644 clang/include/clang/Tooling/Refactor/RefactoringOptionSet.h create mode 100644 clang/include/clang/Tooling/Refactor/RefactoringOptions.h create mode 100644 clang/include/clang/Tooling/Refactor/RefactoringReplacement.h create mode 100644 clang/include/clang/Tooling/Refactor/RenameIndexedFile.h create mode 100644 clang/include/clang/Tooling/Refactor/RenamedSymbol.h create mode 100644 clang/include/clang/Tooling/Refactor/RenamingOperation.h create mode 100644 clang/include/clang/Tooling/Refactor/SymbolName.h create mode 100644 clang/include/clang/Tooling/Refactor/SymbolOccurrenceFinder.h create mode 100644 clang/include/clang/Tooling/Refactor/SymbolOperation.h create mode 100644 clang/include/clang/Tooling/Refactor/USRFinder.h create mode 100644 clang/lib/Edit/FillInMissingProtocolStubs.cpp create mode 100644 clang/lib/Edit/FillInMissingSwitchEnumCases.cpp create mode 100644 clang/lib/Tooling/Refactor/ASTSlice.cpp create mode 100644 clang/lib/Tooling/Refactor/ASTSlice.h create mode 100644 clang/lib/Tooling/Refactor/ASTStateSerialization.cpp create mode 100644 clang/lib/Tooling/Refactor/CMakeLists.txt create mode 100644 clang/lib/Tooling/Refactor/Extract.cpp create mode 100644 clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp create mode 100644 clang/lib/Tooling/Refactor/FillInEnumSwitchCases.cpp create mode 100644 clang/lib/Tooling/Refactor/FillInMissingMethodStubsFromAbstractClasses.cpp create mode 100644 clang/lib/Tooling/Refactor/FillInMissingProtocolStubs.cpp create mode 100644 clang/lib/Tooling/Refactor/IfSwitchConversion.cpp create mode 100644 clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp create mode 100644 clang/lib/Tooling/Refactor/IndexerQueries.cpp create mode 100644 clang/lib/Tooling/Refactor/LocalizeObjCStringLiteral.cpp create mode 100644 clang/lib/Tooling/Refactor/RefactoringActionFinder.cpp create mode 100644 clang/lib/Tooling/Refactor/RefactoringActions.cpp create mode 100644 clang/lib/Tooling/Refactor/RefactoringContinuations.h create mode 100644 clang/lib/Tooling/Refactor/RefactoringOperation.cpp create mode 100644 clang/lib/Tooling/Refactor/RefactoringOperations.h create mode 100644 clang/lib/Tooling/Refactor/RefactoringOptions.cpp create mode 100644 clang/lib/Tooling/Refactor/RenameIndexedFile.cpp create mode 100644 clang/lib/Tooling/Refactor/RenamedSymbol.cpp create mode 100644 clang/lib/Tooling/Refactor/RenamingOperation.cpp create mode 100644 clang/lib/Tooling/Refactor/SourceLocationUtilities.cpp create mode 100644 clang/lib/Tooling/Refactor/SourceLocationUtilities.h create mode 100644 clang/lib/Tooling/Refactor/StmtUtils.cpp create mode 100644 clang/lib/Tooling/Refactor/StmtUtils.h create mode 100644 clang/lib/Tooling/Refactor/SymbolName.cpp create mode 100644 clang/lib/Tooling/Refactor/SymbolOccurrenceFinder.cpp create mode 100644 clang/lib/Tooling/Refactor/SymbolOperation.cpp create mode 100644 clang/lib/Tooling/Refactor/SymbolUSRFinder.cpp create mode 100644 clang/lib/Tooling/Refactor/TypeUtils.cpp create mode 100644 clang/lib/Tooling/Refactor/TypeUtils.h create mode 100644 clang/lib/Tooling/Refactor/USRFinder.cpp create mode 100644 clang/test/FixIt/fixit-fill-in-missing-switch-cases.cpp create mode 100644 clang/test/FixIt/fixit-fill-in-protocol-requirements.m create mode 100644 clang/test/Refactor/Extract/captured-variable-block-types.m create mode 100644 clang/test/Refactor/Extract/captured-variable-function-types.cpp create mode 100644 clang/test/Refactor/Extract/captured-variable-lambda-type.cpp create mode 100644 clang/test/Refactor/Extract/captured-variable-typedef.cpp create mode 100644 clang/test/Refactor/Extract/captured-variable-types.cpp create mode 100644 clang/test/Refactor/Extract/captured-variable-types.m create mode 100644 clang/test/Refactor/Extract/disallowed-expressions.cpp create mode 100644 clang/test/Refactor/Extract/extract-address-of-captured-variable.cpp create mode 100644 clang/test/Refactor/Extract/extract-address-of-captured-variable.mm create mode 100644 clang/test/Refactor/Extract/extract-before-comments.cpp create mode 100644 clang/test/Refactor/Extract/extract-capture-instance-variable.cpp create mode 100644 clang/test/Refactor/Extract/extract-capture-self.m create mode 100644 clang/test/Refactor/Extract/extract-capture-static-var.cpp create mode 100644 clang/test/Refactor/Extract/extract-capture-super.m create mode 100644 clang/test/Refactor/Extract/extract-capture-this.cpp create mode 100644 clang/test/Refactor/Extract/extract-capture-used-after-extraction.cpp create mode 100644 clang/test/Refactor/Extract/extract-capture-used-after-extraction.m create mode 100644 clang/test/Refactor/Extract/extract-expression.cpp create mode 100644 clang/test/Refactor/Extract/extract-from-class.cpp create mode 100644 clang/test/Refactor/Extract/extract-header-inline.h create mode 100644 clang/test/Refactor/Extract/extract-initiate.cpp create mode 100644 clang/test/Refactor/Extract/extract-initiate.m create mode 100644 clang/test/Refactor/Extract/extract-macros.cpp create mode 100644 clang/test/Refactor/Extract/extract-method.cpp create mode 100644 clang/test/Refactor/Extract/extract-method.m create mode 100644 clang/test/Refactor/Extract/extract-mutation-of-captured-variable.cpp create mode 100644 clang/test/Refactor/Extract/extract-objc-property.m create mode 100644 clang/test/Refactor/Extract/extract-reference-of-captured-variable.cpp create mode 100644 clang/test/Refactor/Extract/extract-reference-of-captured-variable.mm create mode 100644 clang/test/Refactor/Extract/extract-statement-macros.cpp create mode 100644 clang/test/Refactor/Extract/extract-statements.cpp create mode 100644 clang/test/Refactor/Extract/extract-statements.m create mode 100644 clang/test/Refactor/Extract/extract-whole-source-construct.cpp create mode 100644 clang/test/Refactor/Extract/extracted-declaration-name.mm create mode 100644 clang/test/Refactor/Extract/return-c-bool.c create mode 100644 clang/test/Refactor/Extract/return-correct-stl-type.cpp create mode 100644 clang/test/Refactor/Extract/return-objc-bool.m create mode 100644 clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-duplicates.cpp create mode 100644 clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-duplicates.m create mode 100644 clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.cpp create mode 100644 clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.m create mode 100644 clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.cpp create mode 100644 clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.m create mode 100644 clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-forward-decl.c create mode 100644 clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-initiate.cpp create mode 100644 clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-neatly-ordered.cpp create mode 100644 clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-opaque-decl.cpp create mode 100644 clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-perform.cpp create mode 100644 clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-wrap-in-compound.cpp create mode 100644 clang/test/Refactor/FillInEnumSwitchCases/initiate-on-enum-constant.c create mode 100644 clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-initiate.cpp create mode 100644 clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-no-attributes.cpp create mode 100644 clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-perform.cpp create mode 100644 clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-with-bodies.cpp create mode 100644 clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-class-extension.m create mode 100644 clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-required-only.m create mode 100644 clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-when-protocoled.m create mode 100644 clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate.m create mode 100644 clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-no-attributes.m create mode 100644 clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-perform.m create mode 100644 clang/test/Refactor/IfSwitchConversion/if-switch-conversion-initiate.cpp create mode 100644 clang/test/Refactor/IfSwitchConversion/if-switch-conversion-perform.cpp create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/Inputs/class.cpp create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/Inputs/classInHeader.cpp create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/Inputs/classInHeader.h create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/Inputs/empty.cpp create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcClass.m create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcHeader.h create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods-initiate.cpp create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods-initiate.m create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.cpp create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/local-record.cpp create mode 100644 clang/test/Refactor/ImplementDeclaredMethods/prohibited-methods.cpp create mode 100644 clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-initiate.m create mode 100644 clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-perform.m create mode 100644 clang/test/Refactor/Rename/CanonicalizeInstantiatedDecls.cpp create mode 100644 clang/test/Refactor/Rename/ClassAsTemplateArgument.cpp create mode 100644 clang/test/Refactor/Rename/ClassSimpleRenaming.cpp create mode 100644 clang/test/Refactor/Rename/ComplexFunctionOverride.cpp create mode 100644 clang/test/Refactor/Rename/ComplicatedClassType.cpp create mode 100644 clang/test/Refactor/Rename/Ctor.cpp create mode 100644 clang/test/Refactor/Rename/CtorInitializer.cpp create mode 100644 clang/test/Refactor/Rename/DeclRefExpr.cpp create mode 100644 clang/test/Refactor/Rename/DependentExpressions.cpp create mode 100644 clang/test/Refactor/Rename/Field.cpp create mode 100644 clang/test/Refactor/Rename/FunctionMacro.cpp create mode 100644 clang/test/Refactor/Rename/FunctionOverride.cpp create mode 100644 clang/test/Refactor/Rename/IndexedObjCMessageSend.mm create mode 100644 clang/test/Refactor/Rename/IndexedObjCMethod.m create mode 100644 clang/test/Refactor/Rename/IndexedObjCMethodDecl.mm create mode 100644 clang/test/Refactor/Rename/IndexedObjCProperty.m create mode 100644 clang/test/Refactor/Rename/Inputs/MultiFileTUHeader.h create mode 100644 clang/test/Refactor/Rename/Inputs/ObjCImplementationTURequestsImplementation.m create mode 100644 clang/test/Refactor/Rename/Inputs/objc-system-header.h create mode 100644 clang/test/Refactor/Rename/Inputs/rename-indexed-file.cpp create mode 100644 clang/test/Refactor/Rename/Inputs/system-header.h create mode 100644 clang/test/Refactor/Rename/LocalBlockSymbol.m create mode 100644 clang/test/Refactor/Rename/LocalBlockSymbolCpp.mm create mode 100644 clang/test/Refactor/Rename/LocalSymbol.cpp create mode 100644 clang/test/Refactor/Rename/MemberExprMacro.cpp create mode 100644 clang/test/Refactor/Rename/MultiFileTU.cpp create mode 100644 clang/test/Refactor/Rename/Namespace.cpp create mode 100644 clang/test/Refactor/Rename/NoNewName.cpp create mode 100644 clang/test/Refactor/Rename/ObjCClass.m create mode 100644 clang/test/Refactor/Rename/ObjCClassProperty.m create mode 100644 clang/test/Refactor/Rename/ObjCCompatibilityAlias.m create mode 100644 clang/test/Refactor/Rename/ObjCImplementationTURequests.m create mode 100644 clang/test/Refactor/Rename/ObjCImplicitProperty.m create mode 100644 clang/test/Refactor/Rename/ObjCMethod.m create mode 100644 clang/test/Refactor/Rename/ObjCMethodMacro.m create mode 100644 clang/test/Refactor/Rename/ObjCProperty.m create mode 100644 clang/test/Refactor/Rename/ObjCPropertyDynamic.m create mode 100644 clang/test/Refactor/Rename/ObjCPropertyIVarInInterfaceWithoutImplementation.m create mode 100644 clang/test/Refactor/Rename/ObjCPropertyInCategory.m create mode 100644 clang/test/Refactor/Rename/ObjCPropertyMacro.m create mode 100644 clang/test/Refactor/Rename/ObjCPropertySynthesize.m create mode 100644 clang/test/Refactor/Rename/ObjCProtocol.m create mode 100644 clang/test/Refactor/Rename/ProhibitedDeclarations.cpp create mode 100644 clang/test/Refactor/Rename/ProhibitedDeclarations.m create mode 100644 clang/test/Refactor/Rename/TemplateClassInstantiation.cpp create mode 100644 clang/test/Refactor/Rename/TemplateParameters.cpp create mode 100644 clang/test/Refactor/Rename/TemplateTypename.cpp create mode 100644 clang/test/Refactor/Rename/TemplatedClassFunction.cpp create mode 100644 clang/test/Refactor/Rename/TransparentTypedef.m create mode 100644 clang/test/Refactor/Rename/TypedefTag.cpp create mode 100644 clang/test/Refactor/Rename/USRForSymbols.m create mode 100644 clang/test/Refactor/Rename/UserDefinedConversion.cpp create mode 100644 clang/test/Refactor/Rename/UsingDecl.cpp create mode 100644 clang/test/Refactor/Rename/Variable.cpp create mode 100644 clang/test/Refactor/Rename/VariableMacro.cpp create mode 100644 clang/test/Refactor/Rename/invalid-indexed-name.m create mode 100644 clang/test/Refactor/Rename/invalid-name.cpp create mode 100644 clang/test/Refactor/Rename/invalid-name.m create mode 100644 clang/test/Refactor/Rename/rename-indexed-file.cpp create mode 100644 clang/test/Refactor/Rename/rename-initiate-usr.cpp create mode 100644 clang/test/Refactor/Rename/rename-initiate.cpp create mode 100644 clang/test/Refactor/list-refactoring-actions.cpp create mode 100644 clang/tools/clang-refactor-test/CMakeLists.txt create mode 100644 clang/tools/clang-refactor-test/ClangRefactorTest.cpp create mode 100644 clang/tools/libclang/CRefactor.cpp diff --git a/clang/include/clang-c/CXErrorCode.h b/clang/include/clang-c/CXErrorCode.h index aff73b746763b..9bee50b4b741f 100644 --- a/clang/include/clang-c/CXErrorCode.h +++ b/clang/include/clang-c/CXErrorCode.h @@ -54,7 +54,25 @@ enum CXErrorCode { /** * \brief An AST deserialization error has occurred. */ - CXError_ASTReadError = 4 + CXError_ASTReadError = 4, + + /** + * \brief A refactoring action is not available at the given location + * or in the given source range. + */ + CXError_RefactoringActionUnavailable = 5, + + /** + * \brief A refactoring action is not able to use the given name because + * it contains an unexpected number of strings. + */ + CXError_RefactoringNameSizeMismatch = 6, + + /** + * \brief A name of a symbol is invalid, i.e. it is reserved by the source + * language and can't be used as a name for this symbol. + */ + CXError_RefactoringNameInvalid = 7 }; #ifdef __cplusplus diff --git a/clang/include/clang-c/Refactor.h b/clang/include/clang-c/Refactor.h new file mode 100644 index 0000000000000..869f562726b6d --- /dev/null +++ b/clang/include/clang-c/Refactor.h @@ -0,0 +1,1288 @@ +/*==-- clang-c/Refactor.h - Refactoring Public C Interface --------*- C -*-===*\ +|* *| +|* The LLVM Compiler Infrastructure *| +|* *| +|* This file is distributed under the University of Illinois Open Source *| +|* License. See LICENSE.TXT for details. *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This header provides a public inferface to a Clang library for performing *| +|* refactoring actions on projects without exposing the full Clang C++ API. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_CLANG_C_REFACTOR_H +#define LLVM_CLANG_C_REFACTOR_H + +#include "clang-c/Index.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup CINDEX_REFACTOR Refactoring options. + * + * @{ + */ + +/** + * \brief The refactoring options that can be specified for each refactoring + * action. + */ +enum CXRefactoringOption { + /** + * \brief The refactoring actions like 'rename' will avoid looking for + * occurrences of the renamed symbol in comments if this option is enabled. + */ + CXRefactorOption_AvoidTextualMatches = 1 +}; + +/** + * \brief Opaque pointer representing a set of options that can be given to + * a refactoring action. + */ +typedef void *CXRefactoringOptionSet; + +/** + * \brief Returns a new option set. + */ +CINDEX_LINKAGE +CXRefactoringOptionSet clang_RefactoringOptionSet_create(); + +/** + * \brief Parses and returns a new option set or NULL if the given string is + * invalid. + */ +CINDEX_LINKAGE +CXRefactoringOptionSet +clang_RefactoringOptionSet_createFromString(const char *String); + +/** + * \brief Adds a new option to the given refactoring option set. + */ +CINDEX_LINKAGE +void clang_RefactoringOptionSet_add(CXRefactoringOptionSet Set, + enum CXRefactoringOption Option); + +/** + * \brief Converts the given refactoring option set to a string value. + */ +CINDEX_LINKAGE +CXString clang_RefactoringOptionSet_toString(CXRefactoringOptionSet Set); + +/** + * \brief Free the given option set. + * + * Option sets should be freed by this function only when they were created + * using the \c clang_RefactoringOptionSet_create* methods. + */ +CINDEX_LINKAGE +void clang_RefactoringOptionSet_dispose(CXRefactoringOptionSet Set); + +/** + * @} + */ + +/** + * \defgroup CINDEX_REFACTOR Refactoring actions. + * + * @{ + */ + +/** + * \brief The refactoring actions that can be performed by libclang. + */ +enum CXRefactoringActionType { + /** + * \brief The 'rename' refactoring action. + */ + CXRefactor_Rename = 0, + + /** + * \brief The local 'rename' refactoring action. + */ + CXRefactor_Rename_Local = 1, + + /** + * \brief The 'extract' refactoring action extracts source code into a + * new function. + */ + CXRefactor_Extract = 2, + + /** + * \brief The sub-action of 'extract' that extracts source code into a new + * method. + */ + CXRefactor_Extract_Method = 3, + + /** + * \brief The action that converts an if/else constructs to a switch block. + */ + CXRefactor_IfSwitchConversion = 4, + + /** + * \brief The action that wraps an Objective-C string literal in an + * NSLocalizedString macro. + */ + CXRefactor_LocalizeObjCStringLiteral = 5, + + /** + * \brief The action that adds missing switch cases to an switch over an enum. + */ + CXRefactor_FillInEnumSwitchCases = 6, + + /** + * \brief The action that adds missing protocol methods to an Objective-C + * class. + */ + CXRefactor_FillInMissingProtocolStubs = 7, + + /** + * \brief The action that extracts an expression that's repeated in a function + * into a new variable. + */ + CXRefactor_ExtractRepeatedExpressionIntoVariable = 8, + + /** + * \brief The action that adds missing abstract class method overrides to a + * class. + */ + CXRefactor_FillInMissingMethodStubsFromAbstractClasses = 9, + + /** + * \brief The action that generates dummy method definitions for method + * declarations without respective definitions. + */ + CXRefactor_ImplementDeclaredMethods = 10, +}; + +/** + * \brief Return the name of the given refactoring action. + */ +CINDEX_LINKAGE +CXString +clang_RefactoringActionType_getName(enum CXRefactoringActionType Action); + +/** + * \brief A set of refactoring actions that can be performed at some specific + * location in a source file. + * + * The actions in the action set are ordered by their priority: most important + * actions are placed before the less important ones. + */ +typedef struct { + const enum CXRefactoringActionType *Actions; + unsigned NumActions; +} CXRefactoringActionSet; + +/** + * \brief Free the given refactoring action set. + */ +CINDEX_LINKAGE void +clang_RefactoringActionSet_dispose(CXRefactoringActionSet *Set); + +typedef struct { + enum CXRefactoringActionType Action; + /** + * \brief The set of diagnostics that describes the reason why this action + * couldn't be initiated. This set of diagnostics is managed by the + * \c CXRefactoringActionSetWithDiagnostics and shouldn't be freed manually. + */ + CXDiagnosticSet Diagnostics; +} CXRefactoringActionWithDiagnostics; + +/** + * \brief A set of refactoring actions that couldn't be initiated at some + * location and their respective diagnostics that describe the reason why + * the initiation failed. + */ +typedef struct { + CXRefactoringActionWithDiagnostics *Actions; + unsigned NumActions; +} CXRefactoringActionSetWithDiagnostics; + +/** + * \brief Free the given refactoring action set with diagnostics. + */ +CINDEX_LINKAGE void clang_RefactoringActionSetWithDiagnostics_dispose( + CXRefactoringActionSetWithDiagnostics *Set); + +/** + * \brief Find the set of refactoring actions that can be performed at the given + * location. + * + * This function examines the AST around the given source range and creates a + * \c CXRefactoringActionSet that contains all of the actions that can be + * performed in the given source range. + * + * \param TU The translation unit which contains the given source range. + * + * \param Location The location at which the refactoring action will be + * performed. + * + * \param SelectionRange The range in which the AST should be checked. Usually + * corresponds to the selection range or location of the cursor in the editor. + * Can be a null range. + * + * \param Options The optional refactoring options that might influence the way + * the search is performed. + * + * \param[out] OutSet A non-NULL pointer to store the created + * \c CXRefactoringActionSet. + * + * \returns Zero on success, CXError_RefactoringActionUnavailable when + * there are no actions available in the given range, or an error code + * otherwise. + */ +CINDEX_LINKAGE +enum CXErrorCode +clang_Refactoring_findActionsAt(CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, + CXRefactoringOptionSet Options, + CXRefactoringActionSet *OutSet); + +/** + * \brief Find the set of refactoring actions that can be performed at the given + * location. + * + * This function examines the AST around the given source range and creates a + * \c CXRefactoringActionSet that contains all of the actions that can be + * performed in the given source range. It also creates a + * \c CXRefactoringActionSetWithDiagnostics that might describe the reason why + * some refactoring actions are not be available. + * + * \param TU The translation unit which contains the given source range. + * + * \param Location The location at which the refactoring action will be + * performed. + * + * \param SelectionRange The range in which the AST should be checked. Usually + * corresponds to the selection range or location of the cursor in the editor. + * Can be a null range. + * + * \param Options The optional refactoring options that might influence the way + * the search is performed. + * + * \param[out] OutSet A non-NULL pointer to store the created + * \c CXRefactoringActionSet. + * + * \param[out] OutFailureSet An optional pointer to store the created + * \c CXRefactoringActionSetWithDiagnostics that describes the failures reasons + * for some of the refactoring actions. + * + * \returns Zero on success, CXError_RefactoringActionUnavailable when + * there are no actions available in the given range, or an error code + * otherwise. + */ +CINDEX_LINKAGE +enum CXErrorCode clang_Refactoring_findActionsWithInitiationFailureDiagnosicsAt( + CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, CXRefactoringOptionSet Options, + CXRefactoringActionSet *OutSet, + CXRefactoringActionSetWithDiagnostics *OutFailureSet); + +/** + * @} + */ + +/** + * \defgroup CINDEX_REFACTOR_INITIATE Refactoring initiation + * + * @{ + */ + +/** + * \brief Opaque pointer representing the initiated refactoring action. + */ +typedef void *CXRefactoringAction; + +/** + * \brief Free the given refactoring action. + * + * The refactoring action should be freed before the initiation and/or + * implementation translation units. + */ +CINDEX_LINKAGE void clang_RefactoringAction_dispose(CXRefactoringAction Action); + +/** + * \brief Return the source range that's associated with the initiated + * refactoring action. + * + * The returned source range covers the source that will be modified by the + * given refactoring action. If the action has no associated source range, + * then this function will return a null \c CXSourceRange. + */ +CINDEX_LINKAGE CXSourceRange +clang_RefactoringAction_getSourceRangeOfInterest(CXRefactoringAction Action); + +/** + * \brief Return the type of the initiated action, which might be different + * to the type of the requested action. For an operation 'rename', the action + * could actually initiate the local 'rename' operation. + */ +CINDEX_LINKAGE +enum CXRefactoringActionType +clang_RefactoringAction_getInitiatedActionType(CXRefactoringAction Action); + +/** + * \brief Return a non-zero value when the refactoring action requires access + * to an additional translation unit that contains an implementation of some + * declaration. + */ +// TODO: Remove (this is no longer needed due to refactoring continuations). +CINDEX_LINKAGE +int clang_RefactoringAction_requiresImplementationTU( + CXRefactoringAction Action); + +/** + * \brief Return a USR that corresponds to the declaration whose implementation + * is required in order for the given refactoring action to work correctly. + */ +// TODO: Remove (this is no longer needed due to refactoring continuations). +CINDEX_LINKAGE +CXString clang_RefactoringAction_getUSRThatRequiresImplementationTU( + CXRefactoringAction Action); + +/** + * \brief Set the translation unit that contains the declaration whose + * implementation is required for the given refactoring action to work + * correctly. + */ +// TODO: Remove (this is no longer needed due to refactoring continuations). +CINDEX_LINKAGE +enum CXErrorCode +clang_RefactoringAction_addImplementationTU(CXRefactoringAction Action, + CXTranslationUnit TU); + +/** + * \brief A refactoring candidate determines on which piece of source code the + * action should be applied. + * + * Most refactoring actions have just one candidate, but some actions, like + * 'Extract' can produce multiple candidates. + * + * The candidates are managed by the refactoring action, and their description + * string doesn't need to be freed manually. + */ +typedef struct { CXString Description; } CXRefactoringCandidate; + +/** + * \brief A set of refactoring candidates on which the previously initiatied + * refactoring action can be performed. + * + * The candidates in the candidate set are ordered by their priority: the + * ones that are more likely to be selected are placed before the other ones. + * + * A non-empty refactoring candidate set always has more than one refactoring + * candidate, because when a refactoring action has just one candidate, + * \c clang_RefactoringAction_getRefactoringCandidates will return an empty + * candidate set. + */ +typedef struct { + const CXRefactoringCandidate *Candidates; + unsigned NumCandidates; +} CXRefactoringCandidateSet; + +/** + * \brief Returns the given action's refactoring candidates. + * + * The resulting refactoring candidate set will be empty when the given \c + * CXRefactoringAction has just one refactoring candidate. + * + * \param Action A previously initiated \c CXRefactoringAction. + * + * \param[out] OutRefactoringCandidateSet An pointer to store the action's + * refactoring candidate set. + * + * \returns Zero on success, or an error code otherwise. + */ +CINDEX_LINKAGE +enum CXErrorCode clang_RefactoringAction_getRefactoringCandidates( + CXRefactoringAction Action, + CXRefactoringCandidateSet *OutRefactoringCandidateSet); + +/** + * \brief Tells the given refactoring action that it has to perform the + * operation on the refactoring candidate that's located at \p Index in the \c + * CXRefactoringCandidateSet. + */ +CINDEX_LINKAGE +enum CXErrorCode +clang_RefactoringAction_selectRefactoringCandidate(CXRefactoringAction Action, + unsigned Index); + +// TODO: Remove. +CINDEX_LINKAGE +enum CXErrorCode clang_Refactoring_initiateActionAt( + CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, enum CXRefactoringActionType ActionType, + CXRefactoringOptionSet Options, CXRefactoringAction *OutAction, + CXString *OutFailureReason); + +/** + * \brief Initiate a specific refactoring action at the given location. + * + * This function initiates an \p ActionType refactoring action when it can + * be initiated at the given location and creates a \c CXRefactoringAction + * action that will allow the control. + * + * \param TU The translation unit in which the action should be initiated. + * + * \param Location The location at which the refactoring action will be + * performed. + * + * \param SelectionRange The range in which the AST should be checked. Usually + * corresponds to the selection range or location of the cursor in the editor. + * Can be a null range. + * + * \param ActionType The type of action that should be initiated. + * + * \param Options The optional refactoring options that might have an influence + * on the initiation process. + * + * \param[out] OutAction A non-NULL pointer to store the created + * \c CXRefactoringAction. + * + * \param[out] OutDiagnostics An optional pointer to store any diagnostics that + * describe why the action wasn't initiated. + * + * \returns Zero on success, CXError_RefactoringActionUnavailable when + * the given refactoring action can't be performed at the given location, or an + * error code otherwise. + */ +CINDEX_LINKAGE +enum CXErrorCode clang_Refactoring_initiateAction( + CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, enum CXRefactoringActionType ActionType, + CXRefactoringOptionSet Options, CXRefactoringAction *OutAction, + CXDiagnosticSet *OutDiagnostics); + +/** + * \brief Initiate a specific refactoring action on a particular declaration. + * + * This function searches for the declaration that corresponds to \p DeclUSR + * and initiates an \p ActionType a refactoring action on that declaration + * if possible. + * + * \param TU The translation unit in which the declaration is defined. + * + * \param DeclUSR The USR that corresponds to the declaration of interest. + * + * \param ActionType The type of action that should be initiated. + * + * \param Options The optional refactoring options that might have an influence + * on the initiation process. + * + * \param[out] OutAction A non-NULL pointer to store the created + * \c CXRefactoringAction. + * + * \returns Zero on success, CXError_RefactoringActionUnavailable when + * the given refactoring action can't be performed on the found declaration, or + * an error code otherwise. + */ +// TODO: Remove (not needed). +CINDEX_LINKAGE +enum CXErrorCode clang_Refactoring_initiateActionOnDecl( + CXTranslationUnit TU, const char *DeclUSR, + enum CXRefactoringActionType ActionType, CXRefactoringOptionSet Options, + CXRefactoringAction *OutAction, CXString *OutFailureReason); + +/** + * @} + */ + +/** + * \defgroup CINDEX_REFACTOR_REPLACEMENT Refactoring replacement + * + * @{ + */ + +/** + * \brief A source location in a single file that is independent of \c + * CXTranslationUnit. + */ +typedef struct { unsigned Line, Column; } CXFileLocation; + +/** + * \brief A source range in a single file that is independent of \c + * CXTranslationUnit. + */ +typedef struct { CXFileLocation Begin, End; } CXFileRange; + +// TODO: Remove +typedef struct { + CXFileRange Range; + CXString ReplacementString; +} CXRefactoringReplacement_Old; + +// TODO: Remove +typedef struct { + CXString Filename; + const CXRefactoringReplacement_Old *Replacements; + unsigned NumReplacements; +} CXRefactoringFileReplacementSet_Old; + +// TODO: Remove +typedef struct { + const CXRefactoringFileReplacementSet_Old *FileReplacementSets; + unsigned NumFileReplacementSets; +} CXRefactoringReplacements_Old; + +/** + * \brief Identifies a character range in the source code of a single file that + * should be replaced with the replacement string. + * + * Replacements are managed by the result of a specific refactoring action, + * like \c CXRenamingResult, and are invalidated when the refactoring result is + * destroyed. + */ +typedef struct { + CXFileRange Range; + CXString ReplacementString; + void *AssociatedData; +} CXRefactoringReplacement; + +/** +* \brief A set of refactoring replacements that are applicable to a certain + * file. + */ +typedef struct { + CXString Filename; + const CXRefactoringReplacement *Replacements; + unsigned NumReplacements; +} CXRefactoringFileReplacementSet; + +/** + * \brief A set of refactoring replacements that have been produced by a + * refactoring operation. + * + * The refactoring replacements depend on \c CXRefactoringResult, and can't be + * used after the refactoring result is freed. + */ +typedef struct { + const CXRefactoringFileReplacementSet *FileReplacementSets; + unsigned NumFileReplacementSets; +} CXRefactoringReplacements; + +/** + * @} + */ + +/** + * \defgroup CINDEX_SYMBOL_OPERATION Symbol-based refactoring operation + * (e.g. Rename). + * + * @{ + */ + +/** + * \brief The type of a symbol occurrence. + * + * The occurrence kind determines if an occurrence can be renamed automatically + * or if the user has to make the decision whether or not this occurrence + * should be renamed. + */ +enum CXSymbolOccurrenceKind { + /** + * \brief This occurrence is an exact match and can be renamed automatically. + */ + CXSymbolOccurrence_MatchingSymbol = 0, + + /** + * \brief This is an occurrence of a matching selector. It can't be renamed + * automatically unless the indexer proves that this selector refers only + * to the declarations that correspond to the renamed symbol. + */ + CXSymbolOccurrence_MatchingSelector = 1, + + /** + * \brief This is an occurrence of an implicit property that uses the + * renamed method. + */ + CXSymbolOccurrence_MatchingImplicitProperty = 2, + + /** + * \brief This is an occurrence of an symbol name in a comment. + */ + CXSymbolOccurrence_MatchingCommentString = 3, + + /** + * \brief This is an occurrence of an symbol name in a documentation comment. + */ + CXSymbolOccurrence_MatchingDocCommentString = 4, + + /** + * \brief This is an occurrence of an symbol name in a filename in an inclusion + * directive. + */ + CXSymbolOccurrence_MatchingFilename = 5, + + /** + * \brief This is an occurrence of a symbol name that belongs to the extracted + * declaration. Note: this occurrence can be in two replacements as we might + * extract an out-of-line method that will be both declared and defined. + */ + CXSymbolOccurrence_ExtractedDeclaration = 100, + + /** + * \brief This is an occurrence of a symbol name that references the extracted + * declaration. + */ + CXSymbolOccurrence_ExtractedDeclaration_Reference = 101, +}; + +// TODO: Remove +typedef struct { + const CXRefactoringReplacement_Old *Replacements; + unsigned ReplacementCount; + enum CXSymbolOccurrenceKind Kind; + /** + * Whether or not this occurrence is inside a macro. When this is true, the + * replacements of the occurrence contain just a single empty replacement that + * points to the location of the macro expansion. + */ + int IsMacroExpansion; +} CXRenamedSymbolOccurrence; + +/** + * \brief An occurrence of a symbol. + * + * Contains the source ranges that represent the pieces of the name of the + * symbol. The occurrences are managed by \c CXRenamingResult, and are + * invalidated when \c CXRenamingResult is destroyed. + */ +typedef struct { + const CXFileRange *NamePieces; + unsigned NumNamePieces; + enum CXSymbolOccurrenceKind Kind; + /** + * Whether or not this occurrence is inside a macro. When this is true, the + * replacements of the occurrence contain just a single empty replacement that + * points to the location of the macro expansion. + */ + int IsMacroExpansion; + unsigned SymbolIndex; +} CXSymbolOccurrence; + +// TODO: Remove +typedef struct { + CXString Filename; + const CXRenamedSymbolOccurrence *Occurrences; + unsigned NumOccurrences; +} CXFileRenamingResult; // TODO: Remove + +/** +* \brief A set of symbol occurrences that occur in a single file. + */ +typedef struct { + CXString Filename; + /** + * The set of occurrences for each symbol of interest. + */ + const CXSymbolOccurrence *Occurrences; + unsigned NumOccurrences; +} CXSymbolOccurrencesInFile; + +/** + * \brief Opaque pointer representing all of the renames that should take place + * in a single translation unit. + * + * The result of a renaming action is indepedent from \c CXRenamingAction, and + * remains valid after \c CXRenamingAction is destroyed. + */ +typedef void *CXRenamingResult; + +/** + * \brief Opaque pointer representing all of the symbol occurrences from a + * single TU/file. + * + * The result of a symbol search occurrence search operation is indepedent from + * \c CXRefactoringAction, and remains valid after \c CXRefactoringAction is + * destroyed. + */ +typedef void *CXSymbolOccurrencesResult; + +/** + * \brief Find the cursor that's being renamed at the given location. + * + * \param TU The translation unit in which the cursor is present. + * + * \param Location The location at which the refactoring action will be + * performed. + * + * \param SelectionRange The range in which the AST should be checked. Usually + * corresponds to the selection range or location of the cursor in the editor. + * Can be a null range. + * + * \returns Zero on success, CXError_RefactoringActionUnavailable when + * there's no suitable cursor at the given location, or an error code otherwise. + */ +CINDEX_LINKAGE +enum CXErrorCode clang_Refactoring_findRenamedCursor( + CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, CXCursor *OutCursor); + +/** + * \brief Initiates a renaming operation on a previously initiated refactoring + * action. + * + * The initiation process finds the symbols that have to be renamed for a + * previously initiated \c CXRefactor_Rename refactoring action. + * + * \returns Zero on success, or an error code otherwise. + */ +// TODO: Remove +CINDEX_LINKAGE +enum CXErrorCode +clang_Refactoring_initiateRenamingOperation(CXRefactoringAction Action); + +/** + * \brief Set the new name of the renamed symbol in the given \c + * RenamingAction. + * + * \returns Zero on success, CXError_RefactoringNameInvalid when the new name + * isn't a valid identifier, CXError_RefactoringNameSizeMismatch when the new + * name has an incorrect number of pieces or a different error code otherwise. + */ +// TODO: Remove +CINDEX_LINKAGE +enum CXErrorCode clang_RenamingOperation_setNewName(CXRefactoringAction Action, + const char *NewName); + +/** + * \brief Return the number of symbols that are renamed by the given renaming + * action. + * + * A renaming action typically works on just one symbol. However, there are + * certain language constructs that require work with more than one symbol in + * order for them to be renamed correctly. Property declarations in Objective-C + * are the perfect example: in addition to the actual property, the action has + * to rename the corresponding getters and setters, as well as the backing ivar. + */ +// TODO: Remove +CINDEX_LINKAGE +unsigned clang_RenamingOperation_getNumSymbols(CXRefactoringAction Action); + +/** + * \brief Return the USR of the declaration that was found for the symbol at the + * given \p Index in the given renaming action. + */ +// TODO: Remove +CINDEX_LINKAGE +CXString clang_RenamingOperation_getUSRForSymbol(CXRefactoringAction Action, + unsigned Index); + +// TODO: Remove +CINDEX_LINKAGE +CXRenamingResult clang_Refactoring_findRenamedOccurrencesInPrimaryTUs( + CXRefactoringAction Action, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles); + +/** + * \brief Find all of the occurrences of the symbol that is being searched for + * by the given refactoring action in the translation unit that was used to + * initiate the refactoring action. + * + * This function searches for all of the \c CXSymbolOccurrence in the + * translation units that are referenced by the given \c CXRefactoringAction by + * iterating through the AST of the each translation unit. The occurrences that + * are found don't have to be from the main file in the translation unit, they + * can be from files included in that translation unit. + * + * \param Action The \c CXRefactoringAction operation that was inititated by + * \c clang_Refactoring_initiateActionAt(). + * + * \param CommandLineArgs The command-line arguments that would be + * passed to the \c clang executable if it were being invoked out-of-process. + * + * \param NumCommandLineArgs The number of command-line arguments in + * \c CommandLineArgs. + * + * \param UnsavedFiles the files that have not yet been saved to disk + * but may be required for parsing, including the contents of + * those files. The contents and name of these files (as specified by + * CXUnsavedFile) are copied when necessary, so the client only needs to + * guarantee their validity until the call to this function returns. + * + * \param NumUnsavedFiles the number of unsaved file entries in \p + * UnsavedFiles. + * + * \returns If successful, a new \c CXSymbolOccurrencesResult structure + * containing the occurrences of the symbol in the initiation translation unit, + * which should eventually be freed with \c clang_SymbolOccurrences_dispose(). + * If symbol search fails, returns NULL. + */ +CINDEX_LINKAGE +CXSymbolOccurrencesResult clang_Refactoring_findSymbolOccurrencesInInitiationTU( + CXRefactoringAction Action, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles); + +// TODO: Remove +typedef struct { + CXFileLocation Location; + /** + * The kind of the declaration/expression that was indexed at this location. + * This is particularly important for Objective-C selectors. The refactoring + * engine requires the following cursor kinds for the following indexed + * occurrences: + * - ObjC method declaration: CXCursor_ObjC(Instance/Class)MethodDecl + * - ObjC method message send: CXCursor_ObjCMessageExpr + * Other occurrences can use any other cursor cursor kinds. + */ + enum CXCursorKind CursorKind; +} CXRenamedIndexedSymbolLocation; + +// TODO: Remove +typedef struct { + /** + * An array of occurrences that represent indexed occurrences of a symbol. + * It's valid to pass-in no indexed locations, the refactoring engine will + * just perform textual search in that case. + */ + const CXRenamedIndexedSymbolLocation *IndexedLocations; + unsigned IndexedLocationCount; + /** + * The kind of the declaration that is being renamed. + * This is particularly important for Objective-C selectors. The refactoring + * engine requires the following cursor kinds for the following renamed + * declaration: + * - ObjC methods: CXCursor_ObjC(Instance/Class)MethodDecl + * Other declarations can use any other cursor cursor kinds. + */ + enum CXCursorKind CursorKind; + const char *Name; + const char *NewName; +} CXRenamedIndexedSymbol; + +// TODO: Remove +CINDEX_LINKAGE +enum CXErrorCode clang_Refactoring_findRenamedOccurrencesInIndexedFile( + const CXRenamedIndexedSymbol *Symbols, unsigned NumSymbols, CXIndex CIdx, + const char *Filename, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles, CXRefactoringOptionSet Options, + CXRenamingResult *OutResult); + +/** + * \brief A location of an already known occurrence of a symbol. + * + * Used for rename-indexed operation where the renaming is performed on an + * already indexed source file. + */ +typedef struct { + CXFileLocation Location; + /** + * The kind of the declaration/expression that was indexed at this location. + * This is particularly important for Objective-C selectors. The refactoring + * engine requires the following cursor kinds for the following indexed + * occurrences: + * - ObjC method declaration: CXCursor_ObjC(Instance/Class)MethodDecl + * - ObjC method message send: CXCursor_ObjCMessageExpr + * - filename in an #include: CXCursor_InclusionDirective + * Other occurrences can use any other cursor cursor kinds. + */ + enum CXCursorKind CursorKind; +} CXIndexedSymbolLocation; + +/** + * \brief A symbol that should be found the an indexer symbol search operation. + * + * Used for rename-indexed operation where the renaming is performed on an + * already indexed source file. + */ +typedef struct { + /** + * An array of occurrences that represent indexed occurrences of a symbol. + * It's valid to pass-in no indexed locations, the refactoring engine will + * just perform textual search in that case. + */ + const CXIndexedSymbolLocation *IndexedLocations; + unsigned IndexedLocationCount; + /** + * The kind of the declaration that is being renamed. + * This is particularly important for Objective-C selectors. The refactoring + * engine requires the following cursor kinds for the following renamed + * declaration: + * - ObjC methods: CXCursor_ObjC(Instance/Class)MethodDecl + * Other declarations can use any other cursor cursor kinds. + */ + enum CXCursorKind CursorKind; + /** + * The name of the symbol. Objective-C selector names should be specified + * using the ':' separator for selector pieces. + */ + const char *Name; +} CXIndexedSymbol; + +/** + * \brief Find all of the occurrences of a symbol in an indexed file. + * + * This function searches for all of the \c CXIndexedSymbol in the + * given file by inspecting the source code at the given indexed locations. + * + * The indexed operations are thread-safe and can be performed concurrently. + * + * \param Symbols The information about the symbols that includes the locations + * for a symbol in the file as determined by the indexer. + * + * \param NumSymbols The number of symbols in \p Symbols. + * + * \param CIdx The index object with which the translation unit will be + * associated. + * + * \param Filename The name of the source file that contains the given + * \p Locations. + * + * \param CommandLineArgs The command-line arguments that would be + * passed to the \c clang executable if it were being invoked out-of-process. + * These command-line options will be parsed and will affect how the translation + * unit is parsed. + * + * \param NumCommandLineArgs The number of command-line arguments in + * \c CommandLineArgs. + * + * \param UnsavedFiles the files that have not yet been saved to disk + * but may be required for parsing, including the contents of + * those files. The contents and name of these files (as specified by + * CXUnsavedFile) are copied when necessary, so the client only needs to + * guarantee their validity until the call to this function returns. + * + * \param NumUnsavedFiles the number of unsaved file entries in \p + * UnsavedFiles. + * + * \param Options The optional refactoring options that might have an influence + * on the initiation process. + * + * \param[out] OutResult A non-NULL pointer to store the created + * \c CXSymbolOccurrencesResult. + * + * \returns Zero on success, or a different error code otherwise. + */ +CINDEX_LINKAGE +enum CXErrorCode clang_Refactoring_findSymbolOccurrencesInIndexedFile( + const CXIndexedSymbol *Symbols, unsigned NumSymbols, CXIndex CIdx, + const char *Filename, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles, CXRefactoringOptionSet Options, + CXSymbolOccurrencesResult *OutResult); + +// TODO: Remove +CINDEX_LINKAGE +unsigned clang_RenamingResult_getNumModifiedFiles(CXRenamingResult Result); + +// TODO: Remove +CINDEX_LINKAGE +void clang_RenamingResult_getResultForFile(CXRenamingResult Result, + unsigned FileIndex, + CXFileRenamingResult *OutResult); + +// TODO: Remove +CINDEX_LINKAGE +void clang_RenamingResult_dispose(CXRenamingResult Result); + +/** + * \brief Return the number of files that have occurrences of the specific + * symbol. + */ +CINDEX_LINKAGE +unsigned clang_SymbolOccurrences_getNumFiles(CXSymbolOccurrencesResult Result); + +/** + * \brief Return the set of symbol occurrences in a single file. + * + * The resulting \c CXSymbolOccurrencesInFile is managed by the + * \c CXSymbolOccurrencesResult and doesn't have to be disposed of manually. + */ +CINDEX_LINKAGE +void clang_SymbolOccurrences_getOccurrencesForFile( + CXSymbolOccurrencesResult Result, unsigned FileIndex, + CXSymbolOccurrencesInFile *OutResult); + +// TODO: Support refactoring continuations for \c CXSymbolOccurrencesResult, +// e.g. for function parameter name rename. + +/** + * \brief Free the given symbol occurrences result. + */ +CINDEX_LINKAGE +void clang_SymbolOccurrences_dispose(CXSymbolOccurrencesResult Result); + +/** + * @} + */ + +/** + * \defgroup CINDEX_REFACTOR_PERFORM Performing refactoring operations. + * + * @{ + */ + +/** + * \brief Opaque pointer representing the results of the refactoring operation. + * + * The result of a refactoring action depends on the \c CXRefactoringAction, and + * is invalidated after \c CXRefactoringAction is destroyed. + */ +typedef void *CXRefactoringResult; + +/** + * \brief Opaque pointer representing a refactoring continuation. + * + * Refactoring continuations allow refactoring operations to run in external + * AST units with some results that were obtained after querying the indexer. + * + * The refactoring continuation is not dependent on the \c CXRefactoringAction + * or \c CXRefactoringResult. It does depend on the initiation + * \c CXTranslationUnit initially, but that dependency can be terminated. + */ +typedef void *CXRefactoringContinuation; + +/** + * \brief Opaque pointer representing a query to the indexer. + */ +typedef void *CXIndexerQuery; + +/** + * \brief Performs the previously initiated refactoring operation. + * + * This function executes the refactoring operation which produces a set of + * candidate source replacements that can be applied to the source files. + * + * \param Action The refactoring action. + * + * \param CommandLineArgs The command-line arguments that would be + * passed to the \c clang executable if it were being invoked out-of-process. + * These command-line options will be parsed and will affect how the translation + * unit is parsed. + * + * \param NumCommandLineArgs The number of command-line arguments in + * \c CommandLineArgs. + * + * \param UnsavedFiles the files that have not yet been saved to disk + * but may be required for parsing, including the contents of + * those files. The contents and name of these files (as specified by + * CXUnsavedFile) are copied when necessary, so the client only needs to + * guarantee their validity until the call to this function returns. + * + * \param NumUnsavedFiles the number of unsaved file entries in \p + * UnsavedFiles. + * + * \param Options The optional refactoring options that might have an influence + * on the way the particular action will be performed. + * + * \param[out] OutFailureReason An optional pointer to store a message that + * describes why the action wasn't performed. + * + * \returns If successful, a new \c CXRefactoringResult structure containing the + * source replacement candidates, which should eventually be freed with + * \c clang_RefactoringResult_dispose(). If the refactoring operation fails, + * returns NULL. + */ +CINDEX_LINKAGE +CXRefactoringResult clang_Refactoring_performOperation( + CXRefactoringAction Action, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles, CXRefactoringOptionSet Options, + CXString *OutFailureReason); + +// TODO: Remove. This is the deprecated API. +CINDEX_LINKAGE +void clang_RefactoringResult_getReplacements( + CXRefactoringResult Result, CXRefactoringReplacements_Old *OutReplacements); + +/** + * \brief Return the set of refactoring source replacements. + * + * The resulting \c CXRefactoringReplacements are managed by the + * \c CXRefactoringResult and don't have to be disposed of manually. + */ +CINDEX_LINKAGE +CXRefactoringReplacements +clang_RefactoringResult_getSourceReplacements(CXRefactoringResult Result); + +/** + * \brief Represents a set of symbol occurrences that are associated with a + * single refactoring replacement. + * + * The symbol occurrences depend on \c CXRefactoringResult, and can't be + * used after the refactoring result is freed. + */ +typedef struct { + const CXSymbolOccurrence *AssociatedSymbolOccurrences; + unsigned NumAssociatedSymbolOccurrences; +} CXRefactoringReplacementAssociatedSymbolOccurrences; + +/** + * \brief Return the set of symbol occurrences that are associated with the + * given \p Replacement. + */ +CXRefactoringReplacementAssociatedSymbolOccurrences +clang_RefactoringReplacement_getAssociatedSymbolOccurrences( + CXRefactoringReplacement Replacement); + +/** + * \brief Returns the refactoring continuation associated with this result, or + * NULL if this result has no refactoring continuation. + */ +CINDEX_LINKAGE +CXRefactoringContinuation +clang_RefactoringResult_getContinuation(CXRefactoringResult Result); + +/** + * \brief Free the given refactoring result. + */ +CINDEX_LINKAGE +void clang_RefactoringResult_dispose(CXRefactoringResult Result); + +/** + * \brief Load the indexer query results from a YAML string. + * + * Mainly used for testing. + */ +CINDEX_LINKAGE +enum CXErrorCode +clang_RefactoringContinuation_loadSerializedIndexerQueryResults( + CXRefactoringContinuation Continuation, const char *Source); + +/** + * \brief Return the number of indexer queries that a refactoring continuation + * has. + */ +CINDEX_LINKAGE +unsigned clang_RefactoringContinuation_getNumIndexerQueries( + CXRefactoringContinuation Continuation); + +/** + * \brief Return the indexer query at index \p Index. + */ +CINDEX_LINKAGE +CXIndexerQuery clang_RefactoringContinuation_getIndexerQuery( + CXRefactoringContinuation Continuation, unsigned Index); + +/** + * \brief Terminate the connection between the initiation TU and the refactoring + * continuation. + * + * The continuation converts all the TU-specific state to TU-independent state. + * The indexer queries that are associate with this continuation are also + * invalidated. + */ +CINDEX_LINKAGE +void clang_RefactoringContinuation_finalizeEvaluationInInitationTU( + CXRefactoringContinuation Continuation); + +/** + * \brief Continue performing the previously initiated and performed refactoring + * operation in the given translation unit \p TU. + */ +CINDEX_LINKAGE +CXRefactoringResult clang_RefactoringContinuation_continueOperationInTU( + CXRefactoringContinuation Continuation, CXTranslationUnit TU, + CXString *OutFailureReason); + +/** + * \brief Free the given refactoring continuation. + */ +CINDEX_LINKAGE +void clang_RefactoringContinuation_dispose( + CXRefactoringContinuation Continuation); + +/** + * @} + */ + +/** + * \defgroup CINDEX_REFACTOR_INDEXER_QUERY Indexer Queries. + * + * @{ + */ + +/** + * \brief The types of indexer queries. + */ +enum CXIndexerQueryKind { + CXIndexerQuery_Unknown = 0, + + /** + * \brief The indexer should find the file that contains/should contain the + * implementation of some declaration. + * A file result is expected. + */ + CXIndexerQuery_Decl_FileThatShouldImplement = 1, + + /** + * \brief The indexer should determine if the some declaration is defined. + * An integer result is expected. + */ + CXIndexerQuery_Decl_IsDefined = 2, +}; + +/** + * \brief Return the kind of the indexer query \p Query. + */ +CINDEX_LINKAGE +enum CXIndexerQueryKind clang_IndexerQuery_getKind(CXIndexerQuery Query); + +/** + * \brief Return the number of cursors that the \p Query has. + */ +CINDEX_LINKAGE +unsigned clang_IndexerQuery_getNumCursors(CXIndexerQuery Query); + +/** + * \brief Return the cursor at the given \p CursorIndex. + */ +CINDEX_LINKAGE +CXCursor clang_IndexerQuery_getCursor(CXIndexerQuery Query, + unsigned CursorIndex); + +/** + * \brief The action that the indexer should take after evaluating the query. + */ +enum CXIndexerQueryAction { + /** + * \brief This result requires no further action. + */ + CXIndexerQueryAction_None = 0, + + /** + * \brief The indexer should run the \c CXRefactoringContinuaton in a + * translation unit that contains this file. + */ + CXIndexerQueryAction_RunContinuationInTUThatHasThisFile = 1, +}; + +/** + * \brief Consumes an integer/boolean query result. + */ +CINDEX_LINKAGE +enum CXIndexerQueryAction +clang_IndexerQuery_consumeIntResult(CXIndexerQuery Query, unsigned CursorIndex, + int Value); + +/** + * \brief Consumes a filename query result. + * + * This function may return + * \c CXIndexerQueryAction_RunContinuationInTUThatHasThisFile which + * should tell the indexer that it has to run the refactoring continuation in + * the TU that contains this file. + */ +CINDEX_LINKAGE +enum CXIndexerQueryAction +clang_IndexerQuery_consumeFileResult(CXIndexerQuery Query, unsigned CursorIndex, + const char *Filename); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* LLVM_CLANG_C_REFACTOR_H */ diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h index 26c0cbe82d176..d287b910a92d0 100644 --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -858,6 +858,11 @@ class ObjCPropertyDecl : public NamedDecl { return Assign; } + /// Return true if this property has an explicitly specified getter name. + bool hasExplicitGetterName() const { + return (PropertyAttributes & OBJC_PR_getter); + } + Selector getGetterName() const { return GetterName; } SourceLocation getGetterNameLoc() const { return GetterNameLoc; } void setGetterName(Selector Sel, SourceLocation Loc = SourceLocation()) { @@ -865,6 +870,11 @@ class ObjCPropertyDecl : public NamedDecl { GetterNameLoc = Loc; } + /// Return true if this property has an explicitly specified setter name. + bool hasExplicitSetterName() const { + return (PropertyAttributes & OBJC_PR_setter); + } + Selector getSetterName() const { return SetterName; } SourceLocation getSetterNameLoc() const { return SetterNameLoc; } void setSetterName(Selector Sel, SourceLocation Loc = SourceLocation()) { @@ -2617,14 +2627,23 @@ class ObjCCompatibleAliasDecl : public NamedDecl { void anchor() override; /// Class that this is an alias of. ObjCInterfaceDecl *AliasedClass; + /// The location of the name of the referenced class. + SourceLocation AliasedClassLoc; + /// The location of the '@'. + SourceLocation AtLoc; + + ObjCCompatibleAliasDecl(DeclContext *DC, SourceLocation NameLoc, + IdentifierInfo *Id, ObjCInterfaceDecl *AliasedClass, + SourceLocation AliasedClassLoc, SourceLocation AtLoc) + : NamedDecl(ObjCCompatibleAlias, DC, NameLoc, Id), + AliasedClass(AliasedClass), AliasedClassLoc(AliasedClassLoc), + AtLoc(AtLoc) {} - ObjCCompatibleAliasDecl(DeclContext *DC, SourceLocation L, IdentifierInfo *Id, - ObjCInterfaceDecl* aliasedClass) - : NamedDecl(ObjCCompatibleAlias, DC, L, Id), AliasedClass(aliasedClass) {} public: - static ObjCCompatibleAliasDecl *Create(ASTContext &C, DeclContext *DC, - SourceLocation L, IdentifierInfo *Id, - ObjCInterfaceDecl* aliasedClass); + static ObjCCompatibleAliasDecl * + Create(ASTContext &C, DeclContext *DC, SourceLocation NameLoc, + IdentifierInfo *Id, ObjCInterfaceDecl *AliasedClass, + SourceLocation AliasedClassLoc, SourceLocation AtLoc); static ObjCCompatibleAliasDecl *CreateDeserialized(ASTContext &C, unsigned ID); @@ -2633,6 +2652,17 @@ class ObjCCompatibleAliasDecl : public NamedDecl { ObjCInterfaceDecl *getClassInterface() { return AliasedClass; } void setClassInterface(ObjCInterfaceDecl *D) { AliasedClass = D; } + SourceLocation getClassInterfaceLoc() const { return AliasedClassLoc; } + + void setClassInterfaceLoc(SourceLocation Loc) { AliasedClassLoc = Loc; } + + SourceLocation getAtLoc() const { return AtLoc; } + void setAtLoc(SourceLocation Loc) { AtLoc = Loc; } + + SourceRange getSourceRange() const override LLVM_READONLY { + return SourceRange(AtLoc, AtLoc); + } + static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classofKind(Kind K) { return K == ObjCCompatibleAlias; } diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h index 2879452f24046..de5342568114f 100644 --- a/clang/include/clang/AST/DeclTemplate.h +++ b/clang/include/clang/AST/DeclTemplate.h @@ -157,6 +157,9 @@ class TemplateParameterList final return SourceRange(TemplateLoc, RAngleLoc); } + void print(llvm::raw_ostream &Out, const PrintingPolicy &Policy, + unsigned Indentation = 0) const; + friend TrailingObjects; template diff --git a/clang/include/clang/AST/DependentASTVisitor.h b/clang/include/clang/AST/DependentASTVisitor.h new file mode 100644 index 0000000000000..4177344f0ae75 --- /dev/null +++ b/clang/include/clang/AST/DependentASTVisitor.h @@ -0,0 +1,91 @@ +//===--- DependentASTVisitor.h - Helper for dependent nodes -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the DependentASTVisitor RecursiveASTVisitor layer, which +// is responsible for visiting unresolved symbol references. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_DEPENDENT_AST_VISITOR_H +#define LLVM_CLANG_AST_DEPENDENT_AST_VISITOR_H + +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Type.h" + +namespace clang { + +// TODO: Use in the indexer. +template +class DependentASTVisitor : public RecursiveASTVisitor { +private: + bool visitDependentReference( + const Type *T, const DeclarationName &Name, SourceLocation Loc, + llvm::function_ref Filter) { + if (!T) + return true; + const TemplateSpecializationType *TST = + T->getAs(); + if (!TST) + return true; + TemplateName TN = TST->getTemplateName(); + const ClassTemplateDecl *TD = + dyn_cast_or_null(TN.getAsTemplateDecl()); + if (!TD) + return true; + CXXRecordDecl *RD = TD->getTemplatedDecl(); + if (!RD->hasDefinition()) + return true; + RD = RD->getDefinition(); + std::vector Symbols = + RD->lookupDependentName(Name, Filter); + // FIXME: Improve overload handling. + if (Symbols.size() != 1) + return true; + if (Loc.isInvalid()) + return true; + return RecursiveASTVisitor::getDerived() + .VisitDependentSymbolReference(Symbols[0], Loc); + } + +public: + bool VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *E) { + const DeclarationNameInfo &Info = E->getMemberNameInfo(); + return visitDependentReference( + E->getBaseType().getTypePtrOrNull(), Info.getName(), Info.getLoc(), + [](const NamedDecl *D) { return D->isCXXInstanceMember(); }); + } + + bool VisitDependentScopeDeclRefExpr(DependentScopeDeclRefExpr *E) { + const DeclarationNameInfo &Info = E->getNameInfo(); + const NestedNameSpecifier *NNS = E->getQualifier(); + return visitDependentReference( + NNS->getAsType(), Info.getName(), Info.getLoc(), + [](const NamedDecl *D) { return !D->isCXXInstanceMember(); }); + } + + bool VisitDependentNameTypeLoc(DependentNameTypeLoc TL) { + const DependentNameType *DNT = TL.getTypePtr(); + const NestedNameSpecifier *NNS = DNT->getQualifier(); + DeclarationName Name(DNT->getIdentifier()); + return visitDependentReference( + NNS->getAsType(), Name, TL.getNameLoc(), + [](const NamedDecl *ND) { return isa(ND); }); + } + + bool VisitDependentSymbolReference(const NamedDecl *Symbol, + SourceLocation SymbolNameLoc) { + return true; + } +}; + +} // end namespace clang + +#endif // LLVM_CLANG_AST_DEPENDENT_AST_VISITOR_H diff --git a/clang/include/clang/AST/PrettyPrinter.h b/clang/include/clang/AST/PrettyPrinter.h index 274df220e160e..00c40c866f7fa 100644 --- a/clang/include/clang/AST/PrettyPrinter.h +++ b/clang/include/clang/AST/PrettyPrinter.h @@ -39,7 +39,7 @@ struct PrintingPolicy { /// \brief Create a default printing policy for the specified language. PrintingPolicy(const LangOptions &LO) : Indentation(2), SuppressSpecifiers(false), - SuppressTagKeyword(LO.CPlusPlus), + SupressStorageClassSpecifiers(false), SuppressTagKeyword(LO.CPlusPlus), IncludeTagDefinition(false), SuppressScope(false), SuppressUnwrittenScope(false), SuppressInitializers(false), ConstantArraySizeAsWritten(false), AnonymousTagLocations(true), @@ -50,7 +50,8 @@ struct PrintingPolicy { UseVoidForZeroParams(!LO.CPlusPlus), TerseOutput(false), PolishForDeclaration(false), Half(LO.Half), MSWChar(LO.MicrosoftExt && !LO.WChar), - IncludeNewlines(true), MSVCFormatting(false) { } + IncludeNewlines(true), MSVCFormatting(false), + UseStdFunctionForLambda(false) { } /// \brief Adjust this printing policy for cases where it's known that /// we're printing C++ code (for instance, if AST dumping reaches a @@ -81,6 +82,10 @@ struct PrintingPolicy { /// "const int" type specifier and instead only print the "*y". bool SuppressSpecifiers : 1; + /// \brief Whether we should supress the printing of the actual storage class + /// specifiers for the given declaration. + bool SupressStorageClassSpecifiers : 1; + /// \brief Whether type printing should skip printing the tag keyword. /// /// This is used when printing the inner type of elaborated types, @@ -200,6 +205,9 @@ struct PrintingPolicy { /// prints anonymous namespaces as `anonymous namespace' and does not insert /// spaces after template arguments. bool MSVCFormatting : 1; + + /// \brief Whether we should use std::function<...> for lambda record types. + bool UseStdFunctionForLambda : 1; }; } // end namespace clang diff --git a/clang/include/clang/Basic/AllDiagnostics.h b/clang/include/clang/Basic/AllDiagnostics.h index fc861a1952a51..4af321fd23b7c 100644 --- a/clang/include/clang/Basic/AllDiagnostics.h +++ b/clang/include/clang/Basic/AllDiagnostics.h @@ -24,6 +24,7 @@ #include "clang/Parse/ParseDiagnostic.h" #include "clang/Sema/SemaDiagnostic.h" #include "clang/Serialization/SerializationDiagnostic.h" +#include "clang/Tooling/Core/RefactoringDiagnostic.h" namespace clang { template diff --git a/clang/include/clang/Basic/CMakeLists.txt b/clang/include/clang/Basic/CMakeLists.txt index 3e0fb8728c483..ced4d63aa1eff 100644 --- a/clang/include/clang/Basic/CMakeLists.txt +++ b/clang/include/clang/Basic/CMakeLists.txt @@ -15,6 +15,7 @@ clang_diag_gen(Lex) clang_diag_gen(Parse) clang_diag_gen(Sema) clang_diag_gen(Serialization) +clang_diag_gen(Refactoring) clang_tablegen(DiagnosticGroups.inc -gen-clang-diag-groups SOURCE Diagnostic.td TARGET ClangDiagnosticGroups) diff --git a/clang/include/clang/Basic/Diagnostic.td b/clang/include/clang/Basic/Diagnostic.td index f25068eca1322..cb87d87837522 100644 --- a/clang/include/clang/Basic/Diagnostic.td +++ b/clang/include/clang/Basic/Diagnostic.td @@ -139,4 +139,4 @@ include "DiagnosticLexKinds.td" include "DiagnosticParseKinds.td" include "DiagnosticSemaKinds.td" include "DiagnosticSerializationKinds.td" - +include "DiagnosticRefactoringKinds.td" diff --git a/clang/include/clang/Basic/DiagnosticIDs.h.rej b/clang/include/clang/Basic/DiagnosticIDs.h.rej new file mode 100644 index 0000000000000..f81c0605abf59 --- /dev/null +++ b/clang/include/clang/Basic/DiagnosticIDs.h.rej @@ -0,0 +1,18 @@ +*************** +*** 37,43 **** + DIAG_START_COMMENT = DIAG_START_AST + 110, + DIAG_START_SEMA = DIAG_START_COMMENT + 100, + DIAG_START_ANALYSIS = DIAG_START_SEMA + 3500, +- DIAG_UPPER_LIMIT = DIAG_START_ANALYSIS + 100 + }; + + class CustomDiagInfo; +--- 37,44 ---- + DIAG_START_COMMENT = DIAG_START_AST + 110, + DIAG_START_SEMA = DIAG_START_COMMENT + 100, + DIAG_START_ANALYSIS = DIAG_START_SEMA + 3500, ++ DIAG_START_REFACTORING = DIAG_START_ANALYSIS + 100, ++ DIAG_UPPER_LIMIT = DIAG_START_REFACTORING + 100 + }; + + class CustomDiagInfo; diff --git a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td new file mode 100644 index 0000000000000..6dfd29169923a --- /dev/null +++ b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td @@ -0,0 +1,27 @@ +//==--- DiagnosticRefactoringKinds.td - refactoring diagnostics -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// Refactoring Diagnostics +//===----------------------------------------------------------------------===// + +let Component = "Refactoring" in { + +let CategoryName = "Rename Issue" in { +def err_rename_builtin_function : Error<"%0 is a builtin function that " + "cannot be renamed">; +def err_rename_sys_header : Error<"%0 cannot be renamed because it is " + "declared in a system header">; +def err_method_rename_override_sys_framework : Error<"method %0 cannot be " + "renamed because it overrides a method declared in a system framework">; +def err_rename_external_source_symbol : Error<"%0 is declared in a %1 file; " + "rename can be initiated in a %1 file only">; +} + +} // end of Refactoring diagnostics diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5b2a8e26baf8d..415288038492f 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1175,6 +1175,12 @@ def warn_unimplemented_selector: Warning< InGroup, DefaultIgnore; def warn_unimplemented_protocol_method : Warning< "method %0 in protocol %1 not implemented">, InGroup; +def warn_class_does_not_conform_protocol : Warning< + "%select{class|category}0 %1 does not conform to protocol" + "%plural{1: %3|2:s %3 and %4|3:s %3, %4 and %5|:s %3, %4, %5, ...}2">, + InGroup; +def note_add_missing_protocol_stubs : Note< + "add stubs for missing protocol requirements">; def warn_multiple_selectors: Warning< "several methods with selector %0 of mismatched types are found " "for the @selector expression">, @@ -7954,6 +7960,7 @@ def warn_missing_case : Warning<"%plural{" "3:enumeration values %1, %2, and %3 not handled in switch|" ":%0 enumeration values not handled in switch: %1, %2, %3...}0">, InGroup; +def note_fill_in_missing_cases : Note<"add missing switch cases">; def warn_unannotated_fallthrough : Warning< "unannotated fall-through between switch labels">, diff --git a/clang/include/clang/Edit/RefactoringFixits.h b/clang/include/clang/Edit/RefactoringFixits.h new file mode 100644 index 0000000000000..bf8adb551e28e --- /dev/null +++ b/clang/include/clang/Edit/RefactoringFixits.h @@ -0,0 +1,66 @@ +//===--- RefactoringFixits.h - Fixit producers for refactorings -*- 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_EDIT_REFACTORING_FIXITS_H +#define LLVM_CLANG_EDIT_REFACTORING_FIXITS_H + +#include "clang/Basic/Diagnostic.h" +#include "llvm/ADT/STLExtras.h" + +namespace clang { + +class ASTContext; +class SwitchStmt; +class EnumDecl; +class ObjCContainerDecl; + +namespace edit { + +/** + * Generates the fix-its that perform the "add missing switch cases" refactoring + * operation. + */ +void fillInMissingSwitchEnumCases( + ASTContext &Context, const SwitchStmt *Switch, const EnumDecl *Enum, + const DeclContext *SwitchContext, + llvm::function_ref Consumer); + +/// Responsible for the fix-its that perform the +/// "add missing protocol requirements" refactoring operation. +namespace fillInMissingProtocolStubs { + +class FillInMissingProtocolStubsImpl; +class FillInMissingProtocolStubs { + std::unique_ptr Impl; + +public: + FillInMissingProtocolStubs(); + ~FillInMissingProtocolStubs(); + FillInMissingProtocolStubs(FillInMissingProtocolStubs &&); + FillInMissingProtocolStubs &operator=(FillInMissingProtocolStubs &&); + + /// Initiate the FillInMissingProtocolStubs edit. + /// + /// \returns true on Error. + bool initiate(ASTContext &Context, const ObjCContainerDecl *Container); + bool hasMissingRequiredMethodStubs(); + void perform(ASTContext &Context, + llvm::function_ref Consumer); +}; + +void addMissingProtocolStubs( + ASTContext &Context, const ObjCContainerDecl *Container, + llvm::function_ref Consumer); + +} // end namespace fillInMissingProtocolStubs + +} // end namespace edit +} // end namespace clang + +#endif // LLVM_CLANG_EDIT_REFACTORING_FIXITS_H diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h index 02b3855320578..2a229f6c77622 100644 --- a/clang/include/clang/Lex/Lexer.h +++ b/clang/include/clang/Lex/Lexer.h @@ -462,6 +462,14 @@ class Lexer : public PreprocessorLexer { const LangOptions &LangOpts, bool SkipTrailingWhitespaceAndNewLine); + /// \brief Returns the source location of the token that comes after the + /// token located at the given location \p Loc (excluding any comments and + /// whitespace). The returned source location will be invalid if the location + /// is inside a macro. + static SourceLocation + findNextTokenLocationAfterTokenAt(SourceLocation Loc, const SourceManager &SM, + const LangOptions &LangOpts); + /// \brief Returns true if the given character could appear in an identifier. static bool isIdentifierBodyChar(char c, const LangOptions &LangOpts); diff --git a/clang/include/clang/Tooling/Core/RefactoringDiagnostic.h b/clang/include/clang/Tooling/Core/RefactoringDiagnostic.h new file mode 100644 index 0000000000000..a17926097df0f --- /dev/null +++ b/clang/include/clang/Tooling/Core/RefactoringDiagnostic.h @@ -0,0 +1,29 @@ +//===--- RefactoringDiagnostic.h - ------------------------------*- 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_TOOLING_CORE_REFACTORINGSTARTDIAGNOSTIC_H +#define LLVM_CLANG_TOOLING_CORE_REFACTORINGSTARTDIAGNOSTIC_H + +#include "clang/Basic/Diagnostic.h" + +namespace clang { +namespace diag { +enum { +#define DIAG(ENUM, FLAGS, DEFAULT_MAPPING, DESC, GROUP, SFINAE, NOWERROR, \ + SHOWINSYSHEADER, CATEGORY) \ + ENUM, +#define REFACTORINGSTART +#include "clang/Basic/DiagnosticRefactoringKinds.inc" +#undef DIAG + NUM_BUILTIN_REFACTORING_DIAGNOSTICS +}; +} // end namespace diag +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_CORE_REFACTORINGSTARTDIAGNOSTIC_H diff --git a/clang/include/clang/Tooling/Refactor/IndexerQuery.h b/clang/include/clang/Tooling/Refactor/IndexerQuery.h new file mode 100644 index 0000000000000..a3b3caa723c44 --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/IndexerQuery.h @@ -0,0 +1,274 @@ +//===--- IndexerQuery.h - A set of indexer query interfaces ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the base indexer queries that can be used with +// refactoring continuations. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_INDEXER_QUERY_H +#define LLVM_CLANG_TOOLING_REFACTOR_INDEXER_QUERY_H + +#include "clang/Tooling/Refactor/RefactoringOperationState.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Error.h" +#include + +namespace clang { +namespace tooling { +namespace indexer { + +/// Represents an abstract indexer query. +class IndexerQuery { +public: + const char *BaseUID; + const char *NameUID; + + IndexerQuery(const char *BaseUID, const char *NameUID) + : BaseUID(BaseUID), NameUID(NameUID) {} + virtual ~IndexerQuery() {} + + virtual void invalidateTUSpecificState() = 0; + + // Mainly used for testing. + static llvm::Error loadResultsFromYAML(StringRef Source, + ArrayRef Queries); + + static bool classof(const IndexerQuery *) { return true; } +}; + +/// An abstract AST query that can produce an AST unit in which the refactoring +/// continuation will run. +class ASTProducerQuery : public IndexerQuery { + static const char *BaseUIDString; + +public: + /// Deriving AST producer queries can redefine this type to generate custom + /// results that are then passed into the refactoring continuations. + using ResultTy = void; + + ASTProducerQuery(const char *NameUID) + : IndexerQuery(BaseUIDString, NameUID) {} + + static bool classof(const IndexerQuery *Q) { + return Q->BaseUID == BaseUIDString; + } +}; + +/// A query that finds a file that contains/should contain the implementation of +/// some declaration. +class ASTUnitForImplementationOfDeclarationQuery final + : public ASTProducerQuery { + static const char *NameUIDString; + + const Decl *D; + PersistentFileID Result; + +public: + ASTUnitForImplementationOfDeclarationQuery(const Decl *D) + : ASTProducerQuery(NameUIDString), D(D), Result("") {} + + using ResultTy = FileID; + + const Decl *getDecl() const { return D; } + + void invalidateTUSpecificState() override { D = nullptr; } + + void setResult(PersistentFileID File) { Result = std::move(File); } + + const PersistentFileID &getResult() const { return Result; } + + static bool classof(const IndexerQuery *D) { + return D->NameUID == NameUIDString; + } +}; + +/// Returns an indexer query that will allow a refactoring continuation to run +/// in an AST unit that contains a file that should contain the implementation +/// of the given declaration \p D. +/// +/// The continuation function will receive \c FileID that corresponds to the +/// implementation file. The indexer can decide which file should be used as an +/// implementation of a declaration based on a number of different heuristics. +/// It does not guarantee that the file will actually have any declarations that +/// correspond to the implementation of \p D yet, as the indexer may decide to +/// point to a file that it thinks will have the implementation declarations in +/// the future. +std::unique_ptr +fileThatShouldContainImplementationOf(const Decl *D); + +/// A declaration predicate operates. +struct DeclPredicate { + const char *Name; + + DeclPredicate(const char *Name) : Name(Name) {} + + bool operator==(const DeclPredicate &P) const { + return StringRef(Name) == P.Name; + } + bool operator!=(const DeclPredicate &P) const { + return StringRef(Name) != P.Name; + } +}; + +/// Represents a declaration predicate that will evaluate to either 'true' or +/// 'false' in an indexer query. +struct BoolDeclPredicate { + DeclPredicate Predicate; + bool IsInverted; + + BoolDeclPredicate(DeclPredicate Predicate, bool IsInverted = false) + : Predicate(Predicate), IsInverted(IsInverted) {} + + BoolDeclPredicate operator!() const { + return BoolDeclPredicate(Predicate, /*IsInverted=*/!IsInverted); + } +}; + +namespace detail { + +/// AST-like representation for decl predicates. +class DeclPredicateNode { +public: + const char *NameUID; + DeclPredicateNode(const char *NameUID) : NameUID(NameUID) {} + + static std::unique_ptr + create(const DeclPredicate &Predicate); + static std::unique_ptr + create(const BoolDeclPredicate &Predicate); + + static bool classof(const DeclPredicateNode *) { return true; } +}; + +class DeclPredicateNodePredicate : public DeclPredicateNode { + static const char *NameUIDString; + + DeclPredicate Predicate; + +public: + DeclPredicateNodePredicate(const DeclPredicate &Predicate) + : DeclPredicateNode(NameUIDString), Predicate(Predicate) {} + + const DeclPredicate &getPredicate() const { return Predicate; } + + static bool classof(const DeclPredicateNode *P) { + return P->NameUID == NameUIDString; + } +}; + +class DeclPredicateNotPredicate : public DeclPredicateNode { + static const char *NameUIDString; + + std::unique_ptr Child; + +public: + DeclPredicateNotPredicate(std::unique_ptr Child) + : DeclPredicateNode(NameUIDString), Child(std::move(Child)) {} + + const DeclPredicateNode &getChild() const { return *Child; } + + static bool classof(const DeclPredicateNode *P) { + return P->NameUID == NameUIDString; + } +}; + +} // end namespace detail + +/// Transforms one set of declarations into another using some predicate. +class DeclarationsQuery : public IndexerQuery { + static const char *BaseUIDString; + + std::vector Input; + std::unique_ptr Predicate; + +protected: + std::vector> Output; + +public: + DeclarationsQuery(std::vector Input, + std::unique_ptr Predicate) + : IndexerQuery(BaseUIDString, nullptr), Input(std::move(Input)), + Predicate(std::move(Predicate)) { + assert(!this->Input.empty() && "empty declarations list!"); + } + + ArrayRef getInputs() const { return Input; } + + void invalidateTUSpecificState() override { Input.clear(); } + + void setOutput(std::vector> Output) { + this->Output = Output; + } + + const detail::DeclPredicateNode &getPredicateNode() const { + return *Predicate; + } + + static bool classof(const IndexerQuery *Q) { + return Q->BaseUID == BaseUIDString; + } +}; + +/// The \c DeclEntity class acts as a proxy for the entity that represents a +/// declaration in the indexer. It defines a set of declaration predicates that +/// can be used in indexer queries. +struct DeclEntity { + /// The indexer will evaluate this predicate to 'true' when a certain + /// declaration has a corresponding definition. + BoolDeclPredicate isDefined() const { + return BoolDeclPredicate("decl.isDefined"); + } +}; + +template +class ManyToManyDeclarationsQuery final + : public std::enable_if::value, + DeclarationsQuery>::type { +public: + ManyToManyDeclarationsQuery( + ArrayRef Input, + std::unique_ptr Predicate) + : DeclarationsQuery(std::vector(Input.begin(), Input.end()), + std::move(Predicate)) {} + + std::vector> getOutput() const { + std::vector> Results; + for (const auto &Ref : DeclarationsQuery::Output) + Results.push_back(PersistentDeclRef(Ref.USR)); + return Results; + } +}; + +/// Returns an indexer query that will pass a filtered list of declarations to +/// a refactoring continuation. +/// +/// The filtering is done based on predicates that are available on the \c +/// DeclEntity types. For example, you can use the following invocation to +/// find a set of declarations that are defined in the entire project: +/// +/// \code +/// filter({ MyDeclA, MyDeclB }, [] (const DeclEntity &D) { return D.isDefined() +/// }) +/// \endcode +template +std::unique_ptr> +filter(ArrayRef Declarations, + BoolDeclPredicate (*Fn)(const DeclEntity &), + typename std::enable_if::value>::type * = + nullptr) { + return llvm::make_unique>( + Declarations, detail::DeclPredicateNode::create(Fn(DeclEntity()))); +} + +} // end namespace indexer +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_INDEXER_QUERY_H diff --git a/clang/include/clang/Tooling/Refactor/RefactoringActionFinder.h b/clang/include/clang/Tooling/Refactor/RefactoringActionFinder.h new file mode 100644 index 0000000000000..395d78c0f9ac4 --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RefactoringActionFinder.h @@ -0,0 +1,60 @@ +//===--- RefactoringActionFinder.h - Clang refactoring library ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides methods to find the refactoring actions that can be +/// performed at specific locations / source ranges in a translation unit. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_FINDER_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_FINDER_H + +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Refactor/RefactoringActions.h" +#include "llvm/ADT/StringSet.h" +#include + +namespace clang { + +class NamedDecl; +class ASTContext; + +namespace tooling { + +/// Contains a set of a refactoring actions. +struct RefactoringActionSet { + /// A set of refactoring actions that can be performed at some specific + /// location in a source file. + /// + /// The actions in the action set are ordered by their priority: most + /// important actions are placed before the less important ones. + std::vector Actions; + + RefactoringActionSet() {} + + RefactoringActionSet(RefactoringActionSet &&) = default; + RefactoringActionSet &operator=(RefactoringActionSet &&) = default; +}; + +/// \brief Returns a \c RefactoringActionSet that contains the set of actions +/// that can be performed at the given location. +RefactoringActionSet findActionSetAt(SourceLocation Loc, + SourceRange SelectionRange, + ASTContext &Context); + +/// \brief Returns a set of USRs that correspond to the given declaration. +llvm::StringSet<> findSymbolsUSRSet(const NamedDecl *FoundDecl, + ASTContext &Context); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_FINDER_H diff --git a/clang/include/clang/Tooling/Refactor/RefactoringActions.def b/clang/include/clang/Tooling/Refactor/RefactoringActions.def new file mode 100644 index 0000000000000..9b7e7e04d037f --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RefactoringActions.def @@ -0,0 +1,60 @@ +//===--- RefactoringActions.def - The list of refactoring actions --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef REFACTORING_ACTION +#define REFACTORING_ACTION(Name, Spelling) +#endif + +#ifndef REFACTORING_SUB_ACTION +#define REFACTORING_SUB_ACTION(Name, Parent, Spelling) \ + REFACTORING_ACTION(Parent##_##Name, Spelling) +#endif + +#ifndef REFACTORING_OPERATION_ACTION +#define REFACTORING_OPERATION_ACTION(Name, Spelling, Command)\ + REFACTORING_ACTION(Name, Spelling) +#endif + +#ifndef REFACTORING_OPERATION_SUB_ACTION +#define REFACTORING_OPERATION_SUB_ACTION(Name, Parent, Spelling, Command)\ + REFACTORING_SUB_ACTION(Name, Parent, Spelling) +#endif + +REFACTORING_ACTION(Rename, "Rename") +REFACTORING_SUB_ACTION(Local, Rename, "Rename") + +REFACTORING_OPERATION_ACTION(Extract, "Extract Function", "extract") +REFACTORING_OPERATION_SUB_ACTION(Method, Extract, "Extract Method", + "extract-method") + +REFACTORING_OPERATION_ACTION(IfSwitchConversion, "Convert to Switch", + "if-switch-conversion") +REFACTORING_OPERATION_ACTION(FillInEnumSwitchCases, "Add Missing Switch Cases", + "fill-in-enum-switch-cases") +REFACTORING_OPERATION_ACTION(FillInMissingProtocolStubs, + "Add Missing Protocol Requirements", + "fill-in-missing-protocol-stubs") +REFACTORING_OPERATION_ACTION(LocalizeObjCStringLiteral, + "Wrap in NSLocalizedString", + "localize-objc-string-literal") +REFACTORING_OPERATION_ACTION(ExtractRepeatedExpressionIntoVariable, + "Extract Repeated Expression", + "extract-repeated-expr-into-var") +REFACTORING_OPERATION_ACTION(FillInMissingMethodStubsFromAbstractClasses, + "Add Missing Abstract Class Overrides", + "fill-in-missing-abstract-methods") + // FIXME: For ObjC this should say 'Methods': +REFACTORING_OPERATION_ACTION(ImplementDeclaredMethods, + "Generate Missing Function Definitions", + "implement-declared-methods") + +#undef REFACTORING_OPERATION_SUB_ACTION +#undef REFACTORING_OPERATION_ACTION +#undef REFACTORING_SUB_ACTION +#undef REFACTORING_ACTION diff --git a/clang/include/clang/Tooling/Refactor/RefactoringActions.h b/clang/include/clang/Tooling/Refactor/RefactoringActions.h new file mode 100644 index 0000000000000..f9d9c6c888ab5 --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RefactoringActions.h @@ -0,0 +1,34 @@ +//===--- RefactoringActions.h - Clang refactoring library -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Contains a list of all the supported refactoring actions. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTIONS_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTIONS_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace tooling { + +enum class RefactoringActionType { +#define REFACTORING_ACTION(Name, Spelling) Name, +#include "clang/Tooling/Refactor/RefactoringActions.def" +}; + +StringRef getRefactoringActionTypeName(RefactoringActionType Action); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTIONS_H diff --git a/clang/include/clang/Tooling/Refactor/RefactoringOperation.h b/clang/include/clang/Tooling/Refactor/RefactoringOperation.h new file mode 100644 index 0000000000000..3332178011912 --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RefactoringOperation.h @@ -0,0 +1,160 @@ +//===--- RefactoringOperations.h - Defines a refactoring operation --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPERATION_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPERATION_H + +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Refactor/RefactoringActions.h" +#include "clang/Tooling/Refactor/RefactoringOptionSet.h" +#include "clang/Tooling/Refactor/RefactoringReplacement.h" +#include "clang/Tooling/Refactor/SymbolOperation.h" +#include "llvm/ADT/None.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace clang { + +class ASTContext; +class Decl; +class Preprocessor; +class Stmt; + +namespace tooling { + +class RefactoringContinuation; + +/// A refactoring result contains the source replacements produced by the +/// refactoring operation and the optional refactoring continuation. +struct RefactoringResult { + std::vector Replacements; + std::vector> + AssociatedSymbols; + std::unique_ptr Continuation; + + RefactoringResult( + std::vector Replacements, + std::unique_ptr Continuation = nullptr) + : Replacements(std::move(Replacements)), + Continuation(std::move(Continuation)) {} + + RefactoringResult(std::unique_ptr Continuation) + : Replacements(), Continuation(std::move(Continuation)) {} + + RefactoringResult(RefactoringResult &&) = default; + RefactoringResult &operator=(RefactoringResult &&) = default; +}; + +namespace indexer { + +class IndexerQuery; +class ASTProducerQuery; + +} // end namespace indexer + +/// Refactoring continuations allow refactoring operations to run in external +/// AST units with some results that were obtained after querying the indexer. +/// +/// The state of the refactoring operation is automatically managed by the +/// refactoring engine: +/// - Declaration references are converted to declaration references in +/// an external translation unit. +class RefactoringContinuation { +public: + virtual ~RefactoringContinuation() {} + + virtual indexer::ASTProducerQuery *getASTUnitIndexerQuery() = 0; + + virtual std::vector + getAdditionalIndexerQueries() = 0; + + /// Converts the TU-specific state in the continuation to a TU-independent + /// state. + /// + /// This function is called before the initiation AST unit is freed. + virtual void persistTUSpecificState() = 0; + + /// Invokes the continuation with the indexer query results and the state + /// values in the context of another AST unit. + virtual llvm::Expected + runInExternalASTUnit(ASTContext &Context) = 0; +}; + +// TODO: Remove in favour of diagnostics. +class RefactoringOperationError + : public llvm::ErrorInfo { +public: + static char ID; + StringRef FailureReason; + + RefactoringOperationError(StringRef FailureReason) + : FailureReason(FailureReason) {} + + void log(raw_ostream &OS) const override; + + std::error_code convertToErrorCode() const override; +}; + +/// Represents an abstract refactoring operation. +class RefactoringOperation { +public: + virtual ~RefactoringOperation() {} + + virtual const Stmt *getTransformedStmt() const { return nullptr; } + + virtual const Stmt *getLastTransformedStmt() const { return nullptr; } + + virtual const Decl *getTransformedDecl() const { return nullptr; } + + virtual const Decl *getLastTransformedDecl() const { return nullptr; } + + virtual std::vector getRefactoringCandidates() { return {}; } + + virtual std::vector getAvailableSubActions() { + return {}; + } + + virtual llvm::Expected + perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex = 0) = 0; +}; + +/// A wrapper around a unique pointer to a \c RefactoringOperation or \c +/// SymbolOperation that determines if the operation was successfully initiated +/// or not, even if the operation itself wasn't created. +struct RefactoringOperationResult { + std::unique_ptr RefactoringOp; + std::unique_ptr SymbolOp; + bool Initiated; + StringRef FailureReason; + + RefactoringOperationResult() : Initiated(false) {} + RefactoringOperationResult(llvm::NoneType) : Initiated(false) {} + explicit RefactoringOperationResult(StringRef FailureReason) + : Initiated(false), FailureReason(FailureReason) {} +}; + +/// Initiate a specific refactoring operation. +RefactoringOperationResult initiateRefactoringOperationAt( + SourceLocation Location, SourceRange SelectionRange, ASTContext &Context, + RefactoringActionType ActionType, bool CreateOperation = true); + +/// Initiate a specific refactoring operation on a declaration that corresponds +/// to the given \p DeclUSR. +RefactoringOperationResult +initiateRefactoringOperationOnDecl(StringRef DeclUSR, ASTContext &Context, + RefactoringActionType ActionType); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPERATION_H diff --git a/clang/include/clang/Tooling/Refactor/RefactoringOperationState.h b/clang/include/clang/Tooling/Refactor/RefactoringOperationState.h new file mode 100644 index 0000000000000..76ee7d4392cd1 --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RefactoringOperationState.h @@ -0,0 +1,66 @@ +//===--- RefactoringOperationState.h - Serializable operation state -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the refactoring operation state types that represent the +// TU-independent state that is used for refactoring continuations. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPERATION_STATE_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPERATION_STATE_H + +#include "clang/AST/Decl.h" +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactor/USRFinder.h" +#include +#include + +namespace clang { +namespace tooling { + +namespace detail { + +struct PersistentDeclRefBase {}; + +} // end namespace detail + +/// Declaration references are persisted across translation units by using +/// USRs. +template +struct PersistentDeclRef : std::enable_if::value, + detail::PersistentDeclRefBase>::type { + std::string USR; + // FIXME: We can improve the efficiency of conversion to Decl * by storing the + // decl kind. + + PersistentDeclRef(std::string USR) : USR(std::move(USR)) {} + PersistentDeclRef(PersistentDeclRef &&Other) = default; + PersistentDeclRef &operator=(PersistentDeclRef &&Other) = default; + PersistentDeclRef(const PersistentDeclRef &Other) = default; + PersistentDeclRef &operator=(const PersistentDeclRef &Other) = default; + + static PersistentDeclRef create(const Decl *D) { + // FIXME: Move the getUSRForDecl method somewhere else. + return PersistentDeclRef(rename::getUSRForDecl(D)); + } +}; + +/// FileIDs are persisted across translation units by using filenames. +struct PersistentFileID { + std::string Filename; + + PersistentFileID(std::string Filename) : Filename(std::move(Filename)) {} + PersistentFileID(PersistentFileID &&Other) = default; + PersistentFileID &operator=(PersistentFileID &&Other) = default; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPERATION_STATE_H diff --git a/clang/include/clang/Tooling/Refactor/RefactoringOptionSet.h b/clang/include/clang/Tooling/Refactor/RefactoringOptionSet.h new file mode 100644 index 0000000000000..c3f05ac0f436d --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RefactoringOptionSet.h @@ -0,0 +1,80 @@ +//===--- RefactoringOptionSet.h - A container for the refactoring options -===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTION_SET_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTION_SET_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Error.h" + +namespace llvm { +namespace yaml { +class IO; +} // end namespace yaml +} // end namespace llvm + +namespace clang { +namespace tooling { + +struct RefactoringOption { + virtual ~RefactoringOption() = default; + + struct SerializationContext { + llvm::yaml::IO &IO; + + SerializationContext(llvm::yaml::IO &IO) : IO(IO) {} + }; + + virtual void serialize(const SerializationContext &Context); +}; + +/// \brief A set of refactoring options that can be given to a refactoring +/// operation. +class RefactoringOptionSet final { + llvm::StringMap> Options; + +public: + RefactoringOptionSet() {} + template RefactoringOptionSet(const T &Option) { add(Option); } + + RefactoringOptionSet(RefactoringOptionSet &&) = default; + RefactoringOptionSet &operator=(RefactoringOptionSet &&) = default; + + RefactoringOptionSet(const RefactoringOptionSet &) = delete; + RefactoringOptionSet &operator=(const RefactoringOptionSet &) = delete; + + template void add(const T &Option) { + auto It = Options.try_emplace(StringRef(T::Name), nullptr); + if (It.second) + It.first->getValue().reset(new T(Option)); + } + + template const T *get() const { + auto It = Options.find(StringRef(T::Name)); + if (It == Options.end()) + return nullptr; + return static_cast(It->getValue().get()); + } + + template const T &get(const T &Default) const { + const auto *Ptr = get(); + return Ptr ? *Ptr : Default; + } + + void print(llvm::raw_ostream &OS) const; + + static llvm::Expected parse(StringRef Source); +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTION_SET_H diff --git a/clang/include/clang/Tooling/Refactor/RefactoringOptions.h b/clang/include/clang/Tooling/Refactor/RefactoringOptions.h new file mode 100644 index 0000000000000..f0ce4ba007d0b --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RefactoringOptions.h @@ -0,0 +1,59 @@ +//===--- RefactoringOptions.h - A set of all the refactoring options ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a set of all possible refactoring options that can be +// given to the refactoring operations. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTIONS_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTIONS_H + +#include "clang/AST/DeclBase.h" +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactor/RefactoringOptionSet.h" + +namespace clang { +namespace tooling { +namespace option { + +namespace detail { + +struct BoolOptionBase : RefactoringOption { +protected: + bool Value = false; + void serializeImpl(const SerializationContext &Context, const char *Name); + +public: + operator bool() const { return Value; } +}; + +template struct BoolOption : BoolOptionBase { + void serialize(const SerializationContext &Context) override { + serializeImpl(Context, Option::Name); + } + + static Option getTrue() { + Option Result; + Result.Value = true; + return Result; + } +}; + +} // end namespace detail + +struct AvoidTextualMatches final : detail::BoolOption { + static constexpr const char *Name = "rename.avoid.textual.matches"; +}; + +} // end namespace option +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTIONS_H diff --git a/clang/include/clang/Tooling/Refactor/RefactoringReplacement.h b/clang/include/clang/Tooling/Refactor/RefactoringReplacement.h new file mode 100644 index 0000000000000..4aed8ab37e76a --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RefactoringReplacement.h @@ -0,0 +1,85 @@ +//===--- RefactoringReplacement.h - ------------------------*- 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_TOOLING_REFACTOR_REFACTORING_REPLACEMENT_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_REPLACEMENT_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Refactor/SymbolName.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" +#include + +namespace clang { +namespace tooling { + +/// \brief Represent a symbol that can be used for an additional refactoring +/// action that associated. +class RefactoringResultAssociatedSymbol { + SymbolName Name; + +public: + RefactoringResultAssociatedSymbol(SymbolName Name) : Name(std::move(Name)) {} + + const SymbolName &getName() const { return Name; } +}; + +/// \brief A replacement range. +class RefactoringReplacement { +public: + SourceRange Range; + std::string ReplacementString; + + /// \brief Represents a symbol that is contained in the replacement string + /// of this replacement. + struct AssociatedSymbolLocation { + /// These offsets point into the ReplacementString. + llvm::SmallVector Offsets; + bool IsDeclaration; + + AssociatedSymbolLocation(ArrayRef Offsets, + bool IsDeclaration = false) + : Offsets(Offsets.begin(), Offsets.end()), + IsDeclaration(IsDeclaration) {} + }; + llvm::SmallDenseMap + SymbolLocations; + + RefactoringReplacement(SourceRange Range) : Range(Range) {} + + RefactoringReplacement(SourceRange Range, StringRef ReplacementString) + : Range(Range), ReplacementString(ReplacementString.str()) {} + RefactoringReplacement(SourceRange Range, std::string ReplacementString) + : Range(Range), ReplacementString(std::move(ReplacementString)) {} + + RefactoringReplacement(SourceRange Range, StringRef ReplacementString, + const RefactoringResultAssociatedSymbol *Symbol, + const AssociatedSymbolLocation &Loc) + : Range(Range), ReplacementString(ReplacementString.str()) { + SymbolLocations.insert(std::make_pair(Symbol, Loc)); + } + + RefactoringReplacement(const FixItHint &Hint) { + Range = Hint.RemoveRange.getAsRange(); + ReplacementString = Hint.CodeToInsert; + } + + RefactoringReplacement(RefactoringReplacement &&) = default; + RefactoringReplacement &operator=(RefactoringReplacement &&) = default; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_REPLACEMENT_H diff --git a/clang/include/clang/Tooling/Refactor/RenameIndexedFile.h b/clang/include/clang/Tooling/Refactor/RenameIndexedFile.h new file mode 100644 index 0000000000000..d598259013b4a --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RenameIndexedFile.h @@ -0,0 +1,83 @@ +//===--- RenameIndexedFile.h - -----------------------------*- 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_TOOLING_REFACTOR_RENAME_INDEXED_FILE_H +#define LLVM_CLANG_TOOLING_REFACTOR_RENAME_INDEXED_FILE_H + +#include "clang/Basic/LLVM.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Refactor/RenamedSymbol.h" +#include "clang/Tooling/Refactor/SymbolName.h" +#include "llvm/ADT/ArrayRef.h" + +namespace clang { +namespace tooling { + +class RefactoringOptionSet; + +namespace rename { + +/// An already known occurrence of the symbol that's being renamed. +struct IndexedOccurrence { + /// The location of this occurrence in the indexed file. + unsigned Line, Column; + enum OccurrenceKind { + IndexedSymbol, + IndexedObjCMessageSend, + InclusionDirective + }; + OccurrenceKind Kind; +}; + +struct IndexedSymbol { + SymbolName Name; + std::vector IndexedOccurrences; + /// Whether this symbol is an Objective-C selector. + bool IsObjCSelector; + + IndexedSymbol(SymbolName Name, + std::vector IndexedOccurrences, + bool IsObjCSelector) + : Name(std::move(Name)), + IndexedOccurrences(std::move(IndexedOccurrences)), + IsObjCSelector(IsObjCSelector) {} + IndexedSymbol(IndexedSymbol &&Other) = default; + IndexedSymbol &operator=(IndexedSymbol &&Other) = default; +}; + +/// Consumes the \c SymbolOccurrences found by \c IndexedFileOccurrenceProducer. +class IndexedFileOccurrenceConsumer { +public: + virtual ~IndexedFileOccurrenceConsumer() {} + virtual void handleOccurrence(const SymbolOccurrence &Occurrence, + SourceManager &SM, + const LangOptions &LangOpts) = 0; +}; + +/// Finds the renamed \c SymbolOccurrences in an already indexed files. +class IndexedFileOccurrenceProducer final : public PreprocessorFrontendAction { + bool IsMultiPiece; + ArrayRef Symbols; + IndexedFileOccurrenceConsumer &Consumer; + const RefactoringOptionSet *Options; + +public: + IndexedFileOccurrenceProducer(ArrayRef Symbols, + IndexedFileOccurrenceConsumer &Consumer, + const RefactoringOptionSet *Options = nullptr); + +private: + void ExecuteAction() override; +}; + +} // end namespace rename +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RENAME_INDEXED_FILE_H diff --git a/clang/include/clang/Tooling/Refactor/RenamedSymbol.h b/clang/include/clang/Tooling/Refactor/RenamedSymbol.h new file mode 100644 index 0000000000000..5c5a9061eac72 --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RenamedSymbol.h @@ -0,0 +1,125 @@ +//===--- RenamedSymbol.h - ---------------------------------*- 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_TOOLING_REFACTOR_RENAMED_SYMBOL_H +#define LLVM_CLANG_TOOLING_REFACTOR_RENAMED_SYMBOL_H + +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Refactor/SymbolName.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" + +namespace clang { + +class NamedDecl; + +namespace tooling { +namespace rename { + +/// \brief A symbol that has to be renamed. +class Symbol { +public: + SymbolName Name; + /// The index of this symbol in a \c SymbolOperation. + unsigned SymbolIndex; + /// The declaration that was used to initiate a refactoring operation for this + /// symbol. May not be the most canonical declaration. + const NamedDecl *FoundDecl; + /// An optional Objective-C selector. + llvm::Optional ObjCSelector; + + Symbol(const NamedDecl *FoundDecl, unsigned SymbolIndex, + const LangOptions &LangOpts); + + Symbol(Symbol &&) = default; + Symbol &operator=(Symbol &&) = default; +}; + +/// \brief An occurrence of a renamed symbol. +/// +/// Provides information about an occurrence of symbol that helps renaming tools +/// determine if they can rename this symbol automatically and which source +/// ranges they have to replace. +/// +/// A single occurrence of a symbol can span more than one source range to +/// account for things like Objective-C selectors. +// TODO: Rename +class SymbolOccurrence { +public: + enum OccurrenceKind { + /// \brief This occurrence is an exact match and can be renamed + /// automatically. + MatchingSymbol, + + /// \brief This is an occurrence of a matching selector. It can't be renamed + /// automatically unless the indexer proves that this selector refers only + /// to the declarations that correspond to the renamed symbol. + MatchingSelector, + + /// \brief This is an occurrence of an implicit property that uses the + /// renamed method. + MatchingImplicitProperty, + + /// \brief This is a textual occurrence of a symbol in a comment. + MatchingComment, + + /// \brief This is a textual occurrence of a symbol in a doc comment. + MatchingDocComment, + + /// \brief This is an occurrence of a symbol in an inclusion directive. + MatchingFilename + }; + + OccurrenceKind Kind; + /// Whether or not this occurrence is inside a macro. When this is true, the + /// locations of the occurrence contain just one location that points to + /// the location of the macro expansion. + bool IsMacroExpansion; + /// The index of the symbol stored in a \c SymbolOperation which matches this + /// occurrence. + unsigned SymbolIndex; + /// The source locations that correspond to the occurence of the symbol. + SmallVector Locations; + + SymbolOccurrence() + : Kind(MatchingSymbol), IsMacroExpansion(false), SymbolIndex(0) {} + + SymbolOccurrence(OccurrenceKind Kind, bool IsMacroExpansion, + unsigned SymbolIndex, ArrayRef Locations) + : Kind(Kind), IsMacroExpansion(IsMacroExpansion), + SymbolIndex(SymbolIndex), + Locations(Locations.begin(), Locations.end()) { + assert(!Locations.empty() && "Renamed occurence without locations!"); + } + + SymbolOccurrence(SymbolOccurrence &&) = default; + SymbolOccurrence &operator=(SymbolOccurrence &&) = default; + + /// Return the source range that corresponds to an individual source location + /// in this occurrence. + SourceRange getLocationRange(SourceLocation Loc, size_t OldNameSize) const { + return SourceRange( + Loc, IsMacroExpansion ? Loc : Loc.getLocWithOffset(OldNameSize)); + } +}; + +/// \brief Less-than operator between the two renamed symbol occurrences. +bool operator<(const SymbolOccurrence &LHS, const SymbolOccurrence &RHS); + +/// \brief Equal-to operator between the two renamed symbol occurrences. +bool operator==(const SymbolOccurrence &LHS, const SymbolOccurrence &RHS); + +} // end namespace rename +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RENAMED_SYMBOL_H diff --git a/clang/include/clang/Tooling/Refactor/RenamingOperation.h b/clang/include/clang/Tooling/Refactor/RenamingOperation.h new file mode 100644 index 0000000000000..bb360a0dc766f --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/RenamingOperation.h @@ -0,0 +1,43 @@ +//===--- RenamingOperation.h - -----------------------------*- 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_TOOLING_REFACTOR_RENAMING_OPERATION_H +#define LLVM_CLANG_TOOLING_REFACTOR_RENAMING_OPERATION_H + +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactor/SymbolName.h" +#include "llvm/ADT/SmallVector.h" + +namespace clang { + +class IdentifierTable; + +namespace tooling { + +class SymbolOperation; + +namespace rename { + +/// Return true if the new name is a valid language identifier. +bool isNewNameValid(const SymbolName &NewName, bool IsSymbolObjCSelector, + IdentifierTable &IDs, const LangOptions &LangOpts); +bool isNewNameValid(const SymbolName &NewName, const SymbolOperation &Operation, + IdentifierTable &IDs, const LangOptions &LangOpts); + +/// \brief Finds the set of new names that apply to the symbols in the given +/// \c SymbolOperation. +void determineNewNames(SymbolName NewName, const SymbolOperation &Operation, + SmallVectorImpl &NewNames, + const LangOptions &LangOpts); + +} // end namespace rename +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RENAMING_OPERATION_H diff --git a/clang/include/clang/Tooling/Refactor/SymbolName.h b/clang/include/clang/Tooling/Refactor/SymbolName.h new file mode 100644 index 0000000000000..b2d18720987ae --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/SymbolName.h @@ -0,0 +1,66 @@ +//===--- SymbolName.h - Clang refactoring library ----------*- 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_TOOLING_REFACTOR_SYMBOL_NAME_H +#define LLVM_CLANG_TOOLING_REFACTOR_SYMBOL_NAME_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace clang { + +class LangOptions; + +namespace tooling { + +/// \brief A name of a declaration that's used in the refactoring process. +/// +/// Names can be composed of multiple string, to account for things like +/// Objective-C selectors. +class SymbolName { +public: + SymbolName() {} + + /// \brief Creates a \c SymbolName by decomposing the given \p Name using + /// language specific logic. + SymbolName(StringRef Name, const LangOptions &LangOpts); + SymbolName(StringRef Name, bool IsObjectiveCSelector); + explicit SymbolName(ArrayRef Name); + + SymbolName(SymbolName &&) = default; + SymbolName &operator=(SymbolName &&) = default; + + SymbolName(const SymbolName &) = default; + SymbolName &operator=(const SymbolName &) = default; + + bool empty() const { return Strings.empty(); } + + /// \brief Returns the number of the strings that make up the given name. + size_t size() const { return Strings.size(); } + + /// \brief Returns the string at the given index. + StringRef operator[](size_t I) const { return Strings[I]; } + + ArrayRef strings() const { return Strings; } + + void print(raw_ostream &OS) const; + +private: + std::vector Strings; +}; + +raw_ostream &operator<<(raw_ostream &OS, const SymbolName &N); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_SYMBOL_NAME_H diff --git a/clang/include/clang/Tooling/Refactor/SymbolOccurrenceFinder.h b/clang/include/clang/Tooling/Refactor/SymbolOccurrenceFinder.h new file mode 100644 index 0000000000000..6ecc23b310011 --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/SymbolOccurrenceFinder.h @@ -0,0 +1,37 @@ +//===--- SymbolOccurrenceFinder.h - Clang refactoring library -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides functionality for finding all occurrences of a USR in a +/// given AST. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_SYMBOL_OCCURRENCE_FINDER_H +#define LLVM_CLANG_TOOLING_REFACTOR_SYMBOL_OCCURRENCE_FINDER_H + +#include "clang/AST/AST.h" +#include "clang/Tooling/Refactor/SymbolOperation.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace clang { +namespace tooling { +namespace rename { + +// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! +std::vector +findSymbolOccurrences(const SymbolOperation &Operation, Decl *Decl); + +} // end namespace rename +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_SYMBOL_OCCURRENCE_FINDER_H diff --git a/clang/include/clang/Tooling/Refactor/SymbolOperation.h b/clang/include/clang/Tooling/Refactor/SymbolOperation.h new file mode 100644 index 0000000000000..7616aa36ae1c3 --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/SymbolOperation.h @@ -0,0 +1,91 @@ +//===--- SymbolOperation.h - -------------------------------*- 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_TOOLING_REFACTOR_SYMBOL_OPERATION_H +#define LLVM_CLANG_TOOLING_REFACTOR_SYMBOL_OPERATION_H + +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactor/RenamedSymbol.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { + +class ASTContext; +class NamedDecl; + +namespace tooling { + +/// \brief A refactoring operation that deals with occurrences of symbols. +class SymbolOperation { + /// Contains the symbols that are required for this operation. + SmallVector Symbols; + + /// Maps from a USR to an index in the \c Symbol array. + /// Contains all of the USRs that correspond to the declarations which use + /// the symbols in this operation. + llvm::StringMap USRToSymbol; + + /// True if all the symbols in this operation occur only in the translation + /// unit that defines them. + bool IsLocal; + + /// The declaration whose implementation is needed for the correct initiation + /// of a symbol operation. + const NamedDecl *DeclThatRequiresImplementationTU; + +public: + SymbolOperation(const NamedDecl *FoundDecl, ASTContext &Context); + + SymbolOperation(SymbolOperation &&) = default; + SymbolOperation &operator=(SymbolOperation &&) = default; + + /// Return the symbol that corresponds to the given USR, or null if this USR + /// isn't interesting from the perspective of this operation. + const rename::Symbol *getSymbolForUSR(StringRef USR) const { + auto It = USRToSymbol.find(USR); + if (It != USRToSymbol.end()) + return &Symbols[It->getValue()]; + return nullptr; + } + + /// The symbols that this operation is working on. + /// + /// Symbol operations, like rename, usually just work on just one symbol. + /// However, there are certain language constructs that require more than + /// one symbol in order for them to be renamed correctly. Property + /// declarations in Objective-C are the perfect example: in addition to the + /// actual property, renaming has to rename the corresponding getters and + /// setters, as well as the backing ivar. + ArrayRef symbols() const { return Symbols; } + + /// True if all the symbols in this operation occur only in the translation + /// unit that defines them. + bool isLocal() const { return IsLocal; } + + /// True if the declaration that was found in the initial TU needs to be + /// examined in the TU that implemented it. + bool requiresImplementationTU() const { + return DeclThatRequiresImplementationTU; + } + + /// Returns the declaration whose implementation is needed for the correct + /// initiation of a symbol operation. + const NamedDecl *declThatRequiresImplementationTU() const { + return DeclThatRequiresImplementationTU; + } +}; + +/// Return true if the given declaration corresponds to a local symbol. +bool isLocalSymbol(const NamedDecl *D, const LangOptions &LangOpts); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_SYMBOL_OPERATION_H diff --git a/clang/include/clang/Tooling/Refactor/USRFinder.h b/clang/include/clang/Tooling/Refactor/USRFinder.h new file mode 100644 index 0000000000000..0a83f3086d16e --- /dev/null +++ b/clang/include/clang/Tooling/Refactor/USRFinder.h @@ -0,0 +1,85 @@ +//===--- USRFinder.h - Clang refactoring library --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Methods for determining the USR of a symbol at a location in source +/// code. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_USR_FINDER_H +#define LLVM_CLANG_TOOLING_REFACTOR_USR_FINDER_H + +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include +#include + +namespace clang { + +class ASTContext; +class Decl; +class SourceLocation; +class NamedDecl; + +namespace tooling { +namespace rename { + +using llvm::StringRef; +using namespace clang::ast_matchers; + +// Given an AST context and a point, returns a NamedDecl identifying the symbol +// at the point. Returns null if nothing is found at the point. +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + SourceLocation Point); + +/// Returns a \c NamedDecl that corresponds to the given \p USR in the given +/// AST context. Returns null if there's no declaration that matches the given +/// \p USR. +const NamedDecl *getNamedDeclWithUSR(const ASTContext &Context, StringRef USR); + +// Converts a Decl into a USR. +std::string getUSRForDecl(const Decl *Decl); + +// FIXME: Implement RecursiveASTVisitor::VisitNestedNameSpecifier instead. +class NestedNameSpecifierLocFinder : public MatchFinder::MatchCallback { +public: + explicit NestedNameSpecifierLocFinder(ASTContext &Context) + : Context(Context) {} + + ArrayRef getNestedNameSpecifierLocations() { + addMatchers(); + Finder.matchAST(Context); + return Locations; + } + +private: + void addMatchers() { + const auto NestedNameSpecifierLocMatcher = + nestedNameSpecifierLoc().bind("nestedNameSpecifierLoc"); + Finder.addMatcher(NestedNameSpecifierLocMatcher, this); + } + + void run(const MatchFinder::MatchResult &Result) override { + const auto *NNS = Result.Nodes.getNodeAs( + "nestedNameSpecifierLoc"); + Locations.push_back(*NNS); + } + + ASTContext &Context; + std::vector Locations; + MatchFinder Finder; +}; + +} // end namespace rename +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_USR_FINDER_H diff --git a/clang/include/clang/module.modulemap b/clang/include/clang/module.modulemap index 3b42381100260..a8a23ff18177f 100644 --- a/clang/include/clang/module.modulemap +++ b/clang/include/clang/module.modulemap @@ -132,9 +132,12 @@ module Clang_StaticAnalyzer_Frontend { module Clang_Tooling { requires cplusplus umbrella "Tooling" module * { export * } - // FIXME: Exclude this header to avoid pulling all of the AST matchers + // FIXME: Exclude these headers to avoid pulling all of the AST matchers // library into clang-format. Due to inline key functions in the headers, // importing the AST matchers library gives a link dependency on the AST // matchers (and thus the AST), which clang-format should not have. exclude header "Tooling/RefactoringCallbacks.h" + exclude header "Tooling/Refactor/USRFinder.h" + + textual header "Tooling/Refactor/RefactoringActions.def" } diff --git a/clang/lib/AST/ASTDumper.cpp b/clang/lib/AST/ASTDumper.cpp index d89be0d9e6fa4..19fdf92d23421 100644 --- a/clang/lib/AST/ASTDumper.cpp +++ b/clang/lib/AST/ASTDumper.cpp @@ -1690,6 +1690,8 @@ void ASTDumper::VisitObjCImplementationDecl(const ObjCImplementationDecl *D) { void ASTDumper::VisitObjCCompatibleAliasDecl(const ObjCCompatibleAliasDecl *D) { dumpName(D); dumpDeclRef(D->getClassInterface()); + OS << " "; + dumpLocation(D->getClassInterfaceLoc()); } void ASTDumper::VisitObjCPropertyDecl(const ObjCPropertyDecl *D) { diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp index 8264493c5c82a..b7b6c098b594b 100644 --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -2155,18 +2155,19 @@ raw_ostream &clang::operator<<(raw_ostream &OS, void ObjCCompatibleAliasDecl::anchor() { } -ObjCCompatibleAliasDecl * -ObjCCompatibleAliasDecl::Create(ASTContext &C, DeclContext *DC, - SourceLocation L, - IdentifierInfo *Id, - ObjCInterfaceDecl* AliasedClass) { - return new (C, DC) ObjCCompatibleAliasDecl(DC, L, Id, AliasedClass); +ObjCCompatibleAliasDecl *ObjCCompatibleAliasDecl::Create( + ASTContext &C, DeclContext *DC, SourceLocation NameLoc, IdentifierInfo *Id, + ObjCInterfaceDecl *AliasedClass, SourceLocation AliasedClassLoc, + SourceLocation AtLoc) { + return new (C, DC) ObjCCompatibleAliasDecl(DC, NameLoc, Id, AliasedClass, + AliasedClassLoc, AtLoc); } ObjCCompatibleAliasDecl * ObjCCompatibleAliasDecl::CreateDeserialized(ASTContext &C, unsigned ID) { - return new (C, ID) ObjCCompatibleAliasDecl(nullptr, SourceLocation(), - nullptr, nullptr); + return new (C, ID) + ObjCCompatibleAliasDecl(nullptr, SourceLocation(), nullptr, nullptr, + SourceLocation(), SourceLocation()); } //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 9fab17e103874..d1f53cf0fc2fc 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -119,6 +119,13 @@ void Decl::print(raw_ostream &Out, const PrintingPolicy &Policy, Printer.Visit(const_cast(this)); } +void TemplateParameterList::print(raw_ostream &Out, + const PrintingPolicy &Policy, + unsigned Indentation) const { + DeclPrinter Printer(Out, Policy, Indentation); + Printer.printTemplateParameters(this); +} + static QualType GetBaseType(QualType T) { // FIXME: This should be on the Type class! QualType BaseType = T; @@ -488,13 +495,15 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) { CXXConversionDecl *ConversionDecl = dyn_cast(D); CXXDeductionGuideDecl *GuideDecl = dyn_cast(D); if (!Policy.SuppressSpecifiers) { - switch (D->getStorageClass()) { - case SC_None: break; - case SC_Extern: Out << "extern "; break; - case SC_Static: Out << "static "; break; - case SC_PrivateExtern: Out << "__private_extern__ "; break; - case SC_Auto: case SC_Register: - llvm_unreachable("invalid for functions"); + if (!Policy.SupressStorageClassSpecifiers) { + switch (D->getStorageClass()) { + case SC_None: break; + case SC_Extern: Out << "extern "; break; + case SC_Static: Out << "static "; break; + case SC_PrivateExtern: Out << "__private_extern__ "; break; + case SC_Auto: case SC_Register: + llvm_unreachable("invalid for functions"); + } } if (D->isInlineSpecified()) Out << "inline "; diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index 0551340c37a17..12eb8cb6d9c49 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -84,11 +84,12 @@ namespace { unsigned Indentation; bool HasEmptyPlaceHolder; bool InsideCCAttribute; + bool IgnoreFunctionProtoTypeConstQual; public: explicit TypePrinter(const PrintingPolicy &Policy, unsigned Indentation = 0) - : Policy(Policy), Indentation(Indentation), - HasEmptyPlaceHolder(false), InsideCCAttribute(false) { } + : Policy(Policy), Indentation(Indentation), HasEmptyPlaceHolder(false), + InsideCCAttribute(false), IgnoreFunctionProtoTypeConstQual(false) {} void print(const Type *ty, Qualifiers qs, raw_ostream &OS, StringRef PlaceHolder); @@ -754,8 +755,12 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T, OS << "__attribute__((no_caller_saved_registers))"; if (unsigned quals = T->getTypeQuals()) { - OS << ' '; - AppendTypeQualList(OS, quals, Policy.Restrict); + if (IgnoreFunctionProtoTypeConstQual) + quals &= ~unsigned(Qualifiers::Const); + if (quals) { + OS << ' '; + AppendTypeQualList(OS, quals, Policy.Restrict); + } } switch (T->getRefQualifier()) { @@ -1002,6 +1007,13 @@ void TypePrinter::printTag(TagDecl *D, raw_ostream &OS) { else if (TypedefNameDecl *Typedef = D->getTypedefNameForAnonDecl()) { assert(Typedef->getIdentifier() && "Typedef without identifier?"); OS << Typedef->getIdentifier()->getName(); + } else if (Policy.UseStdFunctionForLambda && isa(D) && + cast(D)->isLambda()) { + OS << "std::function<"; + QualType T = cast(D)->getLambdaCallOperator()->getType(); + SaveAndRestore NoConst(IgnoreFunctionProtoTypeConstQual, true); + print(T, OS, ""); + OS << '>'; } else { // Make an unambiguous representation for anonymous types, e.g. // (anonymous enum at /usr/include/string.h:120:9) diff --git a/clang/lib/Basic/DiagnosticIDs.cpp b/clang/lib/Basic/DiagnosticIDs.cpp index ce493c1e5caba..73d2821f21571 100644 --- a/clang/lib/Basic/DiagnosticIDs.cpp +++ b/clang/lib/Basic/DiagnosticIDs.cpp @@ -43,7 +43,7 @@ struct StaticDiagInfoRec { unsigned SFINAE : 2; unsigned WarnNoWerror : 1; unsigned WarnShowInSystemHeader : 1; - unsigned Category : 5; + unsigned Category : 6; uint16_t OptionGroupIndex; @@ -88,6 +88,7 @@ static const StaticDiagInfoRec StaticDiagInfo[] = { #include "clang/Basic/DiagnosticCommentKinds.inc" #include "clang/Basic/DiagnosticSemaKinds.inc" #include "clang/Basic/DiagnosticAnalysisKinds.inc" +#include "clang/Basic/DiagnosticRefactoringKinds.inc" #undef DIAG }; @@ -137,6 +138,7 @@ CATEGORY(AST, PARSE) CATEGORY(COMMENT, AST) CATEGORY(SEMA, COMMENT) CATEGORY(ANALYSIS, SEMA) +CATEGORY(REFACTORING, ANALYSIS) #undef CATEGORY // Avoid out of bounds reads. diff --git a/clang/lib/Edit/CMakeLists.txt b/clang/lib/Edit/CMakeLists.txt index a7fa9c28e1f61..99aff3c3aeb19 100644 --- a/clang/lib/Edit/CMakeLists.txt +++ b/clang/lib/Edit/CMakeLists.txt @@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS add_clang_library(clangEdit Commit.cpp EditedSource.cpp + FillInMissingProtocolStubs.cpp + FillInMissingSwitchEnumCases.cpp RewriteObjCFoundationAPI.cpp LINK_LIBS diff --git a/clang/lib/Edit/FillInMissingProtocolStubs.cpp b/clang/lib/Edit/FillInMissingProtocolStubs.cpp new file mode 100644 index 0000000000000..3173a345e56fa --- /dev/null +++ b/clang/lib/Edit/FillInMissingProtocolStubs.cpp @@ -0,0 +1,461 @@ +//===--- FillInMissingProtocolStubs.cpp - --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "Add methods from protocol(s)" refactoring operation. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/NSAPI.h" +#include "clang/Edit/RefactoringFixits.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/DenseSet.h" +#include + +using namespace clang; +using namespace edit; +using namespace fillInMissingProtocolStubs; + +// FIXME: This is duplicated with the refactoring lib. +static bool areOnSameLine(SourceLocation Loc1, SourceLocation Loc2, + const SourceManager &SM) { + return !Loc1.isMacroID() && !Loc2.isMacroID() && + SM.getSpellingLineNumber(Loc1) == SM.getSpellingLineNumber(Loc2); +} + +static bool isSemicolonAtLocation(SourceLocation TokenLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(TokenLoc, TokenLoc), SM, + LangOpts) == ";"; +} + +static SourceLocation getLocationOfPrecedingToken(SourceLocation Loc, + const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation Result = Loc; + if (Result.isMacroID()) + Result = SM.getExpansionLoc(Result); + FileID FID = SM.getFileID(Result); + SourceLocation StartOfFile = SM.getLocForStartOfFile(FID); + if (Loc == StartOfFile) + return SourceLocation(); + return Lexer::GetBeginningOfToken(Result.getLocWithOffset(-1), SM, LangOpts); +} + +static SourceLocation +getLastLineLocationUnlessItHasOtherTokens(SourceLocation SpellingLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + assert(!SpellingLoc.isMacroID() && "Expecting a spelling location"); + SourceLocation NextTokenLoc = + Lexer::findNextTokenLocationAfterTokenAt(SpellingLoc, SM, LangOpts); + if (NextTokenLoc.isValid()) { + bool IsSameLine = areOnSameLine(SpellingLoc, NextTokenLoc, SM); + if (IsSameLine) { + // Could be a ';' on the same line, so try looking after the ';' + if (isSemicolonAtLocation(NextTokenLoc, SM, LangOpts)) + return getLastLineLocationUnlessItHasOtherTokens(NextTokenLoc, SM, + LangOpts); + } else { + SourceLocation LastLoc = SM.translateLineCol( + SM.getFileID(SpellingLoc), SM.getSpellingLineNumber(SpellingLoc), + std::numeric_limits::max()); + if (LastLoc.isValid()) + return LastLoc; + } + } + return Lexer::getLocForEndOfToken(SpellingLoc, 0, SM, LangOpts); +} + +namespace { + +struct ProtocolInfo { + /// The lower the priority, the more important this protocol is considered to + /// be. Typically protocols from the class have lower priority than protocols + /// from superclasses. + int Priority; +}; + +using ProtocolMapTy = llvm::DenseMap; + +/// Contains the set of methods from all the protocols that the class conforms +/// to. +class MethodSet { +public: + struct MethodInfo { + const ObjCMethodDecl *M; + const ObjCProtocolDecl *P; + int ProtocolPriority; + enum MethodPresenceKind { IsDeclared = 0x1, IsImplemented = 0x2 }; + unsigned PresenceKind = 0; + const ObjCMethodDecl *DeclaredOrImplementedMethod = nullptr; + + MethodInfo(const ObjCMethodDecl *M, const ObjCProtocolDecl *P, + int ProtocolPriority) + : M(M), P(P), ProtocolPriority(ProtocolPriority) {} + + bool isRequired() const { + return M->getImplementationControl() == ObjCMethodDecl::Required; + } + void markAs(MethodPresenceKind Kind) { PresenceKind |= Kind; } + bool is(MethodPresenceKind Kind) const { + return (PresenceKind & Kind) == Kind; + } + }; + +private: + llvm::DenseMap InstanceMethods; + llvm::DenseMap ClassMethods; + + void markMethodsFrom(const ObjCContainerDecl *Container, + MethodInfo::MethodPresenceKind Kind) { + for (const ObjCMethodDecl *M : Container->methods()) { + auto &Map = M->isInstanceMethod() ? InstanceMethods : ClassMethods; + auto It = Map.find(M->getSelector()); + if (It != Map.end()) { + It->second.markAs(Kind); + if (!It->second.DeclaredOrImplementedMethod) + It->second.DeclaredOrImplementedMethod = M; + } + } + } + +public: + MethodSet() {} + MethodSet(MethodSet &&Other) = default; + MethodSet &operator=(MethodSet &&Other) = default; + + void gatherMethodsFrom(const ObjCProtocolDecl *P, int Priority) { + for (const ObjCMethodDecl *M : P->methods()) { + if (M->isImplicit()) + continue; + auto &Map = M->isInstanceMethod() ? InstanceMethods : ClassMethods; + Map.insert(std::make_pair(M->getSelector(), MethodInfo(M, P, Priority))); + } + } + + void markImplementedMethods(const ObjCContainerDecl *Container) { + assert(isa(Container) && "Not an implementation container"); + markMethodsFrom(Container, MethodInfo::IsImplemented); + if (const auto *ID = dyn_cast(Container)) { + const auto *I = ID->getClassInterface(); + // Mark declarations from super-classes as implemented to prevent + // redundant implementations. + while ((I = I->getSuperClass())) + markMethodsFrom(I, MethodInfo::IsImplemented); + } + } + + void markDeclaredMethods(const ObjCContainerDecl *Container) { + assert(!isa(Container) && "Not an interface container"); + markMethodsFrom(Container, MethodInfo::IsDeclared); + // Mark declarations from super-classes as declared to prevent redundant + // declarations. + if (const auto *I = dyn_cast(Container)) { + while ((I = I->getSuperClass())) + markMethodsFrom(I, MethodInfo::IsDeclared); + } + } + + /// Returns true if the given container has missing @required method stubs. + /// + /// For @interfaces, this method returns true when the interface is missing + /// a declaration for any @required method in all of the protocols. + /// For @implementations, this method returns true when the implementation is + /// missing an implementation of any @required method in all of the protocols. + bool hasMissingRequiredMethodStubs(const ObjCContainerDecl *Container) { + MethodInfo::MethodPresenceKind Kind = isa(Container) + ? MethodInfo::IsImplemented + : MethodInfo::IsDeclared; + for (const auto &I : InstanceMethods) { + if (!I.second.isRequired()) + continue; + if (!I.second.is(Kind)) + return true; + } + for (const auto &I : ClassMethods) { + if (!I.second.isRequired()) + continue; + if (!I.second.is(Kind)) + return true; + } + return false; + } + + std::vector + getMissingRequiredMethods(const ObjCContainerDecl *Container) { + MethodInfo::MethodPresenceKind Kind = isa(Container) + ? MethodInfo::IsImplemented + : MethodInfo::IsDeclared; + std::vector Results; + for (const auto &I : InstanceMethods) { + if (!I.second.isRequired()) + continue; + if (!I.second.is(Kind)) + Results.push_back(I.second); + } + for (const auto &I : ClassMethods) { + if (!I.second.isRequired()) + continue; + if (!I.second.is(Kind)) + Results.push_back(I.second); + } + return Results; + } + + SourceLocation findLocationForInsertionForMethodsFromProtocol( + const ObjCProtocolDecl *P, const ObjCContainerDecl *Container, + const SourceManager &SM, const LangOptions &LangOpts) { + MethodInfo::MethodPresenceKind Kind = isa(Container) + ? MethodInfo::IsImplemented + : MethodInfo::IsDeclared; + llvm::SmallVector MethodsFromProtocolInContainer; + for (const ObjCMethodDecl *M : P->methods()) { + if (M->isImplicit()) + continue; + const auto &Map = M->isInstanceMethod() ? InstanceMethods : ClassMethods; + auto It = Map.find(M->getSelector()); + if (It == Map.end()) + continue; + if (!It->second.is(Kind)) + continue; + const ObjCMethodDecl *ContainerMethod = + It->second.DeclaredOrImplementedMethod; + // Ignore method declarations from superclasses. + if (ContainerMethod->getLexicalDeclContext() != Container) + continue; + // This is a method from the given protocol that either declared or + // implemented in the container. + MethodsFromProtocolInContainer.push_back(ContainerMethod); + } + // Find the appropriate source locations by looking + if (MethodsFromProtocolInContainer.empty()) + return SourceLocation(); + SourceLocation Loc = MethodsFromProtocolInContainer[0]->getLocEnd(); + if (Loc.isMacroID()) + Loc = SM.getExpansionRange(Loc).second; + for (const ObjCMethodDecl *M : + makeArrayRef(MethodsFromProtocolInContainer).drop_front()) { + SourceLocation EndLoc = M->getLocEnd(); + if (EndLoc.isMacroID()) + EndLoc = SM.getExpansionRange(EndLoc).second; + if (SM.isBeforeInTranslationUnit(Loc, EndLoc)) + Loc = EndLoc; + } + return getLastLineLocationUnlessItHasOtherTokens(Loc, SM, LangOpts); + } +}; + +} // end anonymous namespace + +namespace clang { +namespace edit { +namespace fillInMissingProtocolStubs { + +class FillInMissingProtocolStubsImpl { +public: + const ObjCContainerDecl *Container; + MethodSet Methods; +}; + +} // end namespace fillInMissingProtocolStubsImpl +} // end namespace edit +} // end namespace clang + +static void gatherProtocols( + llvm::iterator_range::iterator> Protocols, + NSAPI &API, ProtocolMapTy &Result, int &Priority) { + for (const ObjCProtocolDecl *P : Protocols) { + // Ignore the 'NSObject' protocol. + if (API.getNSClassId(NSAPI::ClassId_NSObject) == P->getIdentifier()) + continue; + gatherProtocols(P->protocols(), API, Result, Priority); + Result.insert(std::make_pair(P, ProtocolInfo{Priority++})); + } +} + +static ProtocolMapTy +gatherSuitableClassProtocols(const ObjCInterfaceDecl *I, + const ObjCContainerDecl *Container, NSAPI &API) { + ProtocolMapTy Result; + // The class of interest should use the protocols from extensions when the + // operation is initiated from the @implementation / extension. + auto ClassProtocols = + Container == I ? I->protocols() : I->all_referenced_protocols(); + int Priority = 0; + gatherProtocols(ClassProtocols, API, Result, Priority); + while ((I = I->getSuperClass())) + gatherProtocols(I->protocols(), API, Result, Priority); + return Result; +} + +static const ObjCContainerDecl * +getInterfaceOrCategory(const ObjCContainerDecl *Container) { + if (const auto *Impl = dyn_cast(Container)) + return Impl->getClassInterface(); + if (const auto *CategoryImpl = dyn_cast(Container)) + return CategoryImpl->getCategoryDecl(); + return Container; +} + +static bool initiate(FillInMissingProtocolStubsImpl &Dest, ASTContext &Context, + const ObjCContainerDecl *Container) { + const ObjCContainerDecl *ContainerProtocolSource = + getInterfaceOrCategory(Container); + if (!ContainerProtocolSource) + return false; + + // The protocols that are specified in the @interface and/or in the + // superclasses. + ProtocolMapTy Protocols; + NSAPI API(Context); + if (const auto *I = dyn_cast(ContainerProtocolSource)) { + if (!I->hasDefinition()) + return false; + Protocols = gatherSuitableClassProtocols(I, Container, API); + if (Protocols.empty()) + return false; + } else if (const auto *I = + dyn_cast(ContainerProtocolSource)) { + int Priority = 0; + gatherProtocols(I->protocols(), API, Protocols, Priority); + if (Protocols.empty()) + return false; + } + + // Check if there are missing @required methods. + for (const auto &P : Protocols) + Dest.Methods.gatherMethodsFrom(P.first, P.second.Priority); + if (isa(Container)) + Dest.Methods.markImplementedMethods(Container); + else + Dest.Methods.markDeclaredMethods(Container); + + Dest.Container = Container; + return true; +} + +FillInMissingProtocolStubs::FillInMissingProtocolStubs() {} +FillInMissingProtocolStubs::~FillInMissingProtocolStubs() {} +FillInMissingProtocolStubs::FillInMissingProtocolStubs( + FillInMissingProtocolStubs &&Other) + : Impl(std::move(Other.Impl)) {} +FillInMissingProtocolStubs &FillInMissingProtocolStubs:: +operator=(FillInMissingProtocolStubs &&Other) { + Impl = std::move(Other.Impl); + return *this; +} + +bool FillInMissingProtocolStubs::initiate(ASTContext &Context, + const ObjCContainerDecl *Container) { + Impl = llvm::make_unique(); + if (!::initiate(*Impl, Context, Container)) + return true; + return false; +} + +bool FillInMissingProtocolStubs::hasMissingRequiredMethodStubs() { + return Impl->Methods.hasMissingRequiredMethodStubs(Impl->Container); +} + +static void perform(MethodSet &Methods, const ObjCContainerDecl *Container, + ASTContext &Context, + llvm::function_ref Consumer) { + auto MissingMethods = Methods.getMissingRequiredMethods(Container); + // Sort the methods by grouping them into protocol clusters and then sorting + // them alphabetically within the same protocol. + std::sort(MissingMethods.begin(), MissingMethods.end(), + [](const MethodSet::MethodInfo &A, const MethodSet::MethodInfo &B) { + if (A.ProtocolPriority == B.ProtocolPriority) + return A.M->getSelector().getAsString() < + B.M->getSelector().getAsString(); + assert(A.P != B.P && "Same protocols should have same priority"); + return A.ProtocolPriority < B.ProtocolPriority; + }); + + SourceLocation InsertionLoc = + isa(Container) + ? Container->getLocEnd() + : getLocationOfPrecedingToken(Container->getLocEnd(), + Context.getSourceManager(), + Context.getLangOpts()); + if (InsertionLoc.isInvalid()) + InsertionLoc = Container->getLocEnd(); + + PrintingPolicy PP = Context.getPrintingPolicy(); + PP.PolishForDeclaration = true; + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + + std::string EndInsertionOSStr; + llvm::raw_string_ostream EndInsertionOS(EndInsertionOSStr); + + std::string InsertionGroupStr; + llvm::raw_string_ostream InsertionGroupOS(InsertionGroupStr); + + const ObjCProtocolDecl *CurrentProtocol = nullptr; + SourceLocation CurrentProtocolInsertionLoc; + bool IsImplementation = isa(Container); + for (const auto &Method : MissingMethods) { + const ObjCProtocolDecl *P = Method.P; + if (CurrentProtocol != P) { + if (!InsertionGroupOS.str().empty()) { + assert(CurrentProtocolInsertionLoc.isValid()); + Consumer(FixItHint::CreateInsertion(CurrentProtocolInsertionLoc, + InsertionGroupOS.str())); + } + InsertionGroupStr.clear(); + CurrentProtocol = P; + CurrentProtocolInsertionLoc = + Methods.findLocationForInsertionForMethodsFromProtocol( + P, Container, Context.getSourceManager(), Context.getLangOpts()); + } + bool IsInsertingAfterRelatedMethods = CurrentProtocolInsertionLoc.isValid(); + raw_ostream &OS = + IsInsertingAfterRelatedMethods ? InsertionGroupOS : EndInsertionOS; + + std::string MethodDeclStr; + llvm::raw_string_ostream MethodOS(MethodDeclStr); + Method.M->print(MethodOS, PP); + if (IsInsertingAfterRelatedMethods) + OS << "\n\n"; + OS << StringRef(MethodOS.str()).drop_back(); // Drop the ';' + if (IsImplementation) + OS << " { \n <#code#>\n}\n"; + else + OS << ";\n"; + if (!IsInsertingAfterRelatedMethods) + OS << "\n"; + } + if (!InsertionGroupOS.str().empty()) { + assert(CurrentProtocolInsertionLoc.isValid()); + Consumer(FixItHint::CreateInsertion(CurrentProtocolInsertionLoc, + InsertionGroupOS.str())); + } + if (!EndInsertionOS.str().empty()) + Consumer(FixItHint::CreateInsertion(InsertionLoc, EndInsertionOS.str())); +} + +void FillInMissingProtocolStubs::perform( + ASTContext &Context, llvm::function_ref Consumer) { + ::perform(Impl->Methods, Impl->Container, Context, Consumer); +} + +void fillInMissingProtocolStubs::addMissingProtocolStubs( + ASTContext &Context, const ObjCContainerDecl *Container, + llvm::function_ref Consumer) { + FillInMissingProtocolStubsImpl Impl; + if (initiate(Impl, Context, Container)) + perform(Impl.Methods, Impl.Container, Context, Consumer); +} diff --git a/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp b/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp new file mode 100644 index 0000000000000..e2dcdb1f36d65 --- /dev/null +++ b/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp @@ -0,0 +1,189 @@ +//===--- FillInMissingSwitchEnumCases.cpp - ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include "clang/AST/NestedNameSpecifier.h" +#include "clang/Edit/RefactoringFixits.h" +#include + +using namespace clang; + +namespace { + +struct CaseInfo { + const SwitchCase *Case, *NextCase; + unsigned Index; +}; +typedef std::unordered_map CoveredEnumCasesInfoType; + +/// Return true if the ordering of the covered enum cases is similar to the +/// order of the enum case constants that are defined in the enum. +bool useCaseBasedOrdering(const ArrayRef &CoveredEnumCaseValues, + const CoveredEnumCasesInfoType &CoveredEnumCases) { + if (CoveredEnumCaseValues.empty()) + return false; + for (const auto &I : llvm::enumerate(CoveredEnumCaseValues)) { + auto It = CoveredEnumCases.find(I.value()); + if (It == CoveredEnumCases.end()) + return false; + const CaseInfo &Case = It->second; + if (Case.Index != I.index()) + return false; + } + return true; +} + +/// Determine if the inserted cases should be wrapped in braces using a simple +/// heuristic: +/// Wrap only if at least 90% of existing cases use braces. +bool useBraces(const SwitchStmt *S) { + unsigned CaseCount = 0, CompoundCasesCount = 0; + for (const SwitchCase *Case = S->getSwitchCaseList(); Case; + Case = Case->getNextSwitchCase(), ++CaseCount) { + if (isa(Case->getSubStmt())) + ++CompoundCasesCount; + } + return CaseCount && float(CompoundCasesCount) / float(CaseCount) >= 0.9; +} + +} // end anonymous namespace + +void edit::fillInMissingSwitchEnumCases( + ASTContext &Context, const SwitchStmt *Switch, const EnumDecl *Enum, + const DeclContext *SwitchContext, + llvm::function_ref Consumer) { + // Compute the number of cases in the switch. + unsigned CaseCount = 0; + for (const SwitchCase *Case = Switch->getSwitchCaseList(); Case; + Case = Case->getNextSwitchCase()) + ++CaseCount; + + // Compute the set of enum values that are covered by the switch. + CoveredEnumCasesInfoType CoveredEnumCases; + const SwitchCase *DefaultCase = nullptr; + const SwitchCase *FirstCoveredEnumCase = nullptr; + const SwitchCase *NextCase = nullptr; + unsigned CaseIndex = CaseCount; + for (const SwitchCase *Case = Switch->getSwitchCaseList(); Case; + NextCase = Case, Case = Case->getNextSwitchCase()) { + // The cases in the switch are ordered back to front, so the index has + // to be reversed. + --CaseIndex; + if (isa(Case)) { + DefaultCase = Case; + continue; + } + const auto *CS = cast(Case); + if (const auto *LHS = CS->getLHS()) { + llvm::APSInt Value; + if (!LHS->EvaluateAsInt(Value, Context)) + continue; + // Only allow constant that fix into 64 bits. + if (Value.getMinSignedBits() > 64) + continue; + CoveredEnumCases[Value.getSExtValue()] = + CaseInfo{Case, NextCase, CaseIndex}; + // The cases in the switch are ordered back to front, so the last + // case is actually the first enum case in the switch. + FirstCoveredEnumCase = Case; + } + } + + // Wrap the inserted cases in braces using a simple heuristic: + // Wrap only if at least 90% of existing cases use braces. + bool WrapInBraces = useBraces(Switch); + auto CreateReplacementForMissingCaseGroup = + [&](ArrayRef UncoveredEnumCases, + SourceLocation InsertionLoc = SourceLocation()) { + if (UncoveredEnumCases.empty()) + return; + std::string Result; + llvm::raw_string_ostream OS(Result); + for (const auto *EnumCase : UncoveredEnumCases) { + OS << "case "; + if (SwitchContext) { + const auto *NS = NestedNameSpecifier::getRequiredQualification( + Context, SwitchContext, Enum->getLexicalDeclContext()); + if (NS) + NS->print(OS, Context.getPrintingPolicy()); + } + if (Enum->isScoped()) + OS << Enum->getName() << "::"; + OS << EnumCase->getName() << ":"; + if (WrapInBraces) + OS << " {"; + OS << "\n<#code#>\nbreak;\n"; + if (WrapInBraces) + OS << "}\n"; + } + + if (InsertionLoc.isInvalid()) { + // Insert the cases before the 'default' if it's the last case in the + // switch. + // Note: Switch cases are ordered back to front, so the last default + // case would be the first case in the switch statement. + if (DefaultCase && DefaultCase == Switch->getSwitchCaseList()) + InsertionLoc = DefaultCase->getLocStart(); + else + InsertionLoc = Switch->getBody()->getLocEnd(); + } + Consumer(FixItHint::CreateInsertion(InsertionLoc, OS.str())); + }; + + // Determine which enum cases are uncovered. + + llvm::SmallVector, 8> EnumCases; + llvm::SmallVector CoveredEnumCaseValues; + for (const auto *EnumCase : Enum->enumerators()) { + if (EnumCase->getInitVal().getMinSignedBits() > 64) + continue; + int64_t Value = EnumCase->getInitVal().getSExtValue(); + EnumCases.push_back(std::make_pair(EnumCase, Value)); + if (CoveredEnumCases.count(Value)) + CoveredEnumCaseValues.push_back(Value); + } + + llvm::SmallVector UncoveredEnumCases; + // Figure out if the ordering of the covered enum cases is similar to the + // order of enum case values defined in the enum. + if (useCaseBasedOrdering(CoveredEnumCaseValues, CoveredEnumCases)) { + // Start inserting before the first covered case. + SourceLocation InsertionLoc = FirstCoveredEnumCase->getLocStart(); + + for (const auto &EnumCase : EnumCases) { + if (!CoveredEnumCases.count(EnumCase.second)) { + UncoveredEnumCases.push_back(EnumCase.first); + continue; + } + // Create the insertion source replacement for this set of uncovered + // cases. + CreateReplacementForMissingCaseGroup(UncoveredEnumCases, InsertionLoc); + UncoveredEnumCases.clear(); + // Find the insertion location for the next set of uncovered cases. + auto It = CoveredEnumCases.find(EnumCase.second); + assert(It != CoveredEnumCases.end() && "Missing enum case"); + const CaseInfo &Case = It->second; + InsertionLoc = Case.NextCase ? Case.NextCase->getLocStart() + : /*Insert before end*/ SourceLocation(); + } + CreateReplacementForMissingCaseGroup(UncoveredEnumCases, InsertionLoc); + } else { + // Gather all of the uncovered enum cases. + for (const auto &EnumCase : EnumCases) { + if (!CoveredEnumCases.count(EnumCase.second)) + UncoveredEnumCases.push_back(EnumCase.first); + } + assert(!UncoveredEnumCases.empty() && + "Can't fill-in enum cases in a full switch"); + CreateReplacementForMissingCaseGroup(UncoveredEnumCases); + } +} diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index 447ff212f06e1..08a27e80ae8de 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -1258,6 +1258,35 @@ SourceLocation Lexer::findLocationAfterToken(SourceLocation Loc, return TokenLoc.getLocWithOffset(Tok.getLength() + NumWhitespaceChars); } +SourceLocation Lexer::findNextTokenLocationAfterTokenAt( + SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts) { + // TODO: Share the code with the function above when upstreaming. + if (Loc.isMacroID()) { + if (!Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc)) + return SourceLocation(); + } + Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts); + + // Break down the source location. + std::pair LocInfo = SM.getDecomposedLoc(Loc); + + // Try to load the file buffer. + bool InvalidTemp = false; + StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp); + if (InvalidTemp) + return SourceLocation(); + + const char *TokenBegin = File.data() + LocInfo.second; + + // Lex from the start of the given location. + Lexer lexer(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(), + TokenBegin, File.end()); + // Find the token. + Token Tok; + lexer.LexFromRawLexer(Tok); + return Tok.getLocation(); +} + /// getCharAndSizeSlow - Peek a single 'character' from the specified buffer, /// get its size, and return it. This is tricky in several cases: /// 1. If currently at the start of a trigraph, we warn about the trigraph, diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index fe920fd61f6c8..f6a18fd8cb90e 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -20,6 +20,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/SourceManager.h" +#include "clang/Edit/RefactoringFixits.h" #include "clang/Sema/DeclSpec.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Scope.h" @@ -1095,7 +1096,8 @@ Decl *Sema::ActOnCompatibilityAlias(SourceLocation AtLoc, // Everything checked out, instantiate a new alias declaration AST. ObjCCompatibleAliasDecl *AliasDecl = - ObjCCompatibleAliasDecl::Create(Context, CurContext, AtLoc, AliasName, CDecl); + ObjCCompatibleAliasDecl::Create(Context, CurContext, AliasLocation, + AliasName, CDecl, ClassLocation, AtLoc); if (!CheckObjCDeclScope(AliasDecl)) PushOnScopeChains(AliasDecl, TUScope); @@ -2605,14 +2607,12 @@ static void findProtocolsWithExplicitImpls(const ObjCInterfaceDecl *Super, /// CheckProtocolMethodDefs - This routine checks unimplemented methods /// Declared in protocol, and those referenced by it. -static void CheckProtocolMethodDefs(Sema &S, - SourceLocation ImpLoc, - ObjCProtocolDecl *PDecl, - bool& IncompleteImpl, - const Sema::SelectorSet &InsMap, - const Sema::SelectorSet &ClsMap, - ObjCContainerDecl *CDecl, - LazyProtocolNameSet &ProtocolsExplictImpl) { +static void CheckProtocolMethodDefs( + Sema &S, SourceLocation ImpLoc, ObjCProtocolDecl *PDecl, + bool &IncompleteImpl, const Sema::SelectorSet &InsMap, + const Sema::SelectorSet &ClsMap, ObjCContainerDecl *CDecl, + LazyProtocolNameSet &ProtocolsExplictImpl, + llvm::SmallPtrSetImpl *MissingRequirements) { ObjCCategoryDecl *C = dyn_cast(CDecl); ObjCInterfaceDecl *IDecl = C ? C->getClassInterface() : dyn_cast(CDecl); @@ -2672,6 +2672,7 @@ static void CheckProtocolMethodDefs(Sema &S, // protocol. This lookup is slow, but occurs rarely in correct code // and otherwise would terminate in a warning. + bool HasMissingRequirements = false; // check unimplemented instance methods. if (!NSIDecl) for (auto *method : PDecl->instance_methods()) { @@ -2701,8 +2702,11 @@ static void CheckProtocolMethodDefs(Sema &S, continue; unsigned DIAG = diag::warn_unimplemented_protocol_method; if (!S.Diags.isIgnored(DIAG, ImpLoc)) { - WarnUndefinedMethod(S, ImpLoc, method, IncompleteImpl, DIAG, - PDecl); + if (MissingRequirements) + HasMissingRequirements = true; + else + WarnUndefinedMethod(S, ImpLoc, method, IncompleteImpl, DIAG, + PDecl); } } } @@ -2724,14 +2728,21 @@ static void CheckProtocolMethodDefs(Sema &S, unsigned DIAG = diag::warn_unimplemented_protocol_method; if (!S.Diags.isIgnored(DIAG, ImpLoc)) { - WarnUndefinedMethod(S, ImpLoc, method, IncompleteImpl, DIAG, PDecl); + if (MissingRequirements) + HasMissingRequirements = true; + else + WarnUndefinedMethod(S, ImpLoc, method, IncompleteImpl, DIAG, PDecl); } } } + if (HasMissingRequirements) { + assert(MissingRequirements != nullptr && "No missing requirements!"); + MissingRequirements->insert(PDecl); + } // Check on this protocols's referenced protocols, recursively. for (auto *PI : PDecl->protocols()) CheckProtocolMethodDefs(S, ImpLoc, PI, IncompleteImpl, InsMap, ClsMap, - CDecl, ProtocolsExplictImpl); + CDecl, ProtocolsExplictImpl, MissingRequirements); } /// MatchAllMethodDeclarations - Check methods declared in interface @@ -2943,23 +2954,49 @@ void Sema::ImplMethodsVsClassMethods(Scope *S, ObjCImplDecl* IMPDecl, LazyProtocolNameSet ExplicitImplProtocols; + bool UseEditorDiagnostics = !getDiagnostics() + .getDiagnosticOptions() + .DiagnosticSerializationFile.empty() || + getLangOpts().AllowEditorPlaceholders; + llvm::SmallPtrSet MissingRequirements; if (ObjCInterfaceDecl *I = dyn_cast (CDecl)) { for (auto *PI : I->all_referenced_protocols()) CheckProtocolMethodDefs(*this, IMPDecl->getLocation(), PI, IncompleteImpl, - InsMap, ClsMap, I, ExplicitImplProtocols); + InsMap, ClsMap, I, ExplicitImplProtocols, + UseEditorDiagnostics ? &MissingRequirements + : nullptr); } else if (ObjCCategoryDecl *C = dyn_cast(CDecl)) { // For extended class, unimplemented methods in its protocols will // be reported in the primary class. if (!C->IsClassExtension()) { for (auto *P : C->protocols()) - CheckProtocolMethodDefs(*this, IMPDecl->getLocation(), P, - IncompleteImpl, InsMap, ClsMap, CDecl, - ExplicitImplProtocols); + CheckProtocolMethodDefs( + *this, IMPDecl->getLocation(), P, IncompleteImpl, InsMap, ClsMap, + CDecl, ExplicitImplProtocols, + UseEditorDiagnostics ? &MissingRequirements : nullptr); DiagnoseUnimplementedProperties(S, IMPDecl, CDecl, /*SynthesizeProperties=*/false); } } else llvm_unreachable("invalid ObjCContainerDecl type."); + if (!MissingRequirements.empty()) { + { + auto DB = Diag(IMPDecl->getLocation(), + diag::warn_class_does_not_conform_protocol) + << (isa(CDecl) ? /*category=*/1 : /*class=*/0) + << CDecl << (unsigned)MissingRequirements.size(); + unsigned NumProtocols = 0; + for (const auto *PD : MissingRequirements) { + DB << PD; + if (++NumProtocols > 3) + break; + } + } + auto DB = + Diag(IMPDecl->getLocation(), diag::note_add_missing_protocol_stubs); + edit::fillInMissingProtocolStubs::addMissingProtocolStubs( + Context, IMPDecl, [&](const FixItHint &Hint) { DB << Hint; }); + } } Sema::DeclGroupPtrTy diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index eed10b077eb88..98032a04cc789 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -11,11 +11,10 @@ // //===----------------------------------------------------------------------===// -#include "clang/Sema/SemaInternal.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" -#include "clang/AST/CharUnits.h" #include "clang/AST/CXXInheritance.h" +#include "clang/AST/CharUnits.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/EvaluatedExprVisitor.h" #include "clang/AST/ExprCXX.h" @@ -26,11 +25,13 @@ #include "clang/AST/TypeLoc.h" #include "clang/AST/TypeOrdering.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Edit/RefactoringFixits.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Scope.h" #include "clang/Sema/ScopeInfo.h" +#include "clang/Sema/SemaInternal.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" @@ -1160,14 +1161,22 @@ Sema::ActOnFinishSwitchStmt(SourceLocation SwitchLoc, Stmt *Switch, // Produce a nice diagnostic if multiple values aren't handled. if (!UnhandledNames.empty()) { - DiagnosticBuilder DB = Diag(CondExpr->getExprLoc(), - TheDefaultStmt ? diag::warn_def_missing_case - : diag::warn_missing_case) - << (int)UnhandledNames.size(); - - for (size_t I = 0, E = std::min(UnhandledNames.size(), (size_t)3); - I != E; ++I) - DB << UnhandledNames[I]; + { + DiagnosticBuilder DB = + Diag(CondExpr->getExprLoc(), TheDefaultStmt + ? diag::warn_def_missing_case + : diag::warn_missing_case) + << (int)UnhandledNames.size(); + + for (size_t I = 0, E = std::min(UnhandledNames.size(), (size_t)3); + I != E; ++I) + DB << UnhandledNames[I]; + } + auto DB = + Diag(CondExpr->getExprLoc(), diag::note_fill_in_missing_cases); + edit::fillInMissingSwitchEnumCases( + Context, SS, ED, CurContext, + [&](const FixItHint &Hint) { DB << Hint; }); } if (!hasCasesNotInSwitch) diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp index 9c467055fe551..c9ccc16fa9e7c 100644 --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -1136,6 +1136,8 @@ void ASTDeclReader::VisitObjCCategoryDecl(ObjCCategoryDecl *CD) { void ASTDeclReader::VisitObjCCompatibleAliasDecl(ObjCCompatibleAliasDecl *CAD) { VisitNamedDecl(CAD); CAD->setClassInterface(ReadDeclAs()); + CAD->setClassInterfaceLoc(ReadSourceLocation()); + CAD->setAtLoc(ReadSourceLocation()); } void ASTDeclReader::VisitObjCPropertyDecl(ObjCPropertyDecl *D) { diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp index 2d648cb103cbf..8d1428a925257 100644 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -784,6 +784,8 @@ void ASTDeclWriter::VisitObjCCategoryDecl(ObjCCategoryDecl *D) { void ASTDeclWriter::VisitObjCCompatibleAliasDecl(ObjCCompatibleAliasDecl *D) { VisitNamedDecl(D); Record.AddDeclRef(D->getClassInterface()); + Record.AddSourceLocation(D->getClassInterfaceLoc()); + Record.AddSourceLocation(D->getAtLoc()); Code = serialization::DECL_OBJC_COMPATIBLE_ALIAS; } diff --git a/clang/lib/Tooling/CMakeLists.txt b/clang/lib/Tooling/CMakeLists.txt index 7b0c58e7947d6..33214509d450f 100644 --- a/clang/lib/Tooling/CMakeLists.txt +++ b/clang/lib/Tooling/CMakeLists.txt @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS ) add_subdirectory(Core) +add_subdirectory(Refactor) add_subdirectory(Refactoring) add_clang_library(clangTooling @@ -30,4 +31,6 @@ add_clang_library(clangTooling clangLex clangRewrite clangToolingCore + clangToolingRefactor + clangToolingRefactoring ) diff --git a/clang/lib/Tooling/CompilationDatabase.cpp b/clang/lib/Tooling/CompilationDatabase.cpp index 77c5b547ca09a..0238b1c8079c3 100644 --- a/clang/lib/Tooling/CompilationDatabase.cpp +++ b/clang/lib/Tooling/CompilationDatabase.cpp @@ -257,8 +257,10 @@ static bool stripPositionalArgs(std::vector Args, for (const auto &Cmd : Jobs) { // Collect only for Assemble jobs. If we do all jobs we get duplicates // since Link jobs point to Assemble jobs as inputs. - if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass) + if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass || + Cmd.getSource().getKind() == driver::Action::CompileJobClass) { CompileAnalyzer.run(&Cmd.getSource()); + } } if (CompileAnalyzer.Inputs.empty()) { diff --git a/clang/lib/Tooling/Refactor/ASTSlice.cpp b/clang/lib/Tooling/Refactor/ASTSlice.cpp new file mode 100644 index 0000000000000..5447571caee8c --- /dev/null +++ b/clang/lib/Tooling/Refactor/ASTSlice.cpp @@ -0,0 +1,625 @@ +//===--- ASTSlice.cpp - Represents a portion of the AST -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ASTSlice.h" +#include "SourceLocationUtilities.h" +#include "StmtUtils.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/SaveAndRestore.h" +#include + +using namespace clang; +using namespace clang::tooling; + +namespace { + +/// Searches for AST nodes around the given source location and range that can +/// be used to initiate a refactoring operation. +class ASTSliceFinder : public clang::RecursiveASTVisitor { +public: + explicit ASTSliceFinder(SourceLocation Location, SourceRange SelectionRange, + const ASTContext &Context) + : Location(Location), SelectionRange(SelectionRange), Context(Context) {} + + bool TraverseDecl(Decl *D) { + if (!D) + return true; + if (isa(D) && !D->isImplicit()) + collectDeclIfInRange(D); + // TODO: Handle Lambda/Blocks. + if (!isa(D) && !isa(D)) { + RecursiveASTVisitor::TraverseDecl(D); + return true; + } + const Decl *PreviousDecl = CurrentDecl; + CurrentDecl = D; + RecursiveASTVisitor::TraverseDecl(D); + CurrentDecl = PreviousDecl; + return true; + } + + bool TraverseStmt(Stmt *S) { + if (!S) + return true; + // PseudoObjectExpressions don't have to be parents. + if (isa(S)) + return RecursiveASTVisitor::TraverseStmt(S); + llvm::SaveAndRestore Parent(ParentStmt, CurrentStmt); + llvm::SaveAndRestore Current(CurrentStmt, S); + RecursiveASTVisitor::TraverseStmt(S); + return true; + } + + bool TraversePseudoObjectExpr(PseudoObjectExpr *E) { + // Avoid traversing the getter/setter message sends for property + // expressions. + TraverseStmt(E->getSyntacticForm()); + return true; + } + + bool TraverseObjCPropertyRefExpr(ObjCPropertyRefExpr *E) { + RecursiveASTVisitor::TraverseObjCPropertyRefExpr(E); + // Visit the opaque base manually as it won't be traversed by the + // PseudoObjectExpr. + if (E->isObjectReceiver()) { + if (const auto *Opaque = dyn_cast(E->getBase())) + TraverseStmt(Opaque->getSourceExpr()); + } + return true; + } + + // Statement visitors: + + bool VisitStmt(Stmt *S) { + collectStmtIfInRange(S, S->getSourceRange()); + return true; + } + + // Ignore some implicit expressions. + + bool WalkUpFromMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) { + return true; + } + + bool WalkUpFromCXXThisExpr(CXXThisExpr *E) { + if (E->isImplicit()) + return true; + return RecursiveASTVisitor::WalkUpFromCXXThisExpr(E); + } + + /// Checks if the given statement and its source range has the location + /// of interest or overlaps with the selection range, and adds this node to + /// the set of statements for the slice that's being constructed. + void collectStmtIfInRange(const Stmt *S, SourceRange Range) { + SourceLocation Start = Range.getBegin(); + const auto &SM = Context.getSourceManager(); + bool IsStartMacroArg = false; + if (Start.isMacroID()) { + if (SM.isMacroArgExpansion(Start)) { + Start = SM.getSpellingLoc(Start); + IsStartMacroArg = true; + } else { + Start = SM.getExpansionLoc(Start); + } + } + SourceLocation End = Range.getEnd(); + if (End.isMacroID() && SM.isMacroArgExpansion(End)) { + // Ignore the node that's span across normal code and a macro argument. + if (IsStartMacroArg) + End = SM.getSpellingLoc(End); + } + End = getPreciseTokenLocEnd(End, SM, Context.getLangOpts()); + if (!isPairOfFileLocations(Start, End)) + return; + if (SelectionRange.isValid()) { + if (!areRangesOverlapping(SelectionRange, SourceRange(Start, End), + Context.getSourceManager())) + return; + } else if (!isPointWithin(Location, Start, End, Context.getSourceManager())) + return; + Matches.emplace_back(S, ParentStmt, CurrentDecl, SourceRange(Start, End)); + } + + void collectDeclIfInRange(const Decl *D) { + SourceLocation Start = D->getSourceRange().getBegin(); + SourceLocation End = getPreciseTokenLocEnd( + getLexicalEndLocForDecl(D, Context.getSourceManager(), + Context.getLangOpts()), + Context.getSourceManager(), Context.getLangOpts()); + if (!isPairOfFileLocations(Start, End)) + return; + if (SelectionRange.isValid()) { + if (!areRangesOverlapping(SelectionRange, SourceRange(Start, End), + Context.getSourceManager())) + return; + } else if (!isPointWithin(Location, Start, End, Context.getSourceManager())) + return; + Matches.emplace_back(D, CurrentDecl, SourceRange(Start, End)); + } + + SmallVector Matches; + /// The point of interest. + /// + /// Represents a location at which refactoring should be initiated. + const SourceLocation Location; + const SourceRange SelectionRange; + const ASTContext &Context; + const Decl *CurrentDecl = nullptr; + const Stmt *ParentStmt = nullptr, *CurrentStmt = nullptr; +}; + +} // end anonymous namespace + +ASTSlice::SelectedStmt::SelectedStmt(ASTSlice &Slice, const Stmt *S, + unsigned Index) + : Slice(Slice), S(S), Index(Index) { + assert(S && "No statement given!"); +} + +ASTSlice::SelectedDecl::SelectedDecl(const Decl *D) : D(D) { + assert(D && "No decl given!"); +} + +const Decl *ASTSlice::SelectedStmt::getParentDecl() { + return Slice.parentDeclForIndex(Index); +} + +ASTSlice::ASTSlice(SourceLocation Location, SourceRange SelectionRange, + ASTContext &Context) + : Context(Context), SelectionLocation(Location), + SelectionRange(SelectionRange) { + FileID SearchFile = Context.getSourceManager().getFileID(Location); + ASTSliceFinder Visitor(Location, SelectionRange, Context); + SourceLocation EndLoc; + for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { + if (EndLoc.isValid() && + !Context.getSourceManager().isBeforeInTranslationUnit( + CurrDecl->getLocStart(), EndLoc)) + break; + const SourceLocation FileLoc = + Context.getSourceManager().getSpellingLoc(CurrDecl->getLocStart()); + if (Context.getSourceManager().getFileID(FileLoc) == SearchFile) + Visitor.TraverseDecl(CurrDecl); + // We are only interested in looking at a single top level declaration + // even if our selection range spans across multiple top level declarations. + if (!Visitor.Matches.empty()) { + // Objective-C @implementation declarations might have trailing functions + // that are declared outside of the @implementation, so continue looking + // through them. + if (isa(CurrDecl)) { + EndLoc = CurrDecl->getLocEnd(); + continue; + } + break; + } + } + + for (auto I = Visitor.Matches.rbegin(), E = Visitor.Matches.rend(); I != E; + ++I) + NodeTree.push_back(*I); +} + +bool ASTSlice::isSourceRangeSelected(CharSourceRange Range) const { + SourceRange R = Range.getAsRange(); + if (Range.isTokenRange()) + R.setEnd(getPreciseTokenLocEnd(R.getEnd(), Context.getSourceManager(), + Context.getLangOpts())); + if (SelectionRange.isInvalid()) + return isPointWithin(SelectionLocation, R.getBegin(), R.getEnd(), + Context.getSourceManager()); + return areRangesOverlapping(SelectionRange, R, Context.getSourceManager()); +} + +/// Find the 'if' statement that acts as the start of the +/// 'if'/'else if'/'else' construct. +static std::pair +findIfStmtStart(const IfStmt *If, unsigned Index, + ArrayRef NodeTree) { + if (Index >= NodeTree.size()) + return {If, Index}; // We've reached the top of the tree, return. + const auto *ParentIf = + dyn_cast_or_null(NodeTree[Index + 1].getStmtOrNull()); + // The current 'if' is actually an 'else if' when the next 'if' has an else + // statement that points to the current 'if'. + if (!ParentIf || ParentIf->getElse() != If) + return {If, Index}; + return findIfStmtStart(ParentIf, Index + 1, NodeTree); +} + +/// Find an expression that best represents the given selected expression. +static std::pair +canonicalizeSelectedExpr(const Stmt *S, unsigned Index, + ArrayRef NodeTree) { + const auto Same = std::make_pair(S, Index); + if (Index + 1 >= NodeTree.size()) + return Same; + const Stmt *Parent = NodeTree[Index + 1].getStmtOrNull(); + if (!Parent) + return Same; + + const auto Next = std::make_pair(Parent, Index + 1); + // The entire pseudo expression is selected when just its syntactic + // form is selected. + if (isa(S)) { + if (const auto *POE = dyn_cast_or_null(Parent)) { + if (POE->getSyntacticForm() == S) + return Next; + } + } + // The entire ObjC string literal is selected when just its string + // literal is selected. + if (isa(S) && isa(Parent)) + return Next; + // The entire call should be selected when just the member expression + // that refers to the method is selected. + // FIXME: Check if this can be one of the call arguments. + if (isa(S) && isa(Parent)) + return Next; + // The entire call should be selected when just the callee is selected. + if (const auto *DRE = dyn_cast(S)) { + if (const auto *Call = dyn_cast(Parent)) { + if (Call->getCalleeDecl() == DRE->getDecl()) + return Next; + } + } + return Same; +} + +Optional ASTSlice::nearestSelectedStmt( + llvm::function_ref Predicate) { + for (const auto &Node : llvm::enumerate(NodeTree)) { + const Stmt *S = Node.value().getStmtOrNull(); + if (!S || !Predicate(S)) + continue; + + // Found the match. Perform any additional adjustments. + if (isa(S)) { + auto CanonicalExpr = canonicalizeSelectedExpr(S, Node.index(), NodeTree); + return SelectedStmt(*this, CanonicalExpr.first, CanonicalExpr.second); + } + switch (S->getStmtClass()) { + case Stmt::IfStmtClass: { + // TODO: Fix findIfStmtStart bug with Index where it will return the + // index of the last statement. + auto If = findIfStmtStart(cast(S), Node.index(), NodeTree); + return SelectedStmt(*this, If.first, If.second); + } + default: + break; + } + + return SelectedStmt(*this, S, Node.index()); + } + return None; +} + +Optional +ASTSlice::nearestSelectedStmt(Stmt::StmtClass Class) { + return nearestSelectedStmt( + [Class](const Stmt *S) -> bool { return S->getStmtClass() == Class; }); +} + +const Stmt *ASTSlice::nearestStmt(Stmt::StmtClass Class) { + auto Result = nearestSelectedStmt(Class); + return Result ? Result->getStmt() : nullptr; +} + +Optional ASTSlice::innermostSelectedDecl( + llvm::function_ref Predicate, unsigned Options) { + if (SelectionRange.isValid()) { + if (Options & ASTSlice::InnermostDeclOnly) { + auto Result = getInnermostCompletelySelectedDecl(); + if (!Result) + return None; + if (Predicate(Result->getDecl())) + return Result; + return None; + } + // Traverse down through all of the selected node checking the predicate. + // TODO: Cache the SelectionRangeOverlap kinds properly instead of relying + // on getInnermostCompletelySelectedDecl. + getInnermostCompletelySelectedDecl(); + for (const auto &N : NodeTree) { + const Decl *D = N.getDeclOrNull(); + if (!D) + continue; + if (N.SelectionRangeOverlap != Node::ContainsSelectionRange) + continue; + if (Predicate(D)) + return SelectedDecl(D); + } + return None; + } + for (const auto &Node : llvm::enumerate(NodeTree)) { + const Decl *D = Node.value().getDeclOrNull(); + if (!D) + continue; + if (Predicate(D)) + return SelectedDecl(D); + if (Options & ASTSlice::InnermostDeclOnly) + return None; + } + return None; +} + +Optional +ASTSlice::innermostSelectedDecl(ArrayRef Classes, + unsigned Options) { + assert(!Classes.empty() && "Expected at least one decl kind"); + return innermostSelectedDecl( + [&](const Decl *D) { + for (Decl::Kind Class : Classes) { + if (D->getKind() == Class) + return true; + } + return false; + }, + Options); +} + +/// Compute the SelectionRangeOverlap kinds for matched AST nodes. +/// +/// The overlap kinds are computed only upto the first node that contains the +/// entire selection range. +static void +computeSelectionRangeOverlapKinds(MutableArrayRef NodeTree, + SourceRange SelectionRange, + const SourceManager &SM) { + for (ASTSlice::Node &Node : NodeTree) { + bool HasStart = + isPointWithin(SelectionRange.getBegin(), Node.Range.getBegin(), + Node.Range.getEnd(), SM); + bool HasEnd = isPointWithin(SelectionRange.getEnd(), Node.Range.getBegin(), + Node.Range.getEnd(), SM); + if (HasStart && HasEnd) + Node.SelectionRangeOverlap = ASTSlice::Node::ContainsSelectionRange; + else if (HasStart) + Node.SelectionRangeOverlap = ASTSlice::Node::ContainsSelectionRangeStart; + else if (HasEnd) + Node.SelectionRangeOverlap = ASTSlice::Node::ContainsSelectionRangeEnd; + } +} + +const Stmt *findFirstStatementAfter(const CompoundStmt *CS, SourceLocation Loc, + const SourceManager &SM) { + for (const Stmt *S : CS->body()) { + if (!SM.isBeforeInTranslationUnit(S->getLocStart(), Loc)) + return S; + } + return nullptr; +} + +const Stmt *findLastStatementBefore(const CompoundStmt *CS, SourceLocation Loc, + const Stmt *StartAt, + const SourceManager &SM) { + auto It = std::find(CS->body_begin(), CS->body_end(), StartAt); + assert(It != CS->body_end()); + const Stmt *Last = StartAt; + for (auto E = CS->body_end(); It != E; ++It) { + const Stmt *S = *It; + if (!SM.isBeforeInTranslationUnit(S->getLocStart(), Loc)) + return Last; + Last = S; + } + return Last; +} + +/// Return the source construct that contains the given compound statement. +/// +/// This is useful to find the source construct to which the given compound +/// statement belongs to lexically. For example, if we've selected just the +/// body of an if statement, we ideally want to select the entire if statement. +static std::pair +findCompoundStatementSourceConstruct(const CompoundStmt *CS, + ArrayRef NodeTree) { + for (const auto &Node : llvm::enumerate(NodeTree)) { + const Stmt *S = Node.value().getStmtOrNull(); + if (!S) + continue; + for (const Stmt *Child : S->children()) { + if (Child == CS) { + if (isa(S)) + return {CS, 0}; + if (const auto *If = dyn_cast(S)) + return findIfStmtStart(If, Node.index(), NodeTree); + return {S, Node.index()}; + } + } + } + // This is the outer compound statement. + return {CS, 0}; +} + +/// Return the source construct that contains the given switch case. +static std::pair +findSwitchSourceConstruct(const SwitchCase *Case, + ArrayRef NodeTree) { + for (const auto &Node : llvm::enumerate(NodeTree)) { + const Stmt *S = Node.value().getStmtOrNull(); + if (!S) + continue; + if (isa(S)) + return {S, Node.index()}; + } + return {Case, 0}; +} + +SelectedStmtSet SelectedStmtSet::createFromEntirelySelected(const Stmt *S, + unsigned Index) { + SelectedStmtSet Result; + Result.containsSelectionRange = S; + Result.containsSelectionRangeIndex = Index; + return Result; +} + +Optional +ASTSlice::getInnermostCompletelySelectedDecl() { + assert(SelectionRange.isValid() && "No selection range!"); + if (CachedSelectedInnermostDecl) + return *CachedSelectedInnermostDecl; + computeSelectionRangeOverlapKinds(NodeTree, SelectionRange, + Context.getSourceManager()); + Optional Result; + for (const auto &N : llvm::enumerate(NodeTree)) { + const Decl *D = N.value().getDeclOrNull(); + if (!D) + continue; + if (N.value().SelectionRangeOverlap != Node::ContainsSelectionRange) + continue; + Result = SelectedDecl(D); + break; + } + CachedSelectedInnermostDecl = Result; + return Result; +} + +static bool isCaseSelected(const SwitchStmt *S, SourceRange SelectionRange, + const SourceManager &SM) { + for (const SwitchCase *Case = S->getSwitchCaseList(); Case; + Case = Case->getNextSwitchCase()) { + SourceRange Range(Case->getLocStart(), Case->getColonLoc()); + if (areRangesOverlapping(Range, SelectionRange, SM)) + return true; + } + return false; +} + +Optional ASTSlice::computeSelectedStmtSet() { + if (SelectionRange.isInvalid()) + return None; + computeSelectionRangeOverlapKinds(NodeTree, SelectionRange, + Context.getSourceManager()); + + SelectedStmtSet Result; + for (const auto &N : llvm::enumerate(NodeTree)) { + const auto *S = N.value().getStmtOrNull(); + if (!S) + continue; + switch (N.value().SelectionRangeOverlap) { + case Node::ContainsSelectionRange: { + Result.containsSelectionRange = S; + Result.containsSelectionRangeIndex = N.index(); + + const auto *CS = dyn_cast(Result.containsSelectionRange); + if (!CS) { + // The entire if should be selected when just the 'else if' overlaps + // with the selection range. + if (const auto *If = dyn_cast(Result.containsSelectionRange)) { + auto IfConstruct = findIfStmtStart(If, N.index(), NodeTree); + return SelectedStmtSet::createFromEntirelySelected( + IfConstruct.first, IfConstruct.second); + } + // The entire switch should be selected when just a 'case' overlaps + // with the selection range. + if (const auto *Case = + dyn_cast(Result.containsSelectionRange)) { + auto Switch = findSwitchSourceConstruct( + Case, makeArrayRef(NodeTree).drop_front(N.index() + 1)); + return SelectedStmtSet::createFromEntirelySelected( + Switch.first, N.index() + Switch.second); + } + + auto CanonicalExpr = canonicalizeSelectedExpr(S, N.index(), NodeTree); + Result.containsSelectionRange = CanonicalExpr.first; + Result.containsSelectionRangeIndex = CanonicalExpr.second; + return Result; + } + + bool IsLBraceSelected = + !Context.getSourceManager().isBeforeInTranslationUnit( + CS->getLBracLoc(), SelectionRange.getBegin()); + bool IsRBraceSelected = + Context.getSourceManager().isBeforeInTranslationUnit( + CS->getRBracLoc(), SelectionRange.getEnd()); + + // Return the entire source construct that has the compound statement + // when one of the braces is selected, or when an actual `case` of the + // switch is selected. + auto Construct = findCompoundStatementSourceConstruct( + CS, makeArrayRef(NodeTree).drop_front(N.index() + 1)); + if (Construct.first != CS && + ((IsLBraceSelected || IsRBraceSelected) || + (isa(Construct.first) && + isCaseSelected(cast(Construct.first), SelectionRange, + Context.getSourceManager())))) + return SelectedStmtSet::createFromEntirelySelected( + Construct.first, N.index() + Construct.second); + + // When both braces are selected the entire compound statement is + // considered to be selected. + if (IsLBraceSelected && IsRBraceSelected) + return Result; + if (IsLBraceSelected) + Result.containsSelectionRangeStart = CS->body_front(); + else if (IsRBraceSelected) + Result.containsSelectionRangeEnd = CS->body_back(); + + if (!Result.containsSelectionRangeStart) + Result.containsSelectionRangeStart = findFirstStatementAfter( + CS, SelectionRange.getBegin(), Context.getSourceManager()); + + // Return an empty set when the compound statements os empty or the + // selection range starts after the last statement or the selection range + // doesn't overlap with any actual statements. + if (!Result.containsSelectionRangeStart || + !Context.getSourceManager().isBeforeInTranslationUnit( + Result.containsSelectionRangeStart->getLocStart(), + SelectionRange.getEnd())) + return None; + + if (!Result.containsSelectionRangeEnd) + Result.containsSelectionRangeEnd = findLastStatementBefore( + CS, SelectionRange.getEnd(), Result.containsSelectionRangeStart, + Context.getSourceManager()); + + return Result; + } + case Node::ContainsSelectionRangeStart: + Result.containsSelectionRangeStart = S; + break; + case Node::ContainsSelectionRangeEnd: + Result.containsSelectionRangeEnd = S; + break; + case Node::UnknownOverlap: + break; + } + } + return Result; +} + +Optional ASTSlice::getSelectedStmtSet() { + if (CachedSelectedStmtSet) + return *CachedSelectedStmtSet; + CachedSelectedStmtSet = computeSelectedStmtSet(); + return *CachedSelectedStmtSet; +} + +bool ASTSlice::isContainedInCompoundStmt(unsigned Index) { + assert(Index < NodeTree.size() && "Invalid node index"); + for (unsigned I = Index + 1, E = NodeTree.size(); I != E; ++I) { + const Stmt *S = NodeTree[I].getStmtOrNull(); + if (!S) + continue; + if (isa(S)) + return true; + } + return false; +} + +const Decl *ASTSlice::parentDeclForIndex(unsigned Index) { + return NodeTree[Index].ParentDecl; +} + +const Stmt *ASTSlice::parentStmtForIndex(unsigned Index) { + return NodeTree[Index].ParentStmt; +} diff --git a/clang/lib/Tooling/Refactor/ASTSlice.h b/clang/lib/Tooling/Refactor/ASTSlice.h new file mode 100644 index 0000000000000..f5cb9c6f3afd4 --- /dev/null +++ b/clang/lib/Tooling/Refactor/ASTSlice.h @@ -0,0 +1,182 @@ +//===--- ASTSlice.h - Represents a portion of the AST ---------------------===// +// +// 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_TOOLING_REFACTOR_ASTSLICE_H +#define LLVM_CLANG_LIB_TOOLING_REFACTOR_ASTSLICE_H + +#include "clang/AST/DeclBase.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SmallVector.h" + +namespace clang { + +class NamedDecl; + +namespace tooling { + +/// Represents a set of statements that overlap with the selection range. +struct SelectedStmtSet { + /// The outermost statement that contains the start of the selection range. + const Stmt *containsSelectionRangeStart = nullptr; + + /// The outermost statement that contains the end of the selection range. + const Stmt *containsSelectionRangeEnd = nullptr; + + /// The innermost statement that contains the entire selection range. + const Stmt *containsSelectionRange = nullptr; + + /// The index of the innermost statement that contains the entire selection + /// range. The index points into the NodeTree stored in the \c ASTSlice. + Optional containsSelectionRangeIndex; + + static SelectedStmtSet createFromEntirelySelected(const Stmt *S, + unsigned Index); + + /// Returns true if the compound statement is not fully selected. + bool isCompoundStatementPartiallySelected() const { + assert(containsSelectionRange && "No statement selected"); + return isa(containsSelectionRange) && + (containsSelectionRangeStart || containsSelectionRangeEnd); + } +}; + +/// A portion of the AST that is located around the location and/or source +/// range of interest. +class ASTSlice { +public: + struct Node { + enum SelectionRangeOverlapKind { + UnknownOverlap, + ContainsSelectionRangeStart, + ContainsSelectionRangeEnd, + ContainsSelectionRange + }; + llvm::PointerUnion StmtOrDecl; + const Stmt *ParentStmt; + const Decl *ParentDecl; + SourceRange Range; + SelectionRangeOverlapKind SelectionRangeOverlap = UnknownOverlap; + + const Stmt *getStmtOrNull() const { + return StmtOrDecl.dyn_cast(); + } + + const Decl *getDeclOrNull() const { + return StmtOrDecl.dyn_cast(); + } + + Node(const Stmt *S, const Stmt *ParentStmt, const Decl *ParentDecl, + SourceRange Range) + : StmtOrDecl(S), ParentStmt(ParentStmt), ParentDecl(ParentDecl), + Range(Range) {} + Node(const Decl *D, const Decl *ParentDecl, SourceRange Range) + : StmtOrDecl(D), ParentStmt(nullptr), ParentDecl(ParentDecl), + Range(Range) {} + }; + + /// Represents a statement that overlaps with the selection range/point. + class SelectedStmt { + ASTSlice &Slice; + const Stmt *S; + unsigned Index; + + friend class ASTSlice; + + SelectedStmt(ASTSlice &Slice, const Stmt *S, unsigned Index); + + public: + const Stmt *getStmt() { return S; } + const Decl *getParentDecl(); + }; + + /// Represents a declaration that overlaps with the selection range/point. + class SelectedDecl { + const Decl *D; + + friend class ASTSlice; + + SelectedDecl(const Decl *D); + + public: + const Decl *getDecl() { return D; } + }; + + ASTSlice(SourceLocation Location, SourceRange SelectionRange, + ASTContext &Context); + + /// Returns true if the given source range overlaps with the selection. + bool isSourceRangeSelected(CharSourceRange Range) const; + + enum SelectionSearchOptions { + /// Search with-in the innermost declaration only, including the declaration + /// itself without inspecting any other outer declarations. + InnermostDeclOnly = 1 + }; + + /// Returns the statement that results in true when passed into \p Predicate + /// that's nearest to the location of interest, or \c None if such statement + /// isn't found. + Optional + nearestSelectedStmt(llvm::function_ref Predicate); + + /// Returns the statement of the given class that's nearest to the location + /// of interest, or \c None if such statement isn't found. + Optional nearestSelectedStmt(Stmt::StmtClass Class); + + /// TODO: Remove in favour of nearestStmt that returns \c SelectedStmt + const Stmt *nearestStmt(Stmt::StmtClass Class); + + /// Returns the declaration that overlaps with the selection range, is + /// nearest to the location of interest and that results in true when passed + /// into \p Predicate, or \c None if such declaration isn't found. + Optional + innermostSelectedDecl(llvm::function_ref Predicate, + unsigned Options = 0); + + /// Returns the declaration closest to the location of interest whose decl + /// kind is in \p Classes, or \c None if no such decl can't be found. + Optional innermostSelectedDecl(ArrayRef Classes, + unsigned Options = 0); + + /// Returns the set of statements that overlap with the selection range. + Optional getSelectedStmtSet(); + + /// Returns true if the statement with the given index is contained in a + /// compound statement that overlaps with the selection range. + bool isContainedInCompoundStmt(unsigned Index); + + /// Returns the declaration that contains the statement at the given index. + const Decl *parentDeclForIndex(unsigned Index); + + /// Returns the statement that contains the statement at the given index. + const Stmt *parentStmtForIndex(unsigned Index); + +private: + Optional computeSelectedStmtSet(); + + /// Returns the innermost declaration that contains both the start and the + /// end of the selection range. + Optional getInnermostCompletelySelectedDecl(); + + /// The lowest element is the top of the hierarchy + SmallVector NodeTree; + ASTContext &Context; + SourceLocation SelectionLocation; + SourceRange SelectionRange; + Optional> CachedSelectedStmtSet; + Optional> CachedSelectedInnermostDecl; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTOR_ASTSLICE_H diff --git a/clang/lib/Tooling/Refactor/ASTStateSerialization.cpp b/clang/lib/Tooling/Refactor/ASTStateSerialization.cpp new file mode 100644 index 0000000000000..3fa2e606e1f79 --- /dev/null +++ b/clang/lib/Tooling/Refactor/ASTStateSerialization.cpp @@ -0,0 +1,68 @@ +//===-- ASTStateSerialization.cpp - Persists TU-specific state across TUs -===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringContinuations.h" +#include "clang/AST/RecursiveASTVisitor.h" + +using namespace clang; +using namespace clang::tooling; +using namespace clang::tooling::detail; + +namespace { + +class USRToDeclConverter + : public clang::RecursiveASTVisitor { + llvm::StringMap &USRs; + unsigned NumFound = 0; + +public: + USRToDeclConverter(llvm::StringMap &USRs) : USRs(USRs) {} + + bool isDone() const { return NumFound == USRs.size(); } + + bool VisitNamedDecl(const NamedDecl *D) { + std::string USR = rename::getUSRForDecl(D); + auto It = USRs.find(USR); + if (It == USRs.end() || It->second) + return true; + It->second = D; + ++NumFound; + return NumFound != USRs.size(); + } +}; + +} // end anonymous namespace + +const Decl *PersistentToASTSpecificStateConverter::lookupDecl(StringRef USR) { + auto It = ConvertedDeclRefs.find(USR); + if (It != ConvertedDeclRefs.end()) + return It->second; + // FIXME: If we ever need to convert a PersistentDeclRef through the ASTQuery, + // we have to support conversion without coalesced conversion. + assert(false && "Persistent decl refs should be converted all at once"); + return nullptr; +} + +void PersistentToASTSpecificStateConverter::runCoalescedConversions() { + USRToDeclConverter Converter(ConvertedDeclRefs); + for (Decl *D : Context.getTranslationUnitDecl()->decls()) { + Converter.TraverseDecl(D); + if (Converter.isDone()) + break; + } +} + +FileID +PersistentToASTSpecificStateConverter::convert(const PersistentFileID &Ref) { + FileManager &FM = Context.getSourceManager().getFileManager(); + const FileEntry *Entry = FM.getFile(Ref.Filename); + if (!Entry) + return FileID(); + return Context.getSourceManager().translateFile(Entry); +} diff --git a/clang/lib/Tooling/Refactor/CMakeLists.txt b/clang/lib/Tooling/Refactor/CMakeLists.txt new file mode 100644 index 0000000000000..b65f794de4062 --- /dev/null +++ b/clang/lib/Tooling/Refactor/CMakeLists.txt @@ -0,0 +1,43 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangToolingRefactor + ASTSlice.cpp + ASTStateSerialization.cpp + Extract.cpp + ExtractRepeatedExpressionIntoVariable.cpp + FillInEnumSwitchCases.cpp + FillInMissingMethodStubsFromAbstractClasses.cpp + FillInMissingProtocolStubs.cpp + IfSwitchConversion.cpp + ImplementDeclaredMethods.cpp + IndexerQueries.cpp + LocalizeObjCStringLiteral.cpp + RefactoringActions.cpp + RefactoringActionFinder.cpp + RefactoringOperation.cpp + RefactoringOptions.cpp + RenamingOperation.cpp + RenameIndexedFile.cpp + RenamedSymbol.cpp + SourceLocationUtilities.cpp + StmtUtils.cpp + SymbolOperation.cpp + SymbolOccurrenceFinder.cpp + SymbolName.cpp + SymbolUSRFinder.cpp + TypeUtils.cpp + USRFinder.cpp + + DEPENDS + ClangDriverOptions + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangEdit + clangIndex + clangLex + clangToolingCore + clangRewrite + ) diff --git a/clang/lib/Tooling/Refactor/Extract.cpp b/clang/lib/Tooling/Refactor/Extract.cpp new file mode 100644 index 0000000000000..ac381065709c9 --- /dev/null +++ b/clang/lib/Tooling/Refactor/Extract.cpp @@ -0,0 +1,1916 @@ +//===--- Extract.cpp - ---------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "extract" refactoring operation. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringOperations.h" +#include "SourceLocationUtilities.h" +#include "StmtUtils.h" +#include "TypeUtils.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/MacroInfo.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Refactor/RefactoringOptions.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/Support/Path.h" +#include + +using namespace clang; +using namespace clang::tooling; + +namespace { + +struct CompoundStatementRange { + CompoundStmt::const_body_iterator First, Last; + + const Stmt *getFirst() const { + // We must have selected just the child of the case, since a selection that + // includes the case is treated like a selection of the entire switch. + if (const auto *Case = dyn_cast(*First)) { + if (const Stmt *S = Case->getSubStmt()) + return S; + } + return *First; + } + + const Stmt *getLast() const { return *Last; } + + // TODO: We might not want to iterate over the switch case if we've just + // selected its child. We should switch over to an array of nodes instead of + // an iterator pair instead. + CompoundStmt::const_body_iterator begin() const { return First; } + CompoundStmt::const_body_iterator end() const { return Last + 1; } +}; + +class ExtractOperation : public RefactoringOperation { +public: + struct CandidateInfo { + CandidateInfo(SourceRange Range, StringRef PreInsertedText = "", + const Stmt *AnalyzedStatement = nullptr) + : Range(Range), PreInsertedText(PreInsertedText), + AnalyzedStatement(AnalyzedStatement) {} + + /// The candidate token range, i.e. the end location is the starting + /// location of the last token. + SourceRange Range; + /// The text that should be inserted before the call to the extracted + /// function. + StringRef PreInsertedText; + /// The expression that should be analyzed for captured variables and the + /// return value. + const Stmt *AnalyzedStatement; + }; + + ExtractOperation(const Stmt *S, const Stmt *ParentStmt, + const Decl *FunctionLikeParentDecl, + std::vector Candidates, + Optional ExtractedStmtRange, + Optional FirstCandidateInfo, + bool IsMethodExtraction) + : S(S), ParentStmt(ParentStmt), + FunctionLikeParentDecl(FunctionLikeParentDecl), + Candidates(std::move(Candidates)), + ExtractedStmtRange(ExtractedStmtRange), + IsMethodExtraction(IsMethodExtraction) { + if (FirstCandidateInfo) + CandidateExtractionInfo.push_back(*FirstCandidateInfo); + } + + const Stmt *getTransformedStmt() const override { + if (ExtractedStmtRange) + return ExtractedStmtRange->getFirst(); + return S; + } + + const Stmt *getLastTransformedStmt() const override { + if (ExtractedStmtRange) + return ExtractedStmtRange->getLast(); + return nullptr; + } + + std::vector getRefactoringCandidates() override { + return Candidates; + } + + std::vector getAvailableSubActions() override { + if (isa(FunctionLikeParentDecl) || + isa(FunctionLikeParentDecl)) + return {RefactoringActionType::Extract_Method}; + return {}; + } + + llvm::Expected perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + const Stmt *S, *ParentStmt; + const Decl *FunctionLikeParentDecl; + std::vector Candidates; + /// A set of extraction candidates that correspond to the extracted code. + SmallVector CandidateExtractionInfo; + Optional ExtractedStmtRange; + bool IsMethodExtraction; +}; + +} // end anonymous namespace + +bool isSimpleExpression(const Expr *E) { + switch (E->IgnoreParenCasts()->getStmtClass()) { + case Stmt::DeclRefExprClass: + case Stmt::PredefinedExprClass: + case Stmt::IntegerLiteralClass: + case Stmt::FloatingLiteralClass: + case Stmt::ImaginaryLiteralClass: + case Stmt::CharacterLiteralClass: + case Stmt::StringLiteralClass: + return true; + default: + return false; + } +} + +static bool isMultipleCandidateBinOp(BinaryOperatorKind Op) { + return Op == BO_Add || Op == BO_Sub; +} + +/// Returns the first and the last statements that should be extracted from a +/// compound statement. +Optional getExtractedStatements(const CompoundStmt *CS, + const Stmt *Begin, + const Stmt *End) { + if (CS->body_empty()) + return None; + assert(Begin && End); + CompoundStatementRange Result; + Result.First = std::find(CS->body_begin(), CS->body_end(), Begin); + assert(Result.First != CS->body_end()); + Result.Last = std::find(Result.First, CS->body_end(), End); + assert(Result.Last != CS->body_end()); + return Result; +} + +static RefactoringOperationResult +initiateAnyExtractOperation(ASTSlice &Slice, ASTContext &Context, + SourceLocation Location, SourceRange SelectionRange, + bool CreateOperation, + bool IsMethodExtraction = false) { + auto SelectedStmtsOpt = Slice.getSelectedStmtSet(); + if (!SelectedStmtsOpt) + return None; + SelectedStmtSet Stmts = *SelectedStmtsOpt; + // The selection range is contained entirely within this statement (without + // taking leading/trailing comments and whitespace into account). + const Stmt *Selected = Stmts.containsSelectionRange; + + // We only want to perform the extraction if the selection range is entirely + // within a body of a function or method. + if (!Selected) + return None; + const Decl *ParentDecl = + Slice.parentDeclForIndex(*Stmts.containsSelectionRangeIndex); + + if (!ParentDecl || + (!Stmts.isCompoundStatementPartiallySelected() && + !Slice.isContainedInCompoundStmt(*Stmts.containsSelectionRangeIndex))) + return RefactoringOperationResult( + "the selected expression is not in a function"); + + if (isa(Selected) && isSimpleExpression(cast(Selected))) + return RefactoringOperationResult("the selected expression is too simple"); + if (const auto *PRE = dyn_cast(Selected)) { + if (!PRE->isMessagingGetter()) + return RefactoringOperationResult("property setter can't be extracted"); + } + + RefactoringOperationResult Result; + Result.Initiated = true; + if (!CreateOperation) + return Result; + + Optional ExtractedStmtRange; + + // Check if there are multiple candidates that can be extracted. + std::vector Candidates; + Optional FirstCandidateInfo; + if (const auto *BinOp = dyn_cast(Selected)) { + // Binary '+' and '-' operators allow multiple candidates when the + // selection range starts after the LHS expression but still overlaps + // with the RHS. + if (isMultipleCandidateBinOp(BinOp->getOpcode()) && + (!Stmts.containsSelectionRangeStart || + getPreciseTokenLocEnd( + BinOp->getLHS()->getLocEnd(), Context.getSourceManager(), + Context.getLangOpts()) == SelectionRange.getBegin()) && + Stmts.containsSelectionRangeEnd) { + SourceRange FirstCandidateRange = + SourceRange(SelectionRange.getBegin(), BinOp->getLocEnd()); + if (FirstCandidateRange.getEnd().isMacroID()) + FirstCandidateRange.setEnd(Context.getSourceManager().getExpansionLoc( + FirstCandidateRange.getEnd())); + FirstCandidateInfo = ExtractOperation::CandidateInfo( + FirstCandidateRange, "+ ", + /*AnalyzedStatement=*/BinOp->getRHS()); + Candidates.push_back( + Lexer::getSourceText( + CharSourceRange::getTokenRange(FirstCandidateRange), + Context.getSourceManager(), Context.getLangOpts()) + .trim()); + Candidates.push_back(Lexer::getSourceText( + CharSourceRange::getTokenRange(BinOp->getSourceRange()), + Context.getSourceManager(), Context.getLangOpts())); + } + } else if (const auto *CS = dyn_cast(Selected)) { + // We want to extract some child statements from a compound statement unless + // we've selected the entire compound statement including the opening and + // closing brace. + if (Stmts.containsSelectionRangeStart) + ExtractedStmtRange = + getExtractedStatements(CS, Stmts.containsSelectionRangeStart, + Stmts.containsSelectionRangeEnd); + } + + auto Operation = llvm::make_unique( + Selected, Slice.parentStmtForIndex(*Stmts.containsSelectionRangeIndex), + ParentDecl, std::move(Candidates), ExtractedStmtRange, FirstCandidateInfo, + IsMethodExtraction); + auto &CandidateExtractionInfo = Operation->CandidateExtractionInfo; + SourceRange Range; + if (ExtractedStmtRange) + Range = SourceRange(ExtractedStmtRange->getFirst()->getLocStart(), + ExtractedStmtRange->getLast()->getLocEnd()); + else + Range = Selected->getSourceRange(); + bool IsBeginMacroArgument = false; + if (Range.getBegin().isMacroID()) { + if (Context.getSourceManager().isMacroArgExpansion(Range.getBegin())) { + Range.setBegin( + Context.getSourceManager().getSpellingLoc(Range.getBegin())); + IsBeginMacroArgument = true; + } else { + Range.setBegin( + Context.getSourceManager().getExpansionLoc(Range.getBegin())); + } + } + if (Range.getEnd().isMacroID()) { + if (IsBeginMacroArgument && + Context.getSourceManager().isMacroArgExpansion(Range.getEnd())) + Range.setEnd(Context.getSourceManager().getSpellingLoc(Range.getEnd())); + else + Range.setEnd( + Context.getSourceManager().getExpansionRange(Range.getEnd()).second); + } + CandidateExtractionInfo.push_back(ExtractOperation::CandidateInfo(Range)); + Result.RefactoringOp = std::move(Operation); + return Result; +} + +RefactoringOperationResult clang::tooling::initiateExtractOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + return initiateAnyExtractOperation(Slice, Context, Location, SelectionRange, + CreateOperation); +} + +RefactoringOperationResult clang::tooling::initiateExtractMethodOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + // TODO: Verify that method extraction is actually possible. + return initiateAnyExtractOperation(Slice, Context, Location, SelectionRange, + CreateOperation, + /*IsMethodExtraction=*/true); +} + +using ReferencedEntity = + llvm::PointerUnion; + +/// Iterate over the entities (variables/instance variables) that are directly +/// referenced by the given expression \p E. +/// +/// Note: Objective-C ivars are always captured via 'self'. +static void findEntitiesDirectlyReferencedInExpr( + const Expr *E, + llvm::function_ref Handler) { + E = E->IgnoreParenCasts(); + if (const auto *DRE = dyn_cast(E)) + return Handler(DRE); + + if (const auto *ME = dyn_cast(E)) { + if (isa(ME->getBase()->IgnoreParenCasts())) { + if (const auto *FD = dyn_cast_or_null(ME->getMemberDecl())) + Handler(FD); + return; + } + if (const auto *MD = ME->getMemberDecl()) { + if (isa(MD) || isa(MD)) + findEntitiesDirectlyReferencedInExpr(ME->getBase(), Handler); + } + return; + } + + if (const auto *CO = dyn_cast(E)) { + findEntitiesDirectlyReferencedInExpr(CO->getTrueExpr(), Handler); + findEntitiesDirectlyReferencedInExpr(CO->getFalseExpr(), Handler); + return; + } + + if (const auto *BO = dyn_cast(E)) { + if (BO->getOpcode() == BO_Comma) + return findEntitiesDirectlyReferencedInExpr(BO->getRHS(), Handler); + } +} + +template +static void +findMatchingParameters(Matcher &ParameterMatcher, const Stmt *S, + ASTContext &Context, StringRef Node, + llvm::function_ref Handler) { + using namespace clang::ast_matchers; + auto Matches = match(findAll(callExpr(ParameterMatcher)), *S, Context); + for (const auto &Match : Matches) + Handler(Match.template getNodeAs(Node)); + Matches = match(findAll(cxxConstructExpr(ParameterMatcher)), *S, Context); + for (const auto &Match : Matches) + Handler(Match.template getNodeAs(Node)); +} + +static void +findUseOfConstThis(const Stmt *S, ASTContext &Context, + llvm::function_ref Handler) { + using namespace clang::ast_matchers; + // Check the receiver in method call and member operator calls. + auto This = cxxThisExpr().bind("this"); + auto ThisReceiver = ignoringParenCasts( + anyOf(This, unaryOperator(hasOperatorName("*"), + hasUnaryOperand(ignoringParenCasts(This))))); + auto ConstMethodCallee = callee(cxxMethodDecl(isConst())); + auto Matches = match( + findAll(expr(anyOf(cxxMemberCallExpr(ConstMethodCallee, on(ThisReceiver)), + cxxOperatorCallExpr(ConstMethodCallee, + hasArgument(0, ThisReceiver))))), + *S, Context); + for (const auto &Match : Matches) + Handler(Match.getNodeAs("this")); + // Check parameters in calls. + auto ConstPointee = pointee(qualType(isConstQualified())); + auto RefParameter = forEachArgumentWithParam( + ThisReceiver, + parmVarDecl(hasType(qualType(referenceType(ConstPointee))))); + findMatchingParameters(RefParameter, S, Context, "this", Handler); + auto PtrParameter = forEachArgumentWithParam( + ignoringParenCasts(This), + parmVarDecl(hasType(qualType(pointerType(ConstPointee))))); + findMatchingParameters(PtrParameter, S, Context, "this", Handler); +} + +static void findArgumentsPassedByNonConstReference( + const Stmt *S, ASTContext &Context, + llvm::function_ref Handler) { + using namespace clang::ast_matchers; + // Check the receiver in method call and member operator calls. + auto NonPointerReceiver = + expr(unless(hasType(qualType(pointerType())))).bind("arg"); + auto NonConstMethodCallee = callee(cxxMethodDecl(unless(isConst()))); + auto Matches = + match(findAll(expr(anyOf( + cxxMemberCallExpr(NonConstMethodCallee, on(NonPointerReceiver)), + cxxOperatorCallExpr(NonConstMethodCallee, + hasArgument(0, NonPointerReceiver))))), + *S, Context); + for (const auto &Match : Matches) + Handler(Match.getNodeAs("arg")); + // Check parameters in calls. + auto RefParameter = forEachArgumentWithParam( + expr().bind("arg"), parmVarDecl(hasType(qualType(referenceType(unless( + pointee(qualType(isConstQualified())))))))); + Matches = match(findAll(callExpr(RefParameter)), *S, Context); + for (const auto &Match : Matches) + Handler(Match.getNodeAs("arg")); + Matches = match(findAll(cxxConstructExpr(RefParameter)), *S, Context); + for (const auto &Match : Matches) + Handler(Match.getNodeAs("arg")); +} + +static void findAddressExpressionsPassedByConstPointer( + const Stmt *S, ASTContext &Context, + llvm::function_ref Handler) { + using namespace clang::ast_matchers; + auto ConstPtrParameter = forEachArgumentWithParam( + ignoringParenImpCasts(unaryOperator(hasOperatorName("&")).bind("arg")), + parmVarDecl(hasType( + qualType(pointerType(pointee(qualType(isConstQualified()))))))); + auto Matches = match(findAll(callExpr(ConstPtrParameter)), *S, Context); + for (const auto &Match : Matches) + Handler(Match.getNodeAs("arg")); + Matches = match(findAll(cxxConstructExpr(ConstPtrParameter)), *S, Context); + for (const auto &Match : Matches) + Handler(Match.getNodeAs("arg")); +} + +static bool isImplicitInitializer(const VarDecl *VD) { + assert(VD->hasInit()); + const auto *E = VD->getInit(); + if (isa(E)) + return false; + const auto *Construct = dyn_cast(E); + if (!Construct) + return E->getLocStart() == VD->getLocation(); + return Construct->getParenOrBraceRange().isInvalid(); +} + +static const Expr *getInitializerExprWithLexicalRange(const Expr *E) { + if (const auto *EWC = dyn_cast(E)) { + if (const auto *Construct = dyn_cast(EWC->getSubExpr())) { + if (Construct->getNumArgs() == 1) { + if (const auto *ME = + dyn_cast(Construct->getArg(0))) + return ME; + } + } + } + return E; +} + +namespace { + +class ExtractedCodeVisitor : public RecursiveASTVisitor { + int DefineOrdering = 0; + +public: + struct CaptureInfo { + bool IsMutated = false; + bool IsDefined = false; + bool IsAddressTaken = false; + bool IsConstAddressTaken = false; + bool IsFieldCapturedWithThis = false; + bool IsUsed = false; + int DefineOrderingPriority = 0; + + bool isPassedByRefOrPtr() const { + return IsMutated || IsAddressTaken || IsConstAddressTaken; + } + bool isRefOrPtrConst() const { + return IsConstAddressTaken && !IsMutated && !IsAddressTaken; + } + }; + + const ImplicitParamDecl *SelfDecl; + + ExtractedCodeVisitor(const ImplicitParamDecl *SelfDecl) + : SelfDecl(SelfDecl) {} + + bool HasReturnInExtracted = false; + + CaptureInfo &captureVariable(const VarDecl *VD) { + CaptureInfo &Result = CapturedVariables[VD]; + Result.IsUsed = true; + return Result; + } + + CaptureInfo &captureField(const FieldDecl *FD) { return CapturedFields[FD]; } + + bool VisitDeclRefExpr(const DeclRefExpr *E) { + const VarDecl *VD = dyn_cast(E->getDecl()); + if (!VD) + return true; + if (VD == SelfDecl) { + CaptureSelf = true; + SelfType = VD->getType(); + return true; + } + if (!VD->isLocalVarDeclOrParm()) + return true; + captureVariable(VD); + return true; + } + + void captureThisWithoutConstConcerns(const CXXThisExpr *E) { + CaptureThis = true; + ThisRecordType = E->getType()->getPointeeType(); + } + + bool VisitCXXThisExpr(const CXXThisExpr *E) { + captureThisWithoutConstConcerns(E); + ThisUsesWithUnknownConstness.insert(E); + return true; + } + + bool TraverseMemberExpr(MemberExpr *E) { + const auto *Base = dyn_cast(E->getBase()->IgnoreParenCasts()); + if (!Base) + return RecursiveASTVisitor::TraverseMemberExpr(E); + const FieldDecl *FD = dyn_cast_or_null(E->getMemberDecl()); + if (!FD) + return RecursiveASTVisitor::TraverseMemberExpr(E); + CaptureInfo &Info = captureField(FD); + // Don't capture the implicit 'this' for private fields as we don't want to + // capture this if we only use the private fields. + if (FD->getAccess() == AS_public || !Base->isImplicit()) { + Info.IsFieldCapturedWithThis = true; + // The member might have an effect on the constness of the captured 'this' + // but this is checked via mutation/const tracking for the field itself, + // so we just capture 'this' without worrying about checking if it's used + // in a 'const' manner here. + captureThisWithoutConstConcerns(Base); + } + return true; + } + + void captureSuper(QualType T) { + if (CaptureSuper) + return; + SuperType = T; + CaptureSuper = true; + } + + bool TraverseObjCPropertyRefExpr(ObjCPropertyRefExpr *E) { + if (E->isSuperReceiver()) + captureSuper(E->getSuperReceiverType()); + // Base might be an opaque expression, so we have to visit it manually as + // we don't necessarily visit the setter/getter message sends if just the + // property was selected. + if (E->isObjectReceiver()) { + if (const auto *OVE = dyn_cast(E->getBase())) + TraverseStmt(OVE->getSourceExpr()); + } + return RecursiveASTVisitor::TraverseObjCPropertyRefExpr(E); + } + + bool TraverseBinAssign(BinaryOperator *S) { + // RHS might be an opaque expression, if this is a property assignment. We + // have to visit it manually as we don't necessarily visit the setter/getter + // message sends if just the property was selected. + if (const auto *OVE = dyn_cast(S->getRHS())) + TraverseStmt(OVE->getSourceExpr()); + return RecursiveASTVisitor::TraverseBinAssign(S); + } + + void findCapturedVariableOrFieldsInExpression( + const Expr *E, llvm::function_ref Handler) { + findEntitiesDirectlyReferencedInExpr( + E, [&Handler, this](const ReferencedEntity &Entity) { + if (const auto *DRE = Entity.dyn_cast()) { + const VarDecl *VD = dyn_cast(DRE->getDecl()); + if (!VD || !VD->isLocalVarDeclOrParm() || VD->isImplicit()) + return; + return Handler(captureVariable(VD)); + } + return Handler(captureField(Entity.get())); + }); + } + + void + markDirectlyReferencedVariableOrFieldInExpressionAsMutated(const Expr *E) { + findCapturedVariableOrFieldsInExpression( + E, [](CaptureInfo &Capture) { Capture.IsMutated = true; }); + } + + bool VisitBinaryOperator(const BinaryOperator *E) { + if (E->isAssignmentOp()) + markDirectlyReferencedVariableOrFieldInExpressionAsMutated(E->getLHS()); + return true; + } + + bool VisitUnaryPreInc(const UnaryOperator *E) { + markDirectlyReferencedVariableOrFieldInExpressionAsMutated(E->getSubExpr()); + return true; + } + + bool VisitUnaryPostInc(const UnaryOperator *E) { + markDirectlyReferencedVariableOrFieldInExpressionAsMutated(E->getSubExpr()); + return true; + } + + bool VisitUnaryPreDec(const UnaryOperator *E) { + markDirectlyReferencedVariableOrFieldInExpressionAsMutated(E->getSubExpr()); + return true; + } + + bool VisitUnaryPostDec(const UnaryOperator *E) { + markDirectlyReferencedVariableOrFieldInExpressionAsMutated(E->getSubExpr()); + return true; + } + + /// If the given expression refers to a local/instance variable or a + /// a member of such variable that variable is marked as captured by + /// reference. + void captureVariableOrFieldInExpressionByReference(const Expr *E) { + findCapturedVariableOrFieldsInExpression( + E, [](CaptureInfo &Capture) { Capture.IsAddressTaken = true; }); + } + + bool VisitUnaryAddrOf(const UnaryOperator *E) { + // Capture the entity with 'const' reference/pointer when its address is + // passed into a function that takes a 'const' pointer and no other + // mutations or non-const address/reference acquisitions occur. + if (AddressExpressionsPassedToConstPointerParameter.count(E)) + findCapturedVariableOrFieldsInExpression( + E->getSubExpr(), + [](CaptureInfo &Capture) { Capture.IsConstAddressTaken = true; }); + else + captureVariableOrFieldInExpressionByReference(E->getSubExpr()); + return true; + } + + bool VisitObjCMessageExpr(const ObjCMessageExpr *E) { + if (E->getSuperLoc().isValid()) + captureSuper(E->getSuperType()); + const ObjCMethodDecl *MD = E->getMethodDecl(); + if (!MD) + return true; + for (const auto &Param : llvm::enumerate(MD->parameters())) { + QualType T = Param.value()->getType(); + if (Param.index() >= E->getNumArgs()) + break; + if (T->isReferenceType() && !T->getPointeeType().isConstQualified()) + captureVariableOrFieldInExpressionByReference(E->getArg(Param.index())); + if (T->isPointerType() && T->getPointeeType().isConstQualified()) { + // Check if this is an '&' passed into a const pointer parameter. + const Expr *Arg = E->getArg(Param.index()); + if (const auto *Op = + dyn_cast(Arg->IgnoreParenImpCasts())) { + if (Op->getOpcode() == UO_AddrOf) + AddressExpressionsPassedToConstPointerParameter.insert(Op); + } + } + } + return true; + } + + bool VisitVarDecl(const VarDecl *VD) { + // Don't capture using the captureVariable method as we don't want to mark + // the declaration as a 'use'. This allows us to avoid passing in variables + // that are defined in extracted code, used afterwards, but never actually + // used in the extracted code. + CaptureInfo &Capture = CapturedVariables[VD]; + Capture.IsDefined = true; + Capture.DefineOrderingPriority = ++DefineOrdering; + // Ensure the capture is marked as 'used' when the variable declaration has + // an explicit initialization expression. This allows us to pass it by + // reference when it's defined in extracted code, used afterwards, but never + // actually used in the extracted code. The main reason why we want to try + // to keep this initialization in the extracted code is to preserve + // semantics as the initialization expression might have side-effects. + if (!Capture.IsUsed && VD->hasInit() && !isImplicitInitializer(VD)) + Capture.IsUsed = true; + QualType T = VD->getType(); + if (T->isReferenceType() && !T->getPointeeType().isConstQualified() && + VD->hasInit()) + captureVariableOrFieldInExpressionByReference(VD->getInit()); + return true; + } + + bool VisitReturnStmt(const ReturnStmt *S) { + HasReturnInExtracted = true; + return true; + } + + void InspectExtractedStmt(Stmt *S, ASTContext &Context) { + findAddressExpressionsPassedByConstPointer( + S, Context, [this](const UnaryOperator *Arg) { + AddressExpressionsPassedToConstPointerParameter.insert(Arg); + }); + TraverseStmt(S); + findArgumentsPassedByNonConstReference(S, Context, [this](const Expr *Arg) { + captureVariableOrFieldInExpressionByReference(Arg); + }); + if (CaptureThis && !ThisUsesWithUnknownConstness.empty()) { + // Compare the definite 'const' uses of 'this' to all the seen uses + // (except for the known field uses). + findUseOfConstThis(S, Context, [this](const CXXThisExpr *Arg) { + ThisUsesWithUnknownConstness.erase(Arg); + }); + IsThisConstForNonCapturedFieldUses = ThisUsesWithUnknownConstness.empty(); + } + } + + llvm::DenseMap CapturedVariables; + llvm::DenseMap CapturedFields; + llvm::SmallPtrSet + AddressExpressionsPassedToConstPointerParameter; + llvm::SmallPtrSet ThisUsesWithUnknownConstness; + bool CaptureThis = false; + bool IsThisConstForNonCapturedFieldUses = true; + QualType ThisRecordType; + bool CaptureSelf = false, CaptureSuper = false; + QualType SelfType, SuperType; +}; + +/// Traverses the extracted code and finds the uses of captured variables +/// that are passed into the extracted function using a pointer. +class VariableDefinedInExtractedCodeUseAfterExtractionFinder + : public RecursiveASTVisitor< + VariableDefinedInExtractedCodeUseAfterExtractionFinder> { + bool IsAfterExtracted = false; + +public: + const Stmt *LastExtractedStmt; + const llvm::SmallPtrSetImpl &VariablesDefinedInExtractedCode; + llvm::SmallPtrSet VariablesUsedAfterExtraction; + + VariableDefinedInExtractedCodeUseAfterExtractionFinder( + const Stmt *LastExtractedStmt, + const llvm::SmallPtrSetImpl + &VariablesDefinedInExtractedCode) + : LastExtractedStmt(LastExtractedStmt), + VariablesDefinedInExtractedCode(VariablesDefinedInExtractedCode) {} + + bool TraverseStmt(Stmt *S) { + RecursiveASTVisitor::TraverseStmt(S); + if (S == LastExtractedStmt) + IsAfterExtracted = true; + return true; + } + + bool VisitDeclRefExpr(const DeclRefExpr *E) { + if (!IsAfterExtracted) + return true; + const VarDecl *VD = dyn_cast(E->getDecl()); + if (!VD) + return true; + if (VariablesDefinedInExtractedCode.count(VD)) + VariablesUsedAfterExtraction.insert(VD); + return true; + } +}; + +class PossibleShadowingVariableFinder + : public RecursiveASTVisitor { + const VarDecl *TargetVD; + + PossibleShadowingVariableFinder(const VarDecl *TargetVD) + : TargetVD(TargetVD) {} + +public: + bool VisitVarDecl(const VarDecl *VD) { + if (VD == TargetVD || VD->getName() != TargetVD->getName()) + return true; + return false; + } + + /// Returns true if the given statement \p S has a variable declaration whose + /// name is identical to the given variable declaration \p VD. + static bool hasShadowingVar(const VarDecl *VD, const Stmt *S) { + return !PossibleShadowingVariableFinder(VD).TraverseStmt( + const_cast(S)); + } +}; + +/// Traverses the extracted code and rewrites the 'return' statements to ensure +/// that they now return some value. +class ReturnRewriter : public RecursiveASTVisitor { + Rewriter &SourceRewriter; + std::string Text; + +public: + ReturnRewriter(Rewriter &SourceRewriter, StringRef Text) + : SourceRewriter(SourceRewriter), Text(std::string(" ") + Text.str()) {} + + bool VisitReturnStmt(const ReturnStmt *S) { + SourceRewriter.InsertText( + getPreciseTokenLocEnd(S->getLocEnd(), SourceRewriter.getSourceMgr(), + SourceRewriter.getLangOpts()), + Text); + return true; + } +}; + +/// Prints the given initializer expression using the original source code if +/// possible. +static void printInitializerExpressionUsingOriginalSyntax( + const VarDecl *VD, const Expr *E, bool IsDeclaration, const ASTContext &Ctx, + llvm::raw_ostream &OS, const PrintingPolicy &PP) { + E = getInitializerExprWithLexicalRange(E); + SourceRange Range = E->getSourceRange(); + bool UseEquals = true; + bool UseTypeName = false; + if (const auto *Construct = dyn_cast(E)) { + SourceRange SubRange = Construct->getParenOrBraceRange(); + if (SubRange.isValid()) { + UseEquals = false; + UseTypeName = true; + Range = SubRange; + } + } + if (Range.getBegin().isMacroID()) + Range.setBegin(Ctx.getSourceManager().getExpansionLoc(Range.getBegin())); + if (Range.getEnd().isMacroID()) + Range.setEnd(Ctx.getSourceManager().getExpansionLoc(Range.getEnd())); + bool IsInvalid = false; + StringRef Text = Lexer::getSourceText(CharSourceRange::getTokenRange(Range), + Ctx.getSourceManager(), + Ctx.getLangOpts(), &IsInvalid); + if (IsDeclaration && UseEquals) + OS << " = "; + else if (!IsDeclaration && UseTypeName) + VD->getType().print(OS, PP); + if (IsInvalid) + E->printPretty(OS, nullptr, PP); + else + OS << Text; +}; + +/// Traverses the extracted code and rewrites the declaration statements that +/// declare variables that are used after the extracted code. +class DefinedInExtractedCodeDeclStmtRewriter + : public RecursiveASTVisitor { +public: + Rewriter &SourceRewriter; + const llvm::SmallPtrSetImpl &VariablesUsedAfterExtraction; + const PrintingPolicy &PP; + + DefinedInExtractedCodeDeclStmtRewriter( + Rewriter &SourceRewriter, const llvm::SmallPtrSetImpl + &VariablesUsedAfterExtraction, + const PrintingPolicy &PP) + : SourceRewriter(SourceRewriter), + VariablesUsedAfterExtraction(VariablesUsedAfterExtraction), PP(PP) {} + + /// When a declaration statement declares variables that are all used + /// after extraction, we can rewrite it completely into a set of assignments + /// while still preserving the original initializer expressions when we + /// can. + void rewriteAllVariableDeclarationsToAssignments(const DeclStmt *S) { + SourceLocation StartLoc = S->getLocStart(); + for (const Decl *D : S->decls()) { + const auto *VD = dyn_cast(D); + if (!VD || !VariablesUsedAfterExtraction.count(VD)) + continue; + if (!VD->hasInit() || isImplicitInitializer(VD)) { + // Remove the variable declarations without explicit initializers. + // This can affect the semantics of the program if the implicit + // initialization expression has side effects. + SourceRange Range = SourceRange( + StartLoc, S->isSingleDecl() ? S->getLocEnd() : VD->getLocation()); + SourceRewriter.RemoveText(Range); + continue; + } + std::string Str; + llvm::raw_string_ostream OS(Str); + if (StartLoc != S->getLocStart()) + OS << "; "; + const ASTContext &Ctx = D->getASTContext(); + // Dereference the variable unless the source uses C++. + if (!Ctx.getLangOpts().CPlusPlus) + OS << '*'; + OS << VD->getName() << " = "; + const Expr *Init = getInitializerExprWithLexicalRange(VD->getInit()); + SourceLocation End = Init->getLocStart(); + if (const auto *Construct = dyn_cast(Init)) { + SourceRange SubRange = Construct->getParenOrBraceRange(); + if (SubRange.isValid()) { + End = SubRange.getBegin(); + VD->getType().print(OS, PP); + } + } + if (End.isMacroID()) + End = Ctx.getSourceManager().getExpansionLoc(End); + auto Range = CharSourceRange::getCharRange(StartLoc, End); + SourceRewriter.ReplaceText(StartLoc, SourceRewriter.getRangeSize(Range), + OS.str()); + StartLoc = getPreciseTokenLocEnd(D->getLocEnd(), Ctx.getSourceManager(), + Ctx.getLangOpts()); + } + } + + /// When a declaration statement has variables that are both used after + /// extraction and not used after extraction, we create new declaration + /// statements that declare the unused variables, while creating assignment + /// statements that "initialize" the variables that are used after the + /// extraction. This way we can preserve the order of + /// initialization/assignment from the original declaration statement. + void rewriteMixedDeclarations(const DeclStmt *S) { + // Completely rewrite the declaration statement. + std::string Str; + llvm::raw_string_ostream OS(Str); + for (const Decl *D : S->decls()) { + const ASTContext &Ctx = D->getASTContext(); + const VarDecl *VD = dyn_cast(D); + bool IsLast = D == S->decl_end()[-1]; + if (!VD) { + OS << "<>;"; + continue; + } + + auto PrintInit = [&](bool IsDeclaration) { + printInitializerExpressionUsingOriginalSyntax( + VD, VD->getInit(), IsDeclaration, Ctx, OS, PP); + }; + if (!VariablesUsedAfterExtraction.count(VD)) { + VD->getType().print(OS, PP); + OS << " " << VD->getName(); + if (VD->hasInit() && !isImplicitInitializer(VD)) + PrintInit(/*IsDeclaration=*/true); + OS << ";"; + if (!IsLast) + OS << ' '; + continue; + } + if (VD->hasInit() && !isImplicitInitializer(VD)) { + // Dereference the variable unless the source uses C++. + if (!Ctx.getLangOpts().CPlusPlus) + OS << '*'; + OS << VD->getName() << " = "; + PrintInit(/*IsDeclaration=*/false); + OS << ";"; + if (!IsLast) + OS << ' '; + } + } + SourceRewriter.ReplaceText(S->getSourceRange(), OS.str()); + } + + bool VisitDeclStmt(const DeclStmt *S) { + bool AreAllUsed = true; + bool AreNoneUsed = true; + for (const Decl *D : S->decls()) { + const auto *VD = dyn_cast(D); + if (!VD || !VariablesUsedAfterExtraction.count(VD)) { + AreAllUsed = false; + continue; + } + AreNoneUsed = false; + // Exit early when both flags were set in the loop. + if (!AreAllUsed) + break; + } + if (AreNoneUsed) + return true; + + if (AreAllUsed) + rewriteAllVariableDeclarationsToAssignments(S); + else + rewriteMixedDeclarations(S); + return true; + } +}; + +/// Takes care of pseudo object expressions and Objective-C properties to avoid +/// duplicate rewrites and missing rewrites. +template +class PseudoObjectRewriter : public RecursiveASTVisitor { + typedef RecursiveASTVisitor Base; + +public: + bool TraversePseudoObjectExpr(PseudoObjectExpr *E) { + return Base::TraverseStmt(E->getSyntacticForm()); + } + + bool TraverseObjCPropertyRefExpr(ObjCPropertyRefExpr *E) { + // Base might be an opaque expression, so we have to visit it manually as + // we don't necessarily visit the setter/getter message sends if just the + // property was selected. + if (E->isObjectReceiver()) { + if (const auto *OVE = dyn_cast(E->getBase())) + Base::TraverseStmt(OVE->getSourceExpr()); + } + return Base::TraverseObjCPropertyRefExpr(E); + } + + bool TraverseBinAssign(BinaryOperator *S) { + // RHS might be an opaque expression, if this is a property assignment. We + // have to visit it manually as we don't necessarily visit the setter/getter + // message sends if just the property was selected. + if (const auto *OVE = dyn_cast(S->getRHS())) + Base::TraverseStmt(OVE->getSourceExpr()); + return Base::TraverseBinAssign(S); + } +}; + +/// Traverses the extracted code and rewrites the uses of captured variables +/// that are passed into the extracted function using a pointer. +class CapturedVariableCaptureByPointerRewriter + : public PseudoObjectRewriter { +public: + const VarDecl *TargetVD; + Rewriter &SourceRewriter; + + CapturedVariableCaptureByPointerRewriter(const VarDecl *VD, + Rewriter &SourceRewriter) + : TargetVD(VD), SourceRewriter(SourceRewriter) {} + + bool isTargetDeclRefExpr(const Expr *E) { + const auto *DRE = dyn_cast(E); + if (!DRE) + return false; + return dyn_cast(DRE->getDecl()) == TargetVD; + } + + void dereferenceTargetVar(const Expr *E, bool WrapInParens = false) { + SourceRewriter.InsertTextBefore(E->getLocStart(), + WrapInParens ? "(*" : "*"); + if (WrapInParens) + SourceRewriter.InsertTextAfterToken(E->getLocEnd(), ")"); + } + + bool VisitDeclRefExpr(const DeclRefExpr *E) { + const VarDecl *VD = dyn_cast(E->getDecl()); + if (VD != TargetVD) + return true; + dereferenceTargetVar(E); + return true; + } + + bool TraverseUnaryAddrOf(UnaryOperator *E) { + if (const auto *DRE = + dyn_cast(E->getSubExpr()->IgnoreParenCasts())) { + const VarDecl *VD = dyn_cast(DRE->getDecl()); + if (VD == TargetVD) { + // Remove the '&' as the variable is now a pointer. + SourceRewriter.RemoveText( + CharSourceRange::getTokenRange(E->getLocStart(), E->getLocStart())); + return true; + } + } + return RecursiveASTVisitor::TraverseUnaryAddrOf(E); + } + + bool TraverseMemberExpr(MemberExpr *E) { + if (!E->isArrow()) { + if (const auto *DRE = + dyn_cast(E->getBase()->IgnoreParenCasts())) { + const VarDecl *VD = dyn_cast(DRE->getDecl()); + if (VD == TargetVD) { + // Replace '.' with '->'. + SourceRewriter.ReplaceText(E->getOperatorLoc(), 1, "->"); + return true; + } + } + } else if (isTargetDeclRefExpr(E->getBase()->IgnoreImpCasts())) { + // Ensure the variable is wrapped in parenthesis when it's the base of + // '->' operator. + dereferenceTargetVar(E->getBase(), /*WrapInParens=*/true); + return true; + } + return RecursiveASTVisitor::TraverseMemberExpr(E); + } +}; + +/// Traverses the extracted code and rewrites the uses of 'this' that can be +/// rewritten as references. +class CapturedThisReferenceRewriter + : public PseudoObjectRewriter { +public: + Rewriter &SourceRewriter; + llvm::SmallPtrSet RewrittenExpressions; + + CapturedThisReferenceRewriter(Rewriter &SourceRewriter) + : SourceRewriter(SourceRewriter) {} + + void rewriteThis(const CXXThisExpr *E) { + RewrittenExpressions.insert(E); + if (!E->isImplicit()) + SourceRewriter.ReplaceText(E->getLocStart(), 4, "object"); + else + SourceRewriter.InsertText(E->getLocStart(), "object"); + } + + bool VisitMemberExpr(const MemberExpr *E) { + const auto *This = + dyn_cast(E->getBase()->IgnoreParenImpCasts()); + if (This) { + rewriteThis(This); + if (!This->isImplicit() && E->isArrow()) + SourceRewriter.ReplaceText(E->getOperatorLoc(), 2, "."); + else + SourceRewriter.InsertText(E->getBase()->getLocEnd(), "."); + } + return true; + } +}; + +/// Traverses the extracted code and rewrites the uses of 'this' into '&object'. +class CapturedThisPointerRewriter + : public PseudoObjectRewriter { +public: + Rewriter &SourceRewriter; + const llvm::SmallPtrSetImpl &RewrittenExpressions; + + CapturedThisPointerRewriter( + Rewriter &SourceRewriter, + const llvm::SmallPtrSetImpl &RewrittenExpressions) + : SourceRewriter(SourceRewriter), + RewrittenExpressions(RewrittenExpressions) {} + + void replace(const CXXThisExpr *E, StringRef Text) { + SourceRewriter.ReplaceText(E->getLocStart(), 4, Text); + } + + bool VisitCXXThisExpr(const CXXThisExpr *E) { + if (RewrittenExpressions.count(E)) + return true; + if (!E->isImplicit()) + replace(E, "&object"); + return true; + } + + bool TraverseUnaryDeref(UnaryOperator *E) { + if (const auto *This = + dyn_cast(E->getSubExpr()->IgnoreParenImpCasts())) { + if (!This->isImplicit()) { + // Remove the '*' as the variable is now a reference. + SourceRewriter.RemoveText( + CharSourceRange::getTokenRange(E->getLocStart(), E->getLocStart())); + replace(This, "object"); + return true; + } + } + return RecursiveASTVisitor::TraverseUnaryAddrOf(E); + } +}; + +/// Traverses the extracted code and rewrites the uses of 'self' into 'object'. +class CapturedSelfRewriter : public PseudoObjectRewriter { +public: + Rewriter &SourceRewriter; + const ImplicitParamDecl *SelfDecl; + + CapturedSelfRewriter(Rewriter &SourceRewriter, + const ImplicitParamDecl *SelfDecl) + : SourceRewriter(SourceRewriter), SelfDecl(SelfDecl) { + assert(SelfDecl); + } + + bool VisitDeclRefExpr(const DeclRefExpr *E) { + const VarDecl *VD = dyn_cast(E->getDecl()); + if (!VD || VD != SelfDecl) + return true; + if (E->getLocStart().isInvalid()) + return true; + SourceRewriter.ReplaceText(E->getLocStart(), 4, "object"); + return true; + } + + void insertObjectForImplicitSelf(const Expr *E, SourceLocation Loc, + StringRef Text) { + const auto *DRE = dyn_cast(E); + if (!DRE) + return; + const VarDecl *VD = dyn_cast(DRE->getDecl()); + if (!VD || VD != SelfDecl || DRE->getLocStart().isValid()) + return; + SourceRewriter.InsertText(Loc, Text); + } + + bool VisitObjCIvarRefExpr(const ObjCIvarRefExpr *E) { + insertObjectForImplicitSelf(E->getBase()->IgnoreImpCasts(), + E->getLocStart(), "object->"); + return true; + } +}; + +/// Traverses the extracted code and rewrites the uses of 'self' into the name +/// of the class. +class CapturedClassSelfRewriter + : public PseudoObjectRewriter { +public: + Rewriter &SourceRewriter; + StringRef ClassName; + const ImplicitParamDecl *SelfDecl; + + CapturedClassSelfRewriter(Rewriter &SourceRewriter, StringRef ClassName, + const ImplicitParamDecl *SelfDecl) + : SourceRewriter(SourceRewriter), ClassName(ClassName), + SelfDecl(SelfDecl) { + + assert(SelfDecl); + } + + bool VisitDeclRefExpr(const DeclRefExpr *E) { + const VarDecl *VD = dyn_cast(E->getDecl()); + if (!VD || VD != SelfDecl || E->getLocStart().isInvalid()) + return true; + SourceRewriter.ReplaceText(E->getLocStart(), 4, ClassName); + return true; + } +}; + +/// Traverses the extracted code and rewrites the uses of 'super' into +/// 'superObject' or the name of the super class. +class CapturedSuperRewriter + : public PseudoObjectRewriter { +public: + Rewriter &SourceRewriter; + StringRef ReplacementString; + + CapturedSuperRewriter(Rewriter &SourceRewriter, StringRef ReplacementString) + : SourceRewriter(SourceRewriter), ReplacementString(ReplacementString) {} + + void rewriteSuper(SourceLocation Loc) { + SourceRewriter.ReplaceText(Loc, strlen("super"), ReplacementString); + } + + bool VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *E) { + if (E->isSuperReceiver()) + rewriteSuper(E->getReceiverLocation()); + return true; + } + + bool VisitObjCMessageExpr(const ObjCMessageExpr *E) { + if (E->getSuperLoc().isValid()) + rewriteSuper(E->getSuperLoc()); + return true; + } +}; + +struct ExtractionSemicolonPolicy { + bool IsNeededInExtractedFunction; + bool IsNeededInOriginalFunction; + + static ExtractionSemicolonPolicy neededInExtractedFunction() { + return {true, false}; + } + static ExtractionSemicolonPolicy neededInOriginalFunction() { + return {false, true}; + } + static ExtractionSemicolonPolicy neededInBoth() { return {true, true}; } +}; + +} // end anonymous namespace + +ExtractionSemicolonPolicy +computeSemicolonExtractionPolicy(const Stmt *S, SourceRange &ExtractedRange, + const SourceManager &SM, + const LangOptions &LangOpts) { + if (isa(S)) + return ExtractionSemicolonPolicy::neededInExtractedFunction(); + bool NeedsSemi = isSemicolonRequiredAfter(S); + if (!NeedsSemi) + return ExtractionSemicolonPolicy::neededInOriginalFunction(); + SourceLocation End = ExtractedRange.getEnd(); + if (isSemicolonAtLocation(End, SM, LangOpts)) + return ExtractionSemicolonPolicy::neededInOriginalFunction(); + SourceLocation NextTokenLoc = + Lexer::findNextTokenLocationAfterTokenAt(End, SM, LangOpts); + if (NextTokenLoc.isValid() && + isSemicolonAtLocation(NextTokenLoc, SM, LangOpts) && + areOnSameLine(NextTokenLoc, End, SM)) { + ExtractedRange.setEnd(NextTokenLoc); + return ExtractionSemicolonPolicy::neededInOriginalFunction(); + } + return ExtractionSemicolonPolicy::neededInBoth(); +} + +PrintingPolicy getPrintingPolicy(const ASTContext &Context, + const Preprocessor &PP) { + PrintingPolicy Policy = Context.getPrintingPolicy(); + // Our printing policy is copied over the ASTContext printing policy whenever + // a diagnostic is emitted, so recompute it. + Policy.Bool = Context.getLangOpts().Bool; + // FIXME: This is duplicated with Sema.cpp. When upstreaming this should be + // cleaned up. + if (!Policy.Bool) { + if (const MacroInfo *BoolMacro = PP.getMacroInfo(Context.getBoolName())) { + Policy.Bool = BoolMacro->isObjectLike() && + BoolMacro->getNumTokens() == 1 && + BoolMacro->getReplacementToken(0).is(tok::kw__Bool); + } + } + return Policy; +} + +static bool isAssignmentOperator(const Stmt *S) { + if (const auto *PseudoExpr = dyn_cast(S)) + return isAssignmentOperator(PseudoExpr->getSyntacticForm()); + if (const auto *BO = dyn_cast(S)) + return BO->isAssignmentOp(); + return false; +} + +static QualType getFunctionLikeParentDeclReturnType(const Decl *D) { + // FIXME: might need to handle ObjC blocks in the future. + if (const auto *M = dyn_cast(D)) + return M->getReturnType(); + return cast(D)->getReturnType(); +} + +static const Stmt *getEnclosingDeclBody(const Decl *D) { + // FIXME: might need to handle ObjC blocks in the future. + if (const auto *M = dyn_cast(D)) + return M->getBody(); + return cast(D)->getBody(); +} + +static bool isEnclosingMethodConst(const Decl *D) { + if (const auto *MD = dyn_cast(D)) + return MD->isConst(); + return false; +} + +static bool isEnclosingMethodStatic(const Decl *D) { + if (const auto *MD = dyn_cast(D)) + return MD->isStatic(); + return false; +} + +static bool isEnclosingMethodOutOfLine(const Decl *D) { + const auto *MD = dyn_cast(D); + if (!MD) + return false; + return MD->isOutOfLine(); +} + +static void printEnclosingMethodScope(const Decl *D, llvm::raw_ostream &OS, + const PrintingPolicy &PP) { + const auto *MD = dyn_cast(D); + if (!MD) + return; + if (!MD->isOutOfLine() || !MD->getQualifier()) + return; + MD->getQualifier()->print(OS, PP); +} + +static SourceLocation +computeFunctionExtractionLocation(const Decl *D, bool IsMethodExtraction) { + if (!IsMethodExtraction && isa(D)) { + // Code from methods that defined in class bodies should be extracted to a + // function defined just before the class. + while (const auto *RD = dyn_cast(D->getLexicalDeclContext())) + D = RD; + } + return D->getLocStart(); +} + +namespace { +enum class MethodDeclarationPlacement { After, Before }; + +/// \brief Represents an entity captured from the original function that's +/// passed into the new function/method. +struct CapturedVariable { + const VarDecl *VD; + const FieldDecl *FD; + QualType ThisType; + bool PassByRefOrPtr; + bool IsRefOrPtrConst; + bool IsThisSelf = false; + bool IsThisSuper = false; + bool TakeAddress = false; + QualType ParameterType; + + CapturedVariable(const VarDecl *VD, bool PassByRefOrPtr, bool IsRefOrPtrConst) + : VD(VD), FD(nullptr), PassByRefOrPtr(PassByRefOrPtr), + IsRefOrPtrConst(IsRefOrPtrConst) {} + CapturedVariable(const FieldDecl *FD, bool PassByRefOrPtr, + bool IsRefOrPtrConst) + : VD(nullptr), FD(FD), PassByRefOrPtr(PassByRefOrPtr), + IsRefOrPtrConst(IsRefOrPtrConst) {} + CapturedVariable(QualType ThisType, bool PassByRefOrPtr, bool IsConst) + : VD(nullptr), FD(nullptr), ThisType(ThisType), + PassByRefOrPtr(PassByRefOrPtr), IsRefOrPtrConst(IsConst) {} + + static CapturedVariable getThis(QualType T, bool IsConst) { + return CapturedVariable(T, /*PassByRefOrPtr=*/true, /*IsConst*/ IsConst); + } + + static CapturedVariable getSelf(QualType T) { + auto Result = + CapturedVariable(T, /*PassByRefOrPtr=*/false, /*IsConst*/ false); + Result.IsThisSelf = true; + return Result; + } + + static CapturedVariable getSuper(QualType T) { + auto Result = + CapturedVariable(T, /*PassByRefOrPtr=*/false, /*IsConst*/ false); + Result.IsThisSuper = true; + return Result; + } + + StringRef getName() const { + return VD ? VD->getName() + : FD ? FD->getName() : IsThisSuper ? "superObject" : "object"; + } + StringRef getExpr() const { + return ThisType.isNull() + ? getName() + : IsThisSelf ? "self" : IsThisSuper ? "super.self" : "*this"; + } + QualType getType() const { + return VD ? VD->getType() : FD ? FD->getType() : ThisType; + } +}; +} // end anonymous namespace + +static std::pair +computeAppropriateExtractionLocationForMethodDeclaration( + const CXXMethodDecl *D) { + const CXXRecordDecl *RD = D->getParent(); + // Try to put the new declaration after the last method, or just before the + // end of the class. + SourceLocation Loc; + for (const CXXMethodDecl *M : RD->methods()) { + if (M->isImplicit()) + continue; + Loc = M->getLocEnd(); + } + return Loc.isValid() ? std::make_pair(Loc, MethodDeclarationPlacement::After) + : std::make_pair(RD->getLocEnd(), + MethodDeclarationPlacement::Before); +} + +static bool isInHeader(SourceLocation Loc, const SourceManager &SM) { + // Base the header decision on the filename. + StringRef Extension = llvm::sys::path::extension(SM.getFilename(Loc)); + if (Extension.empty()) + return false; + return llvm::StringSwitch(Extension.drop_front()) + .Case("h", true) + .Case("hpp", true) + .Case("hh", true) + .Case("h++", true) + .Case("hxx", true) + .Case("inl", true) + .Case("def", true) + .Default(false); +} + +llvm::Expected ExtractOperation::perform( + ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { + std::vector Replacements; + SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + Rewriter SourceRewriter(SM, LangOpts); + PrintingPolicy PP = getPrintingPolicy(Context, ThePreprocessor); + PP.UseStdFunctionForLambda = true; + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + + const Stmt *S = + CandidateExtractionInfo[SelectedCandidateIndex].AnalyzedStatement + ? CandidateExtractionInfo[SelectedCandidateIndex].AnalyzedStatement + : this->S; + + const auto *EnclosingObjCMethod = + dyn_cast(FunctionLikeParentDecl); + + // Find the variables that are captured by the extracted code. + ExtractedCodeVisitor Visitor(/*SelfDecl=*/EnclosingObjCMethod + ? EnclosingObjCMethod->getSelfDecl() + : nullptr); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + Visitor.InspectExtractedStmt(const_cast(S), Context); + } else + Visitor.InspectExtractedStmt(const_cast(S), Context); + // Compute the return type. + bool IsExpr = isa(S); + // Assignment operators should be treated as statements unless they are a part + // of an expression. + if (IsExpr && isAssignmentOperator(S) && + (!ParentStmt || !isa(ParentStmt))) + IsExpr = false; + QualType ReturnType; + if (IsExpr || Visitor.HasReturnInExtracted) { + if (const auto *E = dyn_cast(S)) { + assert(!ExtractedStmtRange); + ReturnType = findExpressionLexicalType(FunctionLikeParentDecl, E, + E->getType(), PP, Context); + } else + ReturnType = getFunctionLikeParentDeclReturnType(FunctionLikeParentDecl); + } else + ReturnType = Context.VoidTy; + // Sort the captured variables. + std::vector CapturedVariables; + llvm::SmallPtrSet VariablesDefinedInExtractedCode; + CapturedVariables.reserve(Visitor.CapturedVariables.size() + + Visitor.CapturedFields.size()); + for (const auto &I : Visitor.CapturedVariables) { + if (I.getSecond().IsDefined) { + VariablesDefinedInExtractedCode.insert(I.getFirst()); + continue; + } + CapturedVariables.push_back( + CapturedVariable(I.getFirst(), I.getSecond().isPassedByRefOrPtr(), + I.getSecond().isRefOrPtrConst())); + } + // Take a look at the variables that are defined in the extracted code. + VariableDefinedInExtractedCodeUseAfterExtractionFinder + UsedAfterExtractionFinder(ExtractedStmtRange ? *ExtractedStmtRange->Last + : S, + VariablesDefinedInExtractedCode); + UsedAfterExtractionFinder.TraverseStmt( + const_cast(getEnclosingDeclBody(FunctionLikeParentDecl))); + struct RedeclaredVariable { + const VarDecl *VD; + int OrderingPriority; + }; + llvm::SmallVector RedeclaredVariables; + bool CanUseReturnForVariablesUsedAfterwards = + !isa(S) && ReturnType->isVoidType() && + UsedAfterExtractionFinder.VariablesUsedAfterExtraction.size() == 1; + if (CanUseReturnForVariablesUsedAfterwards) { + // Avoid using the return value for the variable that's used afterwards as + // another variable might shadow it at the point of a 'return' that we + // have to rewrite to 'return var'. + const VarDecl *VD = + *UsedAfterExtractionFinder.VariablesUsedAfterExtraction.begin(); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) { + if (PossibleShadowingVariableFinder::hasShadowingVar(VD, S)) { + CanUseReturnForVariablesUsedAfterwards = false; + break; + } + } + } else + CanUseReturnForVariablesUsedAfterwards = + !PossibleShadowingVariableFinder::hasShadowingVar(VD, S); + } + if (CanUseReturnForVariablesUsedAfterwards) { + for (const auto &I : Visitor.CapturedVariables) { + if (!I.getSecond().IsDefined || + !UsedAfterExtractionFinder.VariablesUsedAfterExtraction.count( + I.getFirst())) + continue; + RedeclaredVariables.push_back( + {I.getFirst(), I.getSecond().DefineOrderingPriority}); + ReturnType = I.getFirst()->getType(); + // Const qualifier can be dropped as we don't want to declare the return + // type as 'const'. + if (ReturnType.isConstQualified()) + ReturnType.removeLocalConst(); + break; + } + if (Visitor.HasReturnInExtracted) { + ReturnRewriter ReturnsRewriter(SourceRewriter, + RedeclaredVariables.front().VD->getName()); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + ReturnsRewriter.TraverseStmt(const_cast(S)); + } else + ReturnsRewriter.TraverseStmt(const_cast(S)); + } + } else { + for (const auto &I : Visitor.CapturedVariables) { + if (!I.getSecond().IsDefined || + !UsedAfterExtractionFinder.VariablesUsedAfterExtraction.count( + I.getFirst())) + continue; + RedeclaredVariables.push_back( + {I.getFirst(), I.getSecond().DefineOrderingPriority}); + if (!I.getSecond().IsUsed) + continue; + // Pass the variable that's defined in the extracted code but used + // afterwards as a parameter only when it's actually used in the extracted + // code. + CapturedVariables.push_back(CapturedVariable(I.getFirst(), + /*PassByRefOrPtr=*/true, + /*IsRefOrPtrConst=*/false)); + } + std::sort(RedeclaredVariables.begin(), RedeclaredVariables.end(), + [](const RedeclaredVariable &X, const RedeclaredVariable &Y) { + return X.OrderingPriority < Y.OrderingPriority; + }); + DefinedInExtractedCodeDeclStmtRewriter DeclRewriter( + SourceRewriter, UsedAfterExtractionFinder.VariablesUsedAfterExtraction, + PP); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + DeclRewriter.TraverseStmt(const_cast(S)); + } else + DeclRewriter.TraverseStmt(const_cast(S)); + } + // Capture any fields if necessary. + bool IsThisConstInCapturedFieldUses = true; + if (!IsMethodExtraction) { + for (const auto &I : Visitor.CapturedFields) { + if (I.getSecond().isPassedByRefOrPtr() && + !I.getSecond().isRefOrPtrConst()) + IsThisConstInCapturedFieldUses = false; + // Private fields that use explicit 'this' should be captured using 'this' + // even if they might end up being inaccessible in the extracted function. + if (I.getSecond().IsFieldCapturedWithThis) + continue; + CapturedVariables.push_back( + CapturedVariable(I.getFirst(), I.getSecond().isPassedByRefOrPtr(), + I.getSecond().isRefOrPtrConst())); + } + } + std::sort(CapturedVariables.begin(), CapturedVariables.end(), + [](const CapturedVariable &X, const CapturedVariable &Y) { + return X.getName() < Y.getName(); + }); + // 'This'/'self' should be passed-in first. + if (!IsMethodExtraction && Visitor.CaptureThis) { + CapturedVariables.insert( + CapturedVariables.begin(), + CapturedVariable::getThis( + Visitor.ThisRecordType, + IsThisConstInCapturedFieldUses && + Visitor.IsThisConstForNonCapturedFieldUses)); + CapturedThisReferenceRewriter ThisRewriter(SourceRewriter); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + ThisRewriter.TraverseStmt(const_cast(S)); + } else + ThisRewriter.TraverseStmt(const_cast(S)); + CapturedThisPointerRewriter PtrThisRewriter( + SourceRewriter, ThisRewriter.RewrittenExpressions); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + PtrThisRewriter.TraverseStmt(const_cast(S)); + } else + PtrThisRewriter.TraverseStmt(const_cast(S)); + } else if (!IsMethodExtraction && Visitor.CaptureSelf && + EnclosingObjCMethod) { + if (EnclosingObjCMethod->isInstanceMethod()) { + // Instance methods rewrite 'self' into an 'object' parameter. + CapturedVariables.insert(CapturedVariables.begin(), + CapturedVariable::getSelf(Visitor.SelfType)); + CapturedSelfRewriter SelfRewriter(SourceRewriter, + EnclosingObjCMethod->getSelfDecl()); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + SelfRewriter.TraverseStmt(const_cast(S)); + } else + SelfRewriter.TraverseStmt(const_cast(S)); + } else { + // Class methods rewrite 'self' into the class name and don't pass 'self' + // as a parameter. + CapturedClassSelfRewriter SelfRewriter( + SourceRewriter, EnclosingObjCMethod->getClassInterface()->getName(), + EnclosingObjCMethod->getSelfDecl()); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + SelfRewriter.TraverseStmt(const_cast(S)); + } else + SelfRewriter.TraverseStmt(const_cast(S)); + } + } + if (!IsMethodExtraction && Visitor.CaptureSuper && EnclosingObjCMethod) { + if (EnclosingObjCMethod->isInstanceMethod()) + // Instance methods rewrite 'super' into an 'superObject' parameter. + CapturedVariables.insert(Visitor.CaptureSelf + ? CapturedVariables.begin() + 1 + : CapturedVariables.begin(), + CapturedVariable::getSuper(Visitor.SuperType)); + CapturedSuperRewriter SuperRewriter( + SourceRewriter, EnclosingObjCMethod->isInstanceMethod() + ? "superObject" + : EnclosingObjCMethod->getClassInterface() + ->getSuperClass() + ->getName()); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + SuperRewriter.TraverseStmt(const_cast(S)); + } else + SuperRewriter.TraverseStmt(const_cast(S)); + } + + // Compute the parameter types. + for (auto &Var : CapturedVariables) { + QualType T = Var.getType(); + + // Array types are passed into the extracted function using a pointer. + if (const auto *AT = Context.getAsArrayType(T)) + T = Context.getPointerType(AT->getElementType()); + + // Captured records and other mutated variables are passed into the + // extracted function either using a reference (C++) or a pointer. + if ((T->isRecordType() || Var.PassByRefOrPtr) && !T->isReferenceType()) { + // Add a 'const' qualifier to the record when it's not mutated in the + // extracted code or when we are taking the address of the captured + // variable for just a 'const' use. + if (!Var.PassByRefOrPtr || Var.IsRefOrPtrConst) + T.addConst(); + + if (LangOpts.CPlusPlus) + T = Context.getLValueReferenceType(T); + else { + T = Context.getPointerType(T); + CapturedVariableCaptureByPointerRewriter UseRewriter(Var.VD, + SourceRewriter); + if (ExtractedStmtRange) { + for (const Stmt *S : *ExtractedStmtRange) + UseRewriter.TraverseStmt(const_cast(S)); + } else + UseRewriter.TraverseStmt(const_cast(S)); + Var.TakeAddress = true; + } + } + // Const qualifier can be dropped as we don't want to declare the parameter + // as 'const'. + else if (T.isLocalConstQualified()) + T.removeLocalConst(); + + Var.ParameterType = T; + } + + // TODO: Choose a better name if there are collisions. + StringRef ExtractedName = "extracted"; + llvm::SmallVector ExtractedNamePieces; + ExtractedNamePieces.push_back(ExtractedName); + if (IsMethodExtraction && EnclosingObjCMethod && !CapturedVariables.empty()) { + for (const auto &Var : llvm::makeArrayRef(CapturedVariables).drop_front()) + ExtractedNamePieces.push_back(Var.getName()); + } + std::unique_ptr CreatedSymbol = + llvm::make_unique( + SymbolName(ExtractedNamePieces)); + + SourceLocation FunctionExtractionLoc = computeFunctionExtractionLocation( + FunctionLikeParentDecl, IsMethodExtraction); + FunctionExtractionLoc = + getLocationOfPrecedingComment(FunctionExtractionLoc, SM, LangOpts); + + // Create the replacement that contains the new function. + auto PrintFunctionHeader = + [&](llvm::raw_string_ostream &OS, + bool IsDefinition = + true) -> RefactoringReplacement::AssociatedSymbolLocation { + if (IsMethodExtraction && EnclosingObjCMethod) { + OS << (EnclosingObjCMethod->isClassMethod() ? '+' : '-') << " ("; + ReturnType.print(OS, PP); + OS << ')'; + llvm::SmallVector NameOffsets; + NameOffsets.push_back(OS.str().size()); + OS << ExtractedName; + bool IsFirst = true; + for (const auto &Var : CapturedVariables) { + if (!IsFirst) { + OS << ' '; + NameOffsets.push_back(OS.str().size()); + OS << Var.getName(); + } + IsFirst = false; + OS << ":("; + Var.ParameterType.print(OS, PP); + OS << ')' << Var.getName(); + } + return RefactoringReplacement::AssociatedSymbolLocation( + NameOffsets, /*IsDeclaration=*/true); + } + auto *FD = dyn_cast(FunctionLikeParentDecl); + if (IsMethodExtraction && IsDefinition && + !FD->getDescribedFunctionTemplate()) { + // Print the class template parameter lists for an out-of-line method. + for (unsigned I = 0, + NumTemplateParams = FD->getNumTemplateParameterLists(); + I < NumTemplateParams; ++I) { + FD->getTemplateParameterList(I)->print(OS, PP); + OS << "\n"; + } + } + if (IsMethodExtraction && isEnclosingMethodStatic(FunctionLikeParentDecl)) + OS << "static "; + else if (!IsMethodExtraction) + OS << (isInHeader(FunctionExtractionLoc, SM) ? "inline " : "static "); + ReturnType.print(OS, PP); + OS << ' '; + if (IsMethodExtraction && IsDefinition) + printEnclosingMethodScope(FunctionLikeParentDecl, OS, PP); + unsigned NameOffset = OS.str().size(); + OS << ExtractedName << '('; + bool IsFirst = true; + for (const auto &Var : CapturedVariables) { + if (!IsFirst) + OS << ", "; + IsFirst = false; + Var.ParameterType.print(OS, PP, /*PlaceHolder=*/Var.getName()); + } + OS << ')'; + if (IsMethodExtraction && isEnclosingMethodConst(FunctionLikeParentDecl)) + OS << " const"; + return RefactoringReplacement::AssociatedSymbolLocation( + NameOffset, /*IsDeclaration=*/true); + ; + }; + + if (IsMethodExtraction && + isEnclosingMethodOutOfLine(FunctionLikeParentDecl)) { + // The location of the declaration should be either before the original + // declararation, or, if this method has not declaration, somewhere + // appropriate in the class. + MethodDeclarationPlacement Placement; + SourceLocation DeclarationLoc; + if (FunctionLikeParentDecl->getCanonicalDecl() != FunctionLikeParentDecl) { + DeclarationLoc = computeFunctionExtractionLocation( + FunctionLikeParentDecl->getCanonicalDecl(), IsMethodExtraction); + Placement = MethodDeclarationPlacement::Before; + } else { + auto LocAndPlacement = + computeAppropriateExtractionLocationForMethodDeclaration( + cast(FunctionLikeParentDecl)); + DeclarationLoc = LocAndPlacement.first; + Placement = LocAndPlacement.second; + } + if (Placement == MethodDeclarationPlacement::Before) + DeclarationLoc = + getLocationOfPrecedingComment(DeclarationLoc, SM, LangOpts); + else + DeclarationLoc = getLastLineLocationUnlessItHasOtherTokens( + getPreciseTokenLocEnd(DeclarationLoc, SM, LangOpts), SM, LangOpts); + // Add a replacement for the method declaration if necessary. + std::string DeclarationString; + llvm::raw_string_ostream OS(DeclarationString); + if (Placement == MethodDeclarationPlacement::After) + OS << "\n\n"; + RefactoringReplacement::AssociatedSymbolLocation SymbolLoc = + PrintFunctionHeader(OS, /*IsDefinition=*/false); + OS << ";\n"; + if (Placement == MethodDeclarationPlacement::Before) + OS << "\n"; + Replacements.push_back(RefactoringReplacement( + SourceRange(DeclarationLoc, DeclarationLoc), std::move(OS.str()), + CreatedSymbol.get(), SymbolLoc)); + } + std::string ExtractedCode; + llvm::raw_string_ostream ExtractedOS(ExtractedCode); + RefactoringReplacement::AssociatedSymbolLocation SymbolLoc = + PrintFunctionHeader(ExtractedOS); + ExtractedOS << " {\n"; + if (IsExpr && !ReturnType->isVoidType()) + ExtractedOS << "return "; + SourceRange ExtractedTokenRange = + CandidateExtractionInfo[SelectedCandidateIndex].Range; + auto Semicolons = computeSemicolonExtractionPolicy( + ExtractedStmtRange ? *(ExtractedStmtRange->Last) : S, ExtractedTokenRange, + SM, LangOpts); + ExtractedOS << SourceRewriter.getRewrittenText(ExtractedTokenRange); + if (Semicolons.IsNeededInExtractedFunction) + ExtractedOS << ';'; + if (CanUseReturnForVariablesUsedAfterwards) + ExtractedOS << "\nreturn " << RedeclaredVariables.front().VD->getName() + << ";"; + ExtractedOS << "\n}\n\n"; + Replacements.push_back(RefactoringReplacement( + SourceRange(FunctionExtractionLoc, FunctionExtractionLoc), + std::move(ExtractedOS.str()), CreatedSymbol.get(), SymbolLoc)); + + // Create a replacements that removes the extracted code in favor of the + // function call. + std::string InsertedCode; + llvm::raw_string_ostream InsertedOS(InsertedCode); + // We might have to declare variables that were declared in the extracted code + // but still used afterwards. + if (CanUseReturnForVariablesUsedAfterwards) { + const auto &Var = RedeclaredVariables.front(); + Var.VD->getType().print(InsertedOS, PP); + InsertedOS << ' ' << Var.VD->getName() << " = "; + } else { + for (const auto &Var : RedeclaredVariables) { + Var.VD->getType().print(InsertedOS, PP); + InsertedOS << ' ' << Var.VD->getName() << ";\n"; + } + } + InsertedOS << CandidateExtractionInfo[SelectedCandidateIndex].PreInsertedText; + llvm::SmallVector NameOffsets; + if (IsMethodExtraction && EnclosingObjCMethod) { + InsertedOS << "[self "; + NameOffsets.push_back(InsertedOS.str().size()); + InsertedOS << ExtractedName; + bool IsFirst = true; + for (const auto &Var : CapturedVariables) { + if (!IsFirst) { + InsertedOS << ' '; + NameOffsets.push_back(InsertedOS.str().size()); + InsertedOS << Var.getName(); + } + IsFirst = false; + InsertedOS << ':'; + if (Var.TakeAddress) + InsertedOS << '&'; + InsertedOS << Var.getExpr(); + } + InsertedOS << ']'; + } else { + NameOffsets.push_back(InsertedOS.str().size()); + InsertedOS << ExtractedName << '('; + bool IsFirst = true; + for (const auto &Var : CapturedVariables) { + if (!IsFirst) + InsertedOS << ", "; + IsFirst = false; + if (Var.TakeAddress) + InsertedOS << '&'; + InsertedOS << Var.getExpr(); + } + InsertedOS << ')'; + } + if (Semicolons.IsNeededInOriginalFunction) + InsertedOS << ';'; + SourceRange ExtractedCharRange = SourceRange( + ExtractedTokenRange.getBegin(), + getPreciseTokenLocEnd(ExtractedTokenRange.getEnd(), SM, LangOpts)); + Replacements.push_back(RefactoringReplacement( + ExtractedCharRange, std::move(InsertedOS.str()), CreatedSymbol.get(), + llvm::makeArrayRef(NameOffsets))); + + RefactoringResult Result(std::move(Replacements)); + Result.AssociatedSymbols.push_back(std::move(CreatedSymbol)); + return std::move(Result); +} diff --git a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp new file mode 100644 index 0000000000000..87b65a325c28f --- /dev/null +++ b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp @@ -0,0 +1,369 @@ +//===--- ExtractRepeatedExpressionIntoVariable.cpp - ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "Extract repeated expression into variable" refactoring +// operation. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringOperations.h" +#include "SourceLocationUtilities.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "llvm/Support/SaveAndRestore.h" + +using namespace clang; +using namespace clang::tooling; + +namespace { + +class ExtractRepeatedExpressionIntoVariableOperation + : public RefactoringOperation { +public: + ExtractRepeatedExpressionIntoVariableOperation( + const Expr *E, ArrayRef Duplicates, const Decl *ParentDecl) + : E(E), DuplicateExpressions(Duplicates.begin(), Duplicates.end()), + ParentDecl(ParentDecl) {} + + const Stmt *getTransformedStmt() const override { return E; } + + llvm::Expected perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + const Expr *E; + SmallVector DuplicateExpressions; + const Decl *ParentDecl; +}; + +using UseOfDeclaration = std::pair; + +bool shouldIgnoreParens(const ParenExpr *E) { + if (!E) + return false; + const Expr *Child = E->getSubExpr(); + // Ignore the parens unless they are around an expression that + // really needs them. + if (isa(Child) || isa(Child) || + isa(Child) || + isa(Child)) + return false; + return true; +} + +/// Builds up a list of declarations that are used in an expression. +class DuplicateExprSemanticProfiler + : public RecursiveASTVisitor { + unsigned Index = 0; + llvm::SmallVectorImpl &DeclRefs; + +public: + DuplicateExprSemanticProfiler( + llvm::SmallVectorImpl &DeclRefs) + : DeclRefs(DeclRefs) { + DeclRefs.clear(); + } + + bool VisitStmt(const Stmt *S) { + if (!shouldIgnoreParens(dyn_cast(S))) + ++Index; + return true; + } + + bool VisitDeclRefExpr(const DeclRefExpr *E) { + if (E->getDecl()) + DeclRefs.emplace_back(E->getDecl(), Index); + return true; + } +}; + +class DuplicateExprFinder : public RecursiveASTVisitor, + PrinterHelper { + const Expr *Target; + const PrintingPolicy &PP; + Stmt::StmtClass ExprKind; + QualType T; + std::string ExprString, OSString; + llvm::SmallVector ExprDecls, DeclUses; + + void printExpr(std::string &Str, const Expr *E) { + llvm::raw_string_ostream OS(Str); + E->printPretty(OS, /*Helper=*/this, PP); + } + +public: + SmallVector DuplicateExpressions; + + DuplicateExprFinder(const Expr *E, const PrintingPolicy &PP) + : Target(E), PP(PP), ExprKind(E->getStmtClass()), T(E->getType()) { + printExpr(ExprString, E); + DuplicateExprSemanticProfiler(ExprDecls).TraverseStmt( + const_cast(E)); + } + + bool handledStmt(Stmt *E, raw_ostream &OS) final override { + if (const auto *Paren = dyn_cast(E)) { + if (!shouldIgnoreParens(Paren)) + return false; + Paren->getSubExpr()->printPretty(OS, /*Helper=*/this, PP); + return true; + } + return false; + } + + bool VisitStmt(const Stmt *S) { + if (S->getStmtClass() != ExprKind) + return true; + const auto *E = cast(S); + if (E == Target) { + DuplicateExpressions.push_back(E); + return true; + } + // The expression types should match. + if (E->getType() != T) + return true; + // Check if the expression is a duplicate by comparing their lexical + // representations. + OSString.clear(); + printExpr(OSString, E); + if (OSString == ExprString) { + DuplicateExprSemanticProfiler(DeclUses).TraverseStmt( + const_cast(E)); + // Check if they're semantically equivalent. + if (ExprDecls.size() == DeclUses.size() && + std::equal(ExprDecls.begin(), ExprDecls.end(), DeclUses.begin())) + DuplicateExpressions.push_back(E); + } + return true; + } +}; + +} // end anonymous namespace + +static QualType returnTypeOfCall(const Expr *E) { + if (const auto *Call = dyn_cast(E)) { + if (const auto *Fn = Call->getDirectCallee()) + return Fn->getReturnType(); + } else if (const auto *Msg = dyn_cast(E)) { + if (const auto *M = Msg->getMethodDecl()) + return M->getReturnType(); + } else if (const auto *PRE = dyn_cast(E)) { + if (PRE->isImplicitProperty()) { + if (const auto *M = PRE->getImplicitPropertyGetter()) + return M->getReturnType(); + } else if (const auto *Prop = PRE->getExplicitProperty()) + return Prop->getType(); + } + return QualType(); +} + +static bool isRepeatableExpression(const Stmt *S) { + if (const auto *Op = dyn_cast(S)) + return Op->getOperator() == OO_Call || Op->getOperator() == OO_Subscript; + return isa(S) || isa(S) || + isa(S); +} + +RefactoringOperationResult +clang::tooling::initiateExtractRepeatedExpressionIntoVariableOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + const Stmt *S; + const Decl *ParentDecl; + if (SelectionRange.isValid()) { + auto SelectedStmt = Slice.getSelectedStmtSet(); + if (!SelectedStmt) + return None; + if (!SelectedStmt->containsSelectionRange) + return None; + if (!isRepeatableExpression(SelectedStmt->containsSelectionRange)) + return None; + S = SelectedStmt->containsSelectionRange; + ParentDecl = + Slice.parentDeclForIndex(*SelectedStmt->containsSelectionRangeIndex); + } else { + auto SelectedStmt = Slice.nearestSelectedStmt(isRepeatableExpression); + if (!SelectedStmt) + return None; + S = SelectedStmt->getStmt(); + ParentDecl = SelectedStmt->getParentDecl(); + } + + const Expr *E = cast(S); + // Check if the function/method returns a reference/pointer. + QualType T = returnTypeOfCall(E); + if (!T.getTypePtrOrNull() || + (!T->isAnyPointerType() && !T->isReferenceType())) + return None; + + DuplicateExprFinder DupFinder(E, Context.getPrintingPolicy()); + DupFinder.TraverseDecl(const_cast(ParentDecl)); + if (DupFinder.DuplicateExpressions.size() < 2) + return None; + + RefactoringOperationResult Result; + Result.Initiated = true; + if (!CreateOperation) + return Result; + auto Operation = + llvm::make_unique( + E, DupFinder.DuplicateExpressions, ParentDecl); + Result.RefactoringOp = std::move(Operation); + return Result; +} + +namespace { + +/// Checks if a set of expressions is directly contained in some AST region. +class StmtReachabilityChecker + : public RecursiveASTVisitor { + const llvm::SmallPtrSetImpl &Expressions; + unsigned Count = 0; + + StmtReachabilityChecker( + const llvm::SmallPtrSetImpl &Expressions) + : Expressions(Expressions) {} + + bool areAllExpressionsReached() const { return Count == Expressions.size(); } + +public: + bool VisitStmt(const Stmt *S) { + if (Expressions.count(S)) { + ++Count; + if (areAllExpressionsReached()) + return false; + } + return true; + } + + static bool areAllExpressionsReachableFrom( + CompoundStmt *S, const llvm::SmallPtrSetImpl &Expressions) { + StmtReachabilityChecker Checker(Expressions); + Checker.TraverseStmt(S); + return Checker.areAllExpressionsReached(); + } +}; + +/// Figures out where the extracted variable should go. +class ExtractedVariableInsertionLocFinder + : public RecursiveASTVisitor { + llvm::SmallPtrSet Expressions; + llvm::SmallVector, 4> + InsertionCandidateStack; + bool IsPrevCompoundStmt = false; + +public: + SourceLocation Loc; + + /// Initializes the insertion location finder using the set of duplicate + /// \p Expressions from one function. + ExtractedVariableInsertionLocFinder(ArrayRef Expressions) { + for (const Expr *E : Expressions) + this->Expressions.insert(E); + } + + bool TraverseStmt(Stmt *S) { + if (!S) + return RecursiveASTVisitor::TraverseStmt(S); + if (IsPrevCompoundStmt && !InsertionCandidateStack.empty()) + InsertionCandidateStack.back().second = S; + llvm::SaveAndRestore IsPrevCompoundStmtTracker(IsPrevCompoundStmt, + false); + if (auto *CS = dyn_cast(S)) { + IsPrevCompoundStmt = true; + InsertionCandidateStack.emplace_back(CS, nullptr); + return RecursiveASTVisitor::TraverseStmt(S); + } + return RecursiveASTVisitor::TraverseStmt(S); + } + + bool VisitStmt(const Stmt *S) { + if (Expressions.count(S)) { + // The insertion location should be in the first compound statement that + // includes all of the expressions as descendants as we want the new + // variable to be visible to all uses. + for (auto I = InsertionCandidateStack.rbegin(), + E = InsertionCandidateStack.rend(); + I != E; ++I) { + if (StmtReachabilityChecker::areAllExpressionsReachableFrom( + I->first, Expressions) && + I->second) { + Loc = I->second->getLocStart(); + break; + } + } + return false; + } + return true; + } +}; + +} // end anonymous namespace + +static StringRef nameForExtractedVariable(const Expr *E) { + if (const auto *Call = dyn_cast(E)) { + if (const auto *Fn = Call->getDirectCallee()) + return Fn->getName(); + } else if (const auto *Msg = dyn_cast(E)) { + if (const auto *M = Msg->getMethodDecl()) { + if (M->getSelector().isUnarySelector()) + return M->getSelector().getNameForSlot(0); + } + } else if (const auto *PRE = dyn_cast(E)) { + if (PRE->isImplicitProperty()) { + if (const auto *M = PRE->getImplicitPropertyGetter()) + return M->getSelector().getNameForSlot(0); + } else if (const auto *Prop = PRE->getExplicitProperty()) + return Prop->getName(); + } + return "duplicate"; +} + +llvm::Expected +ExtractRepeatedExpressionIntoVariableOperation::perform( + ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { + std::vector Replacements; + + const SourceManager &SM = Context.getSourceManager(); + ExtractedVariableInsertionLocFinder LocFinder(DuplicateExpressions); + LocFinder.TraverseDecl(const_cast(ParentDecl)); + if (LocFinder.Loc.isInvalid()) + return llvm::make_error( + "no appropriate insertion location found"); + + StringRef Name = nameForExtractedVariable(E); + + // Create the variable that will hold the value of the duplicate expression. + std::string VariableDeclarationString; + llvm::raw_string_ostream OS(VariableDeclarationString); + QualType T = returnTypeOfCall(E); + PrintingPolicy PP = Context.getPrintingPolicy(); + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + T.print(OS, PP, /*PlaceHolder*/ Name); + OS << " = "; + E->printPretty(OS, /*Helper=*/nullptr, Context.getPrintingPolicy()); + OS << ";\n"; + Replacements.emplace_back(SourceRange(LocFinder.Loc, LocFinder.Loc), + OS.str()); + + // Replace the duplicates with a reference to the variable. + for (const Expr *E : DuplicateExpressions) + Replacements.emplace_back( + SourceRange( + E->getLocStart(), + getPreciseTokenLocEnd(E->getLocEnd(), SM, Context.getLangOpts())), + Name); + + return std::move(Replacements); +} diff --git a/clang/lib/Tooling/Refactor/FillInEnumSwitchCases.cpp b/clang/lib/Tooling/Refactor/FillInEnumSwitchCases.cpp new file mode 100644 index 0000000000000..2cab0ee182c2c --- /dev/null +++ b/clang/lib/Tooling/Refactor/FillInEnumSwitchCases.cpp @@ -0,0 +1,110 @@ +//===--- FillInEnumSwitchCases.cpp - -------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "Add missing switch cases" refactoring operation. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringOperations.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/Edit/RefactoringFixits.h" + +using namespace clang; +using namespace clang::tooling; + +namespace { + +class FillInEnumSwitchCasesOperation : public RefactoringOperation { +public: + FillInEnumSwitchCasesOperation(const EnumDecl *Enum, const SwitchStmt *Switch, + const DeclContext *SwitchContext) + : Enum(Enum), Switch(Switch), SwitchContext(SwitchContext) {} + + const Stmt *getTransformedStmt() const override { return Switch; } + + llvm::Expected + perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + const EnumDecl *Enum; + const SwitchStmt *Switch; + const DeclContext *SwitchContext; +}; + +} // end anonymous namespace + +RefactoringOperationResult +clang::tooling::initiateFillInEnumSwitchCasesOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + const SwitchStmt *Switch; + const Decl *ParentDecl; + if (SelectionRange.isValid()) { + auto SelectedSet = Slice.getSelectedStmtSet(); + if (!SelectedSet) + return None; + Switch = dyn_cast_or_null(SelectedSet->containsSelectionRange); + // FIXME: Improve the interface for this to make it similar to SelectedStmt + if (SelectedSet->containsSelectionRange) + ParentDecl = + Slice.parentDeclForIndex(*SelectedSet->containsSelectionRangeIndex); + } else { + auto SelectedStmt = Slice.nearestSelectedStmt(Stmt::SwitchStmtClass); + if (!SelectedStmt) + return None; + Switch = cast(SelectedStmt->getStmt()); + ParentDecl = SelectedStmt->getParentDecl(); + } + if (!Switch) + return None; + + // Ensure that the type is an enum. + const Expr *Cond = Switch->getCond()->IgnoreImpCasts(); + const EnumDecl *ED = nullptr; + if (const auto *ET = Cond->getType()->getAs()) + ED = ET->getDecl(); + else { + // Enum literals are 'int' in C. + if (const auto *DRE = dyn_cast(Cond)) { + if (const auto *EC = dyn_cast(DRE->getDecl())) + ED = dyn_cast(EC->getDeclContext()); + } + } + + if (!ED) + return RefactoringOperationResult("The switch doesn't operate on an enum"); + if (!ED->isCompleteDefinition()) + return RefactoringOperationResult("The enum type is incomplete"); + + if (Switch->isAllEnumCasesCovered()) + return RefactoringOperationResult("All enum cases are already covered"); + + RefactoringOperationResult Result; + Result.Initiated = true; + if (!CreateOperation) + return Result; + auto Operation = llvm::make_unique( + ED, Switch, dyn_cast(ParentDecl)); + Result.RefactoringOp = std::move(Operation); + return Result; +} + +llvm::Expected +FillInEnumSwitchCasesOperation::perform(ASTContext &Context, + const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) { + std::vector Replacements; + edit::fillInMissingSwitchEnumCases( + Context, Switch, Enum, SwitchContext, + [&](const FixItHint &Hint) { Replacements.push_back(Hint); }); + return std::move(Replacements); +} diff --git a/clang/lib/Tooling/Refactor/FillInMissingMethodStubsFromAbstractClasses.cpp b/clang/lib/Tooling/Refactor/FillInMissingMethodStubsFromAbstractClasses.cpp new file mode 100644 index 0000000000000..42497907ee936 --- /dev/null +++ b/clang/lib/Tooling/Refactor/FillInMissingMethodStubsFromAbstractClasses.cpp @@ -0,0 +1,293 @@ +//===--- FillInMissingMethodStubsFromAbstractClasses.cpp - ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "Add missing abstract class method overrides" refactoring +// operation. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringOperations.h" +#include "SourceLocationUtilities.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "llvm/ADT/DenseSet.h" + +using namespace clang; +using namespace clang::tooling; + +namespace { + +class FillInMissingMethodStubsFromAbstractClassesOperation + : public RefactoringOperation { +public: + FillInMissingMethodStubsFromAbstractClassesOperation( + const CXXRecordDecl *Class) + : Class(Class) {} + + const Decl *getTransformedDecl() const override { return Class; } + + llvm::Expected perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + const CXXRecordDecl *Class; +}; + +} // end anonymous namespace + +static bool hasAbstractBases(const CXXRecordDecl *Class) { + for (const CXXBaseSpecifier &Base : Class->bases()) { + if (const auto *RD = Base.getType()->getAsCXXRecordDecl()) { + if (RD->isAbstract()) + return true; + } + } + return false; +} + +RefactoringOperationResult +clang::tooling::initiateFillInMissingMethodStubsFromAbstractClassesOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + auto SelectedDecl = Slice.innermostSelectedDecl( + llvm::makeArrayRef(Decl::CXXRecord), ASTSlice::InnermostDeclOnly); + if (!SelectedDecl) + return None; + const auto *Class = cast(SelectedDecl->getDecl()); + if (Class->isUnion() || !Class->isThisDeclarationADefinition()) + return None; + if (!hasAbstractBases(Class)) + return RefactoringOperationResult("The class has no abstract bases"); + if (!Class->isDependentType() && !Class->isAbstract()) + return RefactoringOperationResult( + "The class has no missing abstract class methods"); + + RefactoringOperationResult Result; + Result.Initiated = true; + if (!CreateOperation) + return Result; + auto Operation = + llvm::make_unique( + Class); + Result.RefactoringOp = std::move(Operation); + return Result; +} + +namespace { + +class PureMethodSet { + llvm::DenseMap Methods; + + void addPureMethodsFromAbstractClasses(const CXXRecordDecl *Class, + int &Priority) { + for (const CXXBaseSpecifier &Base : Class->bases()) { + const auto *RD = Base.getType()->getAsCXXRecordDecl(); + if (!RD || !RD->isAbstract()) + continue; + for (const CXXMethodDecl *M : RD->methods()) { + if (M->isPure()) + Methods.insert(std::make_pair(M->getCanonicalDecl(), Priority++)); + } + addPureMethodsFromAbstractClasses(RD, Priority); + } + } + + void addPureMethodsFromAbstractClasses(const CXXRecordDecl *Class) { + int Priority = 0; + addPureMethodsFromAbstractClasses(Class, Priority); + } + + void subtractImplementedPureMethods(const CXXRecordDecl *Class) { + for (const CXXMethodDecl *M : Class->methods()) { + if (!M->isVirtual() || M->isPure()) + continue; + for (const CXXMethodDecl *OM : M->overridden_methods()) { + OM = OM->getCanonicalDecl(); + if (OM->isPure()) + Methods.erase(OM); + } + } + for (const CXXBaseSpecifier &Base : Class->bases()) { + const auto *RD = Base.getType()->getAsCXXRecordDecl(); + if (!RD || !RD->isAbstract()) + continue; + subtractImplementedPureMethods(RD); + } + } + +public: + static std::vector + gatherMissingMethods(const CXXRecordDecl *Class) { + PureMethodSet MethodSet; + MethodSet.addPureMethodsFromAbstractClasses(Class); + MethodSet.subtractImplementedPureMethods(Class); + // Sort the missing methods. That will place methods from the same abstract + // class together in the order in which they were declared. + struct MethodInfo { + const CXXMethodDecl *M; + int Priority; + }; + std::vector MissingMethods; + for (const auto &M : MethodSet.Methods) + MissingMethods.push_back({M.first, M.second}); + std::sort(MissingMethods.begin(), MissingMethods.end(), + [](const MethodInfo &LHS, const MethodInfo &RHS) { + return LHS.Priority < RHS.Priority; + }); + std::vector Result; + Result.reserve(MissingMethods.size()); + for (const auto &M : MissingMethods) + Result.push_back(M.M); + return Result; + } +}; + +} // end anonymous namespace + +static SourceLocation findInsertionLocationForMethodsFromAbstractClass( + const CXXRecordDecl *AbstractClass, const CXXRecordDecl *Class, + const SourceManager &SM, const LangOptions &LangOpts) { + SourceLocation Loc; + for (const CXXMethodDecl *M : Class->methods()) { + if (!M->isVirtual() || M->isPure()) + continue; + for (const CXXMethodDecl *OM : M->overridden_methods()) { + OM = OM->getCanonicalDecl(); + if (OM->getLexicalDeclContext() == AbstractClass) { + SourceLocation EndLoc = M->getLocEnd(); + if (EndLoc.isMacroID()) + EndLoc = SM.getExpansionRange(EndLoc).second; + if (Loc.isInvalid()) + Loc = EndLoc; + else if (SM.isBeforeInTranslationUnit(Loc, EndLoc)) + Loc = EndLoc; + break; + } + } + } + if (Loc.isInvalid()) + return Loc; + return getLastLineLocationUnlessItHasOtherTokens(Loc, SM, LangOpts); +} + +/// Returns true if the given \p Class implements the majority of declared +/// methods in the class itself. +static bool shouldImplementMethodsInClass(const CXXRecordDecl *Class) { + // Check if this class implements the methods in the class itself. + unsigned NumMethods = 0, NumImplementedMethods = 0; + for (const CXXMethodDecl *M : Class->methods()) { + if (M->isImplicit()) + continue; + // Only look at methods/operators. + if (isa(M) || isa(M)) + continue; + ++NumMethods; + if (M->hasBody()) + ++NumImplementedMethods; + } + if (!NumMethods) + return false; + // Use the following arbitrary heuristic: + // If the number of method declarations is less than 4, then all of the + // methods must have bodies. Otherwise, at least 75% of the methods must + // have bodies. + return NumMethods < 4 + ? NumMethods == NumImplementedMethods + : float(NumImplementedMethods) / float(NumMethods) > 0.75; +} + +llvm::Expected +FillInMissingMethodStubsFromAbstractClassesOperation::perform( + ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { + std::vector Replacements; + + std::vector MissingMethods = + PureMethodSet::gatherMissingMethods(Class); + + bool GenerateBodyDummies = shouldImplementMethodsInClass(Class); + + PrintingPolicy PP = Context.getPrintingPolicy(); + PP.PolishForDeclaration = true; + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + + std::string EndInsertionOSStr; + llvm::raw_string_ostream EndInsertionOS(EndInsertionOSStr); + + std::string InsertionGroupOSStr; + llvm::raw_string_ostream InsertionGroupOS(InsertionGroupOSStr); + + SourceLocation InsertionLoc = Class->getLocEnd(); + const CXXRecordDecl *CurrentAbstractClass = nullptr; + SourceLocation CurrentGroupInsertionLoc; + for (const auto &I : llvm::enumerate(MissingMethods)) { + const CXXMethodDecl *Method = I.value(); + const CXXRecordDecl *AbstractClass = Method->getParent(); + if (CurrentAbstractClass != AbstractClass) { + if (!InsertionGroupOS.str().empty()) { + assert(CurrentGroupInsertionLoc.isValid()); + Replacements.emplace_back( + SourceRange(CurrentGroupInsertionLoc, CurrentGroupInsertionLoc), + InsertionGroupOS.str()); + } + InsertionGroupOSStr.clear(); + CurrentAbstractClass = AbstractClass; + CurrentGroupInsertionLoc = + findInsertionLocationForMethodsFromAbstractClass( + CurrentAbstractClass, Class, Context.getSourceManager(), + Context.getLangOpts()); + } + bool IsInsertingAfterRelatedMethods = CurrentGroupInsertionLoc.isValid(); + raw_ostream &OS = + IsInsertingAfterRelatedMethods ? InsertionGroupOS : EndInsertionOS; + + if (IsInsertingAfterRelatedMethods && InsertionGroupOS.str().empty()) + OS << "\n\n"; + // Print the method without the 'virtual' specifier and the pure '= 0' + // annotation. + auto *MD = const_cast(Method); + bool IsVirtual = MD->isVirtualAsWritten(); + MD->setVirtualAsWritten(false); + bool IsPure = MD->isPure(); + MD->setPure(false); + MD->print(OS, PP); + MD->setVirtualAsWritten(IsVirtual); + MD->setPure(IsPure); + + OS << " override"; + if (GenerateBodyDummies) + OS << " { \n <#code#>\n}\n"; + else + OS << ";\n"; + // Avoid an additional newline for the last method in an insertion group. + if (IsInsertingAfterRelatedMethods) { + const CXXRecordDecl *NextAbstractClass = + (I.index() + 1) != MissingMethods.size() + ? MissingMethods[I.index() + 1]->getParent() + : nullptr; + if (NextAbstractClass == CurrentAbstractClass) + OS << "\n"; + } else + OS << "\n"; + } + if (!InsertionGroupOS.str().empty()) { + assert(CurrentGroupInsertionLoc.isValid()); + Replacements.emplace_back( + SourceRange(CurrentGroupInsertionLoc, CurrentGroupInsertionLoc), + InsertionGroupOS.str()); + } + if (!EndInsertionOS.str().empty()) + Replacements.emplace_back(SourceRange(InsertionLoc, InsertionLoc), + EndInsertionOS.str()); + + return std::move(Replacements); +} diff --git a/clang/lib/Tooling/Refactor/FillInMissingProtocolStubs.cpp b/clang/lib/Tooling/Refactor/FillInMissingProtocolStubs.cpp new file mode 100644 index 0000000000000..de8cfbe4ce7c7 --- /dev/null +++ b/clang/lib/Tooling/Refactor/FillInMissingProtocolStubs.cpp @@ -0,0 +1,91 @@ +//===--- FillInMissingProtocolStubs.cpp - --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "Add methods from protocol(s)" refactoring operation. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringOperations.h" +#include "clang/AST/AST.h" +#include "clang/Edit/RefactoringFixits.h" + +using namespace clang; +using namespace clang::tooling; +using namespace edit::fillInMissingProtocolStubs; + +namespace { + +class FillInMissingProtocolStubsOperation : public RefactoringOperation { +public: + FillInMissingProtocolStubsOperation(const ObjCContainerDecl *Container, + FillInMissingProtocolStubs Impl) + : Container(Container), Impl(std::move(Impl)) {} + + const Decl *getTransformedDecl() const override { return Container; } + + llvm::Expected perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + const ObjCContainerDecl *Container; + FillInMissingProtocolStubs Impl; +}; + +} // end anonymous namespace + +RefactoringOperationResult +clang::tooling::initiateFillInMissingProtocolStubsOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + auto SelectedDecl = Slice.innermostSelectedDecl( + {Decl::ObjCImplementation, Decl::ObjCCategoryImpl, Decl::ObjCInterface, + Decl::ObjCCategory}, + ASTSlice::InnermostDeclOnly); + if (!SelectedDecl) + return None; + const auto *Container = cast(SelectedDecl->getDecl()); + + // If this in a class extension, initiate the operation on the @implementation + // if it's in the same TU. + if (const auto *Category = dyn_cast(Container)) { + if (Category->IsClassExtension()) { + const ObjCInterfaceDecl *I = Category->getClassInterface(); + if (I && I->getImplementation()) + Container = I->getImplementation(); + else + return RefactoringOperationResult( + "Class extension without suitable @implementation"); + } + } + + FillInMissingProtocolStubs Impl; + if (Impl.initiate(Context, Container)) + return None; + if (!Impl.hasMissingRequiredMethodStubs()) + return RefactoringOperationResult("All of the @required methods are there"); + + RefactoringOperationResult Result; + Result.Initiated = true; + if (!CreateOperation) + return Result; + auto Operation = llvm::make_unique( + Container, std::move(Impl)); + Result.RefactoringOp = std::move(Operation); + return Result; +} + +llvm::Expected +FillInMissingProtocolStubsOperation::perform( + ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { + std::vector Replacements; + Impl.perform(Context, + [&](const FixItHint &Hint) { Replacements.push_back(Hint); }); + return std::move(Replacements); +} diff --git a/clang/lib/Tooling/Refactor/IfSwitchConversion.cpp b/clang/lib/Tooling/Refactor/IfSwitchConversion.cpp new file mode 100644 index 0000000000000..1a284b8d49814 --- /dev/null +++ b/clang/lib/Tooling/Refactor/IfSwitchConversion.cpp @@ -0,0 +1,460 @@ +//===--- IfSwitchConversion.cpp - ----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "convert to switch" refactoring operation. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringOperations.h" +#include "SourceLocationUtilities.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/RecursiveASTVisitor.h" + +using namespace clang; +using namespace clang::tooling; + +namespace { + +class IfSwitchConversionOperation : public RefactoringOperation { +public: + IfSwitchConversionOperation(const IfStmt *If) : If(If) {} + + const Stmt *getTransformedStmt() const override { return If; } + + llvm::Expected perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + const IfStmt *If; +}; + +class ValidIfBodyVerifier : public RecursiveASTVisitor { + bool CheckBreaks = true; + +public: + bool IsValid = true; + + bool VisitBreakStmt(const BreakStmt *S) { + if (!CheckBreaks) + return true; + IsValid = false; + return false; + } + bool VisitDefaultStmt(const DefaultStmt *S) { + IsValid = false; + return false; + } + bool VisitCaseStmt(const CaseStmt *S) { + IsValid = false; + return false; + } + +// Handle nested loops: + +#define TRAVERSE_LOOP(STMT) \ + bool Traverse##STMT(STMT *S) { \ + bool Prev = CheckBreaks; \ + CheckBreaks = false; \ + RecursiveASTVisitor::Traverse##STMT(S); \ + CheckBreaks = Prev; \ + return true; \ + } + + TRAVERSE_LOOP(ForStmt) + TRAVERSE_LOOP(WhileStmt) + TRAVERSE_LOOP(DoStmt) + TRAVERSE_LOOP(CXXForRangeStmt) + TRAVERSE_LOOP(ObjCForCollectionStmt) + +#undef TRAVERSE_LOOP + + // Handle switches: + + bool TraverseSwitchStmt(SwitchStmt *S) { + // Don't visit the body as 'break'/'case'/'default' are all allowed inside + // switches. + return true; + } +}; + +} // end anonymous namespace + +/// Returns true if any of the if statements in the given if construct have +/// conditions that aren't allowed by the "convert to switch" operation. +static bool checkIfsHaveConditionExpression(const IfStmt *If) { + for (; If; If = dyn_cast_or_null(If->getElse())) { + if (If->getConditionVariable() || If->getInit() || !If->getCond()) + return true; + } + return false; +} + +static Optional> +matchBinOp(const Expr *E, BinaryOperator::Opcode Kind) { + const auto *BinOp = dyn_cast(E->IgnoreParens()); + if (!BinOp || BinOp->getOpcode() != Kind) + return None; + return std::pair( + BinOp->getLHS()->IgnoreParenImpCasts(), BinOp->getRHS()->IgnoreParens()); +} + +typedef llvm::SmallDenseSet RHSValueSet; + +/// Returns true if the conditional expression of an 'if' statement allows +/// the "convert to switch" refactoring action. +static bool isConditionValid(const Expr *E, ASTContext &Context, + Optional &MatchedLHSNodeID, + RHSValueSet &RHSValues) { + auto Equals = matchBinOp(E, BO_EQ); + if (!Equals.hasValue()) { + auto LogicalOr = matchBinOp(E, BO_LOr); + if (!LogicalOr.hasValue()) + return false; + return isConditionValid(LogicalOr.getValue().first, Context, + MatchedLHSNodeID, RHSValues) && + isConditionValid(LogicalOr.getValue().second, Context, + MatchedLHSNodeID, RHSValues); + } + const Expr *LHS = Equals.getValue().first; + const Expr *RHS = Equals.getValue().second; + if (!LHS->getType()->isIntegralOrEnumerationType() || + !RHS->getType()->isIntegralOrEnumerationType()) + return false; + + // RHS must be a constant and unique. + llvm::APSInt Value; + if (!RHS->EvaluateAsInt(Value, Context)) + return false; + // Only allow constant that fix into 64 bits. + if (Value.getMinSignedBits() > 64 || + !RHSValues.insert(Value.getExtValue()).second) + return false; + + // LHS must be identical to the other LHS expressions. + llvm::FoldingSetNodeID LHSNodeID; + LHS->Profile(LHSNodeID, Context, /*Canonical=*/false); + if (MatchedLHSNodeID.hasValue()) { + if (MatchedLHSNodeID.getValue() != LHSNodeID) + return false; + } else + MatchedLHSNodeID = std::move(LHSNodeID); + return true; +} + +RefactoringOperationResult clang::tooling::initiateIfSwitchConversionOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + // FIXME: Add support for selections. + const auto *If = cast_or_null(Slice.nearestStmt(Stmt::IfStmtClass)); + if (!If) + return None; + + // Don't allow if statements without any 'else' or 'else if'. + if (!If->getElse()) + return None; + + // Don't allow ifs with variable declarations in conditions or C++17 + // initializer statements. + if (checkIfsHaveConditionExpression(If)) + return None; + + // Find the ranges in which initiation can be performed and verify that the + // ifs don't have any initialization expressions or condition variables. + SmallVector Ranges; + SourceLocation RangeStart = If->getLocStart(); + const IfStmt *CurrentIf = If; + const SourceManager &SM = Context.getSourceManager(); + while (true) { + const Stmt *Then = CurrentIf->getThen(); + Ranges.emplace_back(RangeStart, + findLastLocationOfSourceConstruct( + CurrentIf->getCond()->getLocEnd(), Then, SM)); + const auto *Else = CurrentIf->getElse(); + if (!Else) + break; + RangeStart = + findFirstLocationOfSourceConstruct(CurrentIf->getElseLoc(), Then, SM); + if (const auto *If = dyn_cast(Else)) { + CurrentIf = If; + continue; + } + Ranges.emplace_back(RangeStart, findLastLocationOfSourceConstruct( + CurrentIf->getElseLoc(), Else, SM)); + break; + } + + if (!isLocationInAnyRange(Location, Ranges, SM)) + return None; + + // Verify that the bodies don't have any 'break'/'default'/'case' statements. + ValidIfBodyVerifier BodyVerifier; + BodyVerifier.TraverseStmt(const_cast(If)); + if (!BodyVerifier.IsValid) + return RefactoringOperationResult( + "if's body contains a 'break'/'default'/'case' statement"); + + // FIXME: Use ASTMatchers if possible. + Optional MatchedLHSNodeID; + RHSValueSet RHSValues; + for (const IfStmt *CurrentIf = If; CurrentIf; + CurrentIf = dyn_cast_or_null(CurrentIf->getElse())) { + if (!isConditionValid(CurrentIf->getCond(), Context, MatchedLHSNodeID, + RHSValues)) + return RefactoringOperationResult("unsupported conditional expression"); + } + + RefactoringOperationResult Result; + Result.Initiated = true; + if (CreateOperation) + Result.RefactoringOp.reset(new IfSwitchConversionOperation(If)); + return Result; +} + +/// Returns the first LHS expression in the if's condition. +const Expr *getConditionFirstLHS(const Expr *E) { + auto Equals = matchBinOp(E, BO_EQ); + if (!Equals.hasValue()) { + auto LogicalOr = matchBinOp(E, BO_LOr); + if (!LogicalOr.hasValue()) + return nullptr; + return getConditionFirstLHS(LogicalOr.getValue().first); + } + return Equals.getValue().first; +} + +/// Gathers all of the RHS operands of the == expressions in the if's condition. +void gatherCaseValues(const Expr *E, + SmallVectorImpl &CaseValues) { + auto Equals = matchBinOp(E, BO_EQ); + if (Equals.hasValue()) { + CaseValues.push_back(Equals.getValue().second); + return; + } + auto LogicalOr = matchBinOp(E, BO_LOr); + if (!LogicalOr.hasValue()) + return; + gatherCaseValues(LogicalOr.getValue().first, CaseValues); + gatherCaseValues(LogicalOr.getValue().second, CaseValues); +} + +/// Return true iff the given body should be terminated with a 'break' statement +/// when used inside of a switch. +static bool isBreakNeeded(const Stmt *Body) { + const auto *CS = dyn_cast(Body); + if (!CS) + return !isa(Body); + return CS->body_empty() ? true : isBreakNeeded(CS->body_back()); +} + +/// Returns true if the given statement declares a variable. +static bool isVarDeclaringStatement(const Stmt *S) { + const auto *DS = dyn_cast(S); + if (!DS) + return false; + for (const Decl *D : DS->decls()) { + if (isa(D)) + return true; + } + return false; +} + +/// Return true if the body of an if/else if/else needs to be wrapped in braces +/// when put in a switch. +static bool areBracesNeeded(const Stmt *Body) { + const auto *CS = dyn_cast(Body); + if (!CS) + return isVarDeclaringStatement(Body); + for (const Stmt *S : CS->body()) { + if (isVarDeclaringStatement(S)) + return true; + } + return false; +} + +namespace { + +/// Information about the replacement that replaces 'if'/'else' with a 'case' or +/// a 'default'. +struct CasePlacement { + /// The location of the 'case' or 'default'. + SourceLocation CaseStartLoc; + /// True when this 'case' or 'default' statement needs a newline. + bool NeedsNewLine; + /// True if this the first 'if' in the source construct. + bool IsFirstIf; + /// True if we need to insert a 'break' to terminate the previous body + /// before the 'case' or 'default'. + bool IsBreakNeeded; + /// True if we need to insert a '}' before the case. + bool ArePreviousBracesNeeded; + + CasePlacement(SourceLocation Loc) + : CaseStartLoc(Loc), NeedsNewLine(false), IsFirstIf(true), + IsBreakNeeded(false), ArePreviousBracesNeeded(false) {} + + CasePlacement(const IfStmt *If, const SourceManager &SM, + bool AreBracesNeeded) { + CaseStartLoc = isa(If->getThen()) ? If->getThen()->getLocEnd() + : If->getElseLoc(); + SourceLocation BodyEndLoc = findLastNonCompoundLocation(If->getThen()); + NeedsNewLine = BodyEndLoc.isValid() + ? areOnSameLine(CaseStartLoc, BodyEndLoc, SM) + : false; + IsFirstIf = false; + IsBreakNeeded = isBreakNeeded(If->getThen()); + ArePreviousBracesNeeded = AreBracesNeeded; + } + + std::string getCaseReplacementString(bool IsDefault = false, + bool AreNextBracesNeeded = false) const { + if (IsFirstIf) + return ") {\ncase "; + std::string Result; + llvm::raw_string_ostream OS(Result); + if (NeedsNewLine) + OS << '\n'; + if (IsBreakNeeded) + OS << "break;\n"; + if (ArePreviousBracesNeeded) + OS << "}\n"; + OS << (IsDefault ? "default:" : "case "); + if (IsDefault && AreNextBracesNeeded) + OS << " {"; + return std::move(OS.str()); + } +}; + +} // end anonymous namespace + +static llvm::Error +addCaseReplacements(const IfStmt *If, const CasePlacement &CaseInfo, + bool &AreBracesNeeded, + std::vector &Replacements, + const SourceManager &SM, const LangOptions &LangOpts) { + SmallVector CaseValues; + gatherCaseValues(If->getCond(), CaseValues); + assert(!CaseValues.empty()); + Replacements.emplace_back( + SourceRange(CaseInfo.CaseStartLoc, CaseValues[0]->getLocStart()), + CaseInfo.getCaseReplacementString()); + + SourceLocation PrevCaseEnd = + getPreciseTokenLocEnd(CaseValues[0]->getLocEnd(), SM, LangOpts); + for (const Expr *CaseValue : llvm::makeArrayRef(CaseValues).drop_front()) { + Replacements.emplace_back( + SourceRange(PrevCaseEnd, CaseValue->getLocStart()), + StringRef(":\ncase ")); + PrevCaseEnd = getPreciseTokenLocEnd(CaseValue->getLocEnd(), SM, LangOpts); + } + + AreBracesNeeded = areBracesNeeded(If->getThen()); + StringRef ColonReplacement = AreBracesNeeded ? ": {" : ":"; + if (isa(If->getThen())) { + Replacements.emplace_back( + SourceRange( + PrevCaseEnd, + getPreciseTokenLocEnd(If->getThen()->getLocStart(), SM, LangOpts)), + ColonReplacement); + } else { + // Find the location of the if's ')' + SourceLocation End = + findClosingParenLocEnd(If->getCond()->getLocEnd(), SM, LangOpts); + if (!End.isValid()) + return llvm::make_error( + "couldn't find the location of ')'"); + Replacements.emplace_back(SourceRange(PrevCaseEnd, End), ColonReplacement); + } + return llvm::Error::success(); +} + +llvm::Expected +IfSwitchConversionOperation::perform(ASTContext &Context, + const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) { + std::vector Replacements; + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + // The first if should be replaced with a 'switch' and the text for first LHS + // should be preserved. + const Expr *LHS = getConditionFirstLHS(If->getCond()); + assert(LHS && "Missing == expression"); + Replacements.emplace_back(SourceRange(If->getLocStart(), LHS->getLocStart()), + StringRef("switch (")); + + bool AreBracesNeeded = false; + if (auto Error = addCaseReplacements( + If, + CasePlacement(getPreciseTokenLocEnd(LHS->getLocEnd(), SM, LangOpts)), + AreBracesNeeded, Replacements, SM, LangOpts)) + return std::move(Error); + + // Convert the remaining ifs to 'case' statements. + const IfStmt *CurrentIf = If; + while (true) { + const IfStmt *NextIf = dyn_cast_or_null(CurrentIf->getElse()); + if (!NextIf) + break; + if (auto Error = addCaseReplacements( + NextIf, CasePlacement(CurrentIf, SM, AreBracesNeeded), + AreBracesNeeded, Replacements, SM, LangOpts)) + return std::move(Error); + CurrentIf = NextIf; + } + + // Convert the 'else' to 'default' + if (const Stmt *Else = CurrentIf->getElse()) { + CasePlacement DefaultInfo(CurrentIf, SM, AreBracesNeeded); + AreBracesNeeded = areBracesNeeded(Else); + SourceLocation EndLoc = getPreciseTokenLocEnd( + isa(Else) ? Else->getLocStart() : CurrentIf->getElseLoc(), + SM, LangOpts); + Replacements.emplace_back(SourceRange(DefaultInfo.CaseStartLoc, EndLoc), + DefaultInfo.getCaseReplacementString( + /*IsDefault=*/true, AreBracesNeeded)); + } + + // Add the trailing break and one or two '}' if needed. + const Stmt *LastBody = + CurrentIf->getElse() ? CurrentIf->getElse() : CurrentIf->getThen(); + bool IsLastBreakNeeded = isBreakNeeded(LastBody); + SourceLocation TerminatingReplacementLoc; + std::string TerminatingReplacement; + llvm::raw_string_ostream OS(TerminatingReplacement); + if (!isa(LastBody)) { + TerminatingReplacementLoc = LastBody->getLocEnd(); + // Try to adjust the location in order to preserve any trailing comments on + // the last line of the last body. + if (!TerminatingReplacementLoc.isMacroID()) + TerminatingReplacementLoc = getLastLineLocationUnlessItHasOtherTokens( + TerminatingReplacementLoc, SM, LangOpts); + if (IsLastBreakNeeded) + OS << "\nbreak;"; + OS << "\n}"; + if (AreBracesNeeded) + OS << "\n}"; + } else { + TerminatingReplacementLoc = LastBody->getLocEnd(); + if (IsLastBreakNeeded) + OS << "break;\n"; + if (AreBracesNeeded) + OS << "}\n"; + } + + if (!OS.str().empty()) + Replacements.emplace_back( + SourceRange(TerminatingReplacementLoc, TerminatingReplacementLoc), + std::move(OS.str())); + + // TODO: verify replacements (no macro locs + ordered). + return std::move(Replacements); +} diff --git a/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp b/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp new file mode 100644 index 0000000000000..5055aac32d158 --- /dev/null +++ b/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp @@ -0,0 +1,414 @@ +//===--- ImplementDeclaredMethods.cpp - ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "Generate missing method definitions" refactoring +// operation. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringContinuations.h" +#include "RefactoringOperations.h" +#include "SourceLocationUtilities.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" + +using namespace clang; +using namespace clang::tooling; + +namespace { + +template +class ImplementDeclaredMethodsOperation : public RefactoringOperation { +public: + ImplementDeclaredMethodsOperation( + const ClassType *Container, ArrayRef SelectedMethods) + : Container(Container), + SelectedMethods(SelectedMethods.begin(), SelectedMethods.end()) {} + + const Decl *getTransformedDecl() const override { + return SelectedMethods.front(); + } + + const Decl *getLastTransformedDecl() const override { + return SelectedMethods.back(); + } + + static RefactoringOperationResult + initiate(const ClassType *Container, ArrayRef Methods, + bool CreateOperation) { + if (Methods.empty()) + return None; + + RefactoringOperationResult Result; + Result.Initiated = true; + if (!CreateOperation) + return Result; + auto Operation = llvm::make_unique(Container, Methods); + Result.RefactoringOp = std::move(Operation); + return Result; + } + + const ClassType *Container; + llvm::SmallVector SelectedMethods; +}; + +class ImplementDeclaredCXXMethodsOperation + : public ImplementDeclaredMethodsOperation< + CXXRecordDecl, CXXMethodDecl, ImplementDeclaredCXXMethodsOperation> { +public: + ImplementDeclaredCXXMethodsOperation( + const CXXRecordDecl *Container, + ArrayRef SelectedMethods) + : ImplementDeclaredMethodsOperation(Container, SelectedMethods) {} + + llvm::Expected + perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + static void addInlineBody(const CXXMethodDecl *MD, const ASTContext &Context, + std::vector &Replacements); + + static llvm::Expected + runInImplementationAST(ASTContext &Context, const FileID &File, + const CXXRecordDecl *Class, + ArrayRef SelectedMethods); +}; + +class ImplementDeclaredObjCMethodsOperation + : public ImplementDeclaredMethodsOperation< + ObjCContainerDecl, ObjCMethodDecl, + ImplementDeclaredObjCMethodsOperation> { +public: + ImplementDeclaredObjCMethodsOperation( + const ObjCContainerDecl *Container, + ArrayRef SelectedMethods) + : ImplementDeclaredMethodsOperation(Container, SelectedMethods) {} + + llvm::Expected + perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + static llvm::Expected + runInImplementationAST(ASTContext &Context, const FileID &File, + const ObjCContainerDecl *Container, + ArrayRef SelectedMethods); +}; + +/// Returns true if the given Objective-C method has an implementation. +bool isImplemented(const ObjCMethodDecl *M) { + if (M->hasBody() || M->isDefined()) + return true; + return false; +} + +} // end anonymous namespace + +RefactoringOperationResult +clang::tooling::initiateImplementDeclaredMethodsOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + // Find the selected Class. + auto SelectedDecl = Slice.innermostSelectedDecl([](const Decl *D) { + return isa(D) || isa(D) || + isa(D); + }); + if (!SelectedDecl) + return None; + // Look at the set of methods that intersect with the selection. + if (const auto *CXXClass = dyn_cast(SelectedDecl->getDecl())) { + if (CXXClass->isDependentType()) + return RefactoringOperationResult("templates are unsupported"); + llvm::SmallVector SelectedMethods; + for (const CXXMethodDecl *M : CXXClass->methods()) { + if (M->isImplicit() || M->hasBody() || M->isPure() || M->isDefaulted() || + M->isDeletedAsWritten() || M->getDescribedFunctionTemplate()) + continue; + if (Slice.isSourceRangeSelected( + CharSourceRange::getTokenRange(M->getSourceRange()))) + SelectedMethods.push_back(M); + } + return ImplementDeclaredCXXMethodsOperation::initiate( + CXXClass, SelectedMethods, CreateOperation); + } + const ObjCContainerDecl *Container = + cast(SelectedDecl->getDecl()); + llvm::SmallVector SelectedMethods; + for (const ObjCMethodDecl *M : Container->methods()) { + if (M->isImplicit() || isImplemented(M)) + continue; + if (Slice.isSourceRangeSelected( + CharSourceRange::getTokenRange(M->getSourceRange()))) + SelectedMethods.push_back(M); + } + return ImplementDeclaredObjCMethodsOperation::initiate( + Container, SelectedMethods, CreateOperation); +} + +llvm::Expected +ImplementDeclaredCXXMethodsOperation::perform( + ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { + if (Container->isLexicallyWithinFunctionOrMethod()) { + // Local methods can be implemented inline. + std::vector Replacements; + for (const CXXMethodDecl *MD : SelectedMethods) + addInlineBody(MD, Context, Replacements); + return std::move(Replacements); + } + using namespace indexer; + return continueInExternalASTUnit( + fileThatShouldContainImplementationOf(Container), runInImplementationAST, + Container, filter(llvm::makeArrayRef(SelectedMethods), + [](const DeclEntity &D) { return !D.isDefined(); })); +} + +void ImplementDeclaredCXXMethodsOperation::addInlineBody( + const CXXMethodDecl *MD, const ASTContext &Context, + std::vector &Replacements) { + SourceLocation EndLoc = MD->getLocEnd(); + SourceRange SemiRange = getRangeOfNextToken( + EndLoc, tok::semi, Context.getSourceManager(), Context.getLangOpts()); + if (SemiRange.isValid()) { + Replacements.push_back(RefactoringReplacement(SemiRange)); + EndLoc = SemiRange.getEnd(); + } + SourceLocation InsertionLoc = getLastLineLocationUnlessItHasOtherTokens( + EndLoc, Context.getSourceManager(), Context.getLangOpts()); + Replacements.push_back( + RefactoringReplacement(SourceRange(InsertionLoc, InsertionLoc), + StringRef(" { \n <#code#>;\n}"))); +} + +static const RecordDecl *findOutermostRecord(const RecordDecl *RD) { + const RecordDecl *Result = RD; + for (const DeclContext *DC = Result->getLexicalDeclContext(); + isa(DC); DC = Result->getLexicalDeclContext()) + Result = cast(DC); + return Result; +} + +static bool containsUsingOf(const NamespaceDecl *ND, + const ASTContext &Context) { + for (const Decl *D : Context.getTranslationUnitDecl()->decls()) { + if (const auto *UDD = dyn_cast(D)) { + if (UDD->getNominatedNamespace() == ND) + return true; + } + } + return false; +} + +llvm::Expected +ImplementDeclaredCXXMethodsOperation::runInImplementationAST( + ASTContext &Context, const FileID &File, const CXXRecordDecl *Class, + ArrayRef SelectedMethods) { + if (!Class) + return llvm::make_error( + "the target class is not defined in the continuation AST unit"); + + SourceManager &SM = Context.getSourceManager(); + + // Find the defined methods of the class. + llvm::SmallVector DefinedOutOfLineMethods; + for (const CXXMethodDecl *M : Class->methods()) { + if (M->isImplicit()) + continue; + if (const FunctionDecl *MD = M->getDefinition()) { + if (!MD->isOutOfLine()) + continue; + SourceLocation Loc = SM.getExpansionLoc(MD->getLocStart()); + if (SM.getFileID(Loc) == File) + DefinedOutOfLineMethods.push_back(cast(MD)); + } + } + + std::vector Replacements; + std::string MethodString; + llvm::raw_string_ostream OS(MethodString); + + // Pick a good insertion location. + SourceLocation InsertionLoc; + const CXXMethodDecl *InsertAfterMethod = nullptr; + NestedNameSpecifier *NamePrefix = nullptr; + if (DefinedOutOfLineMethods.empty()) { + const RecordDecl *OutermostRecord = findOutermostRecord(Class); + InsertionLoc = SM.getExpansionRange(OutermostRecord->getLocEnd()).second; + if (SM.getFileID(InsertionLoc) == File) { + // We can insert right after the class. Compute the appropriate + // qualification. + NamePrefix = NestedNameSpecifier::getRequiredQualification( + Context, OutermostRecord->getLexicalDeclContext(), + Class->getLexicalDeclContext()); + } else { + // We can't insert after the end of the class, since the indexer told us + // that some file should have the implementation of it, even when there + // are no methods here. We should try to insert at the end of the file. + InsertionLoc = SM.getLocForEndOfFile(File); + NamePrefix = NestedNameSpecifier::getRequiredQualification( + Context, Context.getTranslationUnitDecl(), + Class->getLexicalDeclContext()); + llvm::SmallVector Namespaces; + for (const NestedNameSpecifier *Qualifier = NamePrefix; Qualifier; + Qualifier = Qualifier->getPrefix()) { + if (const NamespaceDecl *ND = Qualifier->getAsNamespace()) + Namespaces.push_back(ND); + } + // When the class is in a namespace, add a 'using' declaration if it's + // needed and adjust the out-of-line qualification. + if (!Namespaces.empty()) { + const NamespaceDecl *InnermostNamespace = Namespaces[0]; + if (!containsUsingOf(InnermostNamespace, Context)) { + std::string NamespaceString; + llvm::raw_string_ostream NamespaceOS(NamespaceString); + for (const NamespaceDecl *ND : llvm::reverse(Namespaces)) { + if (!NamespaceOS.str().empty()) + NamespaceOS << "::"; + NamespaceOS << ND->getDeclName(); + } + OS << "\nusing namespace " << NamespaceOS.str() << ";"; + } + // Re-compute the name qualifier without the namespace. + NamePrefix = NestedNameSpecifier::getRequiredQualification( + Context, InnermostNamespace, Class->getLexicalDeclContext()); + } + } + } else { + // Insert at the end of the defined methods. + for (const CXXMethodDecl *M : DefinedOutOfLineMethods) { + SourceLocation EndLoc = SM.getExpansionRange(M->getLocEnd()).second; + if (InsertionLoc.isInvalid() || + SM.isBeforeInTranslationUnit(InsertionLoc, EndLoc)) { + InsertionLoc = EndLoc; + InsertAfterMethod = M; + } + } + } + InsertionLoc = getLastLineLocationUnlessItHasOtherTokens( + InsertionLoc, SM, Context.getLangOpts()); + + PrintingPolicy PP = Context.getPrintingPolicy(); + PP.PolishForDeclaration = true; + PP.SupressStorageClassSpecifiers = true; + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + OS << "\n"; + for (const CXXMethodDecl *MD : SelectedMethods) { + // Check if the method is already defined. + if (!MD) + continue; + + // Drop the 'virtual' specifier. + bool IsVirtual = MD->isVirtualAsWritten(); + const_cast(MD)->setVirtualAsWritten(false); + + // Drop the default arguments. + llvm::SmallVector, 4> DefaultArgs; + for (const ParmVarDecl *P : MD->parameters()) { + if (!P->hasDefaultArg()) + continue; + Expr *E = const_cast(P)->getDefaultArg(); + const_cast(P)->setDefaultArg(nullptr); + DefaultArgs.emplace_back(const_cast(P), E); + } + + // Add the nested name specifiers that are appropriate for an out-of-line + // method. + auto *Qualifier = + InsertAfterMethod + ? InsertAfterMethod->getQualifier() + : NestedNameSpecifier::Create( + Context, /*Prefix=*/NamePrefix, /*Template=*/false, + Context.getRecordType(Class).getTypePtr()); + NestedNameSpecifierLoc PrevQualifierInfo = MD->getQualifierLoc(); + const_cast(MD)->setQualifierInfo( + NestedNameSpecifierLoc(Qualifier, /*Loc=*/nullptr)); + + OS << "\n"; + MD->print(OS, PP); + OS << " { \n <#code#>;\n}\n"; + + // Restore the original method + for (const auto &DefaultArg : DefaultArgs) + DefaultArg.first->setDefaultArg(DefaultArg.second); + const_cast(MD)->setVirtualAsWritten(IsVirtual); + const_cast(MD)->setQualifierInfo(PrevQualifierInfo); + } + + Replacements.push_back(RefactoringReplacement( + SourceRange(InsertionLoc, InsertionLoc), std::move(OS.str()))); + + return std::move(Replacements); +} + +llvm::Expected +ImplementDeclaredObjCMethodsOperation::perform( + ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { + using namespace indexer; + return continueInExternalASTUnit( + fileThatShouldContainImplementationOf(Container), runInImplementationAST, + Container, filter(llvm::makeArrayRef(SelectedMethods), + [](const DeclEntity &D) { return !D.isDefined(); })); +} + +static const ObjCImplDecl * +getImplementationContainer(const ObjCContainerDecl *Container) { + if (!Container) + return nullptr; + if (const auto *ID = dyn_cast(Container)) + return ID->getImplementation(); + if (const auto *CD = dyn_cast(Container)) + return CD->getImplementation(); + return nullptr; +} + +llvm::Expected +ImplementDeclaredObjCMethodsOperation::runInImplementationAST( + ASTContext &Context, const FileID &File, const ObjCContainerDecl *Container, + ArrayRef SelectedMethods) { + const ObjCImplDecl *ImplementationContainer = + getImplementationContainer(Container); + if (!ImplementationContainer) + return llvm::make_error( + "the target @interface is not implemented in the continuation AST " + "unit"); + + std::vector Replacements; + + std::string MethodString; + llvm::raw_string_ostream OS(MethodString); + + PrintingPolicy PP = Context.getPrintingPolicy(); + PP.PolishForDeclaration = true; + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + for (const ObjCMethodDecl *MD : SelectedMethods) { + // Skip methods that are already defined. + if (!MD) + continue; + + std::string MethodDeclStr; + llvm::raw_string_ostream MethodOS(MethodDeclStr); + MD->print(MethodOS, PP); + + OS << StringRef(MethodOS.str()).drop_back(); // Drop the ';' + OS << " { \n <#code#>;\n}\n\n"; + } + SourceLocation InsertionLoc = ImplementationContainer->getLocEnd(); + + Replacements.push_back(RefactoringReplacement( + SourceRange(InsertionLoc, InsertionLoc), std::move(OS.str()))); + + return std::move(Replacements); +} diff --git a/clang/lib/Tooling/Refactor/IndexerQueries.cpp b/clang/lib/Tooling/Refactor/IndexerQueries.cpp new file mode 100644 index 0000000000000..063a27be3b83a --- /dev/null +++ b/clang/lib/Tooling/Refactor/IndexerQueries.cpp @@ -0,0 +1,136 @@ +//===--- IndexerQueries.cpp - Indexer queries -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/IndexerQuery.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace clang; +using namespace clang::tooling; +using namespace clang::tooling::indexer; +using namespace clang::tooling::indexer::detail; +using namespace llvm::yaml; + +const char *ASTProducerQuery::BaseUIDString = "ast.producer.query"; +const char *DeclarationsQuery::BaseUIDString = "decl.query"; +const char *ASTUnitForImplementationOfDeclarationQuery::NameUIDString = + "file.for.impl.of.decl"; + +const char *DeclPredicateNodePredicate::NameUIDString = "decl.predicate"; +const char *DeclPredicateNotPredicate::NameUIDString = "not.decl.predicate"; + +std::unique_ptr +DeclPredicateNode::create(const DeclPredicate &Predicate) { + return llvm::make_unique(Predicate); +} + +std::unique_ptr +DeclPredicateNode::create(const BoolDeclPredicate &Predicate) { + if (Predicate.IsInverted) + return llvm::make_unique( + create(Predicate.Predicate)); + return create(Predicate.Predicate); +} + +std::unique_ptr +clang::tooling::indexer::fileThatShouldContainImplementationOf(const Decl *D) { + return llvm::make_unique(D); +} + +namespace { + +struct QueryPredicateNode { + std::string Name; + std::vector IntegerValues; +}; + +struct QueryYAMLNode { + std::string Name; + std::vector PredicateResults; + std::string FilenameResult; +}; + +} // end anonymous namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(int) +LLVM_YAML_IS_SEQUENCE_VECTOR(QueryPredicateNode) +LLVM_YAML_IS_SEQUENCE_VECTOR(QueryYAMLNode) + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &Yaml, QueryPredicateNode &Predicate) { + Yaml.mapRequired("name", Predicate.Name); + Yaml.mapRequired("intValues", Predicate.IntegerValues); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &Yaml, QueryYAMLNode &Query) { + Yaml.mapRequired("name", Query.Name); + Yaml.mapOptional("predicateResults", Query.PredicateResults); + Yaml.mapOptional("filenameResult", Query.FilenameResult); + // FIXME: Report an error if no results are provided at all. + } +}; + +} // end namespace yaml +} // end namespace llvm + +llvm::Error +IndexerQuery::loadResultsFromYAML(StringRef Source, + ArrayRef Queries) { + std::vector QueryResults; + Input YamlIn(Source); + YamlIn >> QueryResults; + if (YamlIn.error()) + return llvm::make_error("Failed to parse query results", + YamlIn.error()); + if (QueryResults.size() != Queries.size()) + return llvm::make_error("Mismatch in query results size", + llvm::errc::invalid_argument); + for (const auto &QueryTuple : llvm::zip(Queries, QueryResults)) { + IndexerQuery *Query = std::get<0>(QueryTuple); + const QueryYAMLNode &Result = std::get<1>(QueryTuple); + if ((Query->NameUID && Query->NameUID != Result.Name) && + (Query->BaseUID && Query->BaseUID != Result.Name)) + continue; + if (auto *DQ = dyn_cast(Query)) { + const DeclPredicateNode &Predicate = DQ->getPredicateNode(); + DeclPredicate ActualPredicate(""); + bool IsNot = false; + if (const auto *Not = dyn_cast(&Predicate)) { + ActualPredicate = + cast(Not->getChild()).getPredicate(); + IsNot = true; + } else + ActualPredicate = + cast(Predicate).getPredicate(); + for (const auto &PredicateResult : Result.PredicateResults) { + if (PredicateResult.Name != ActualPredicate.Name) + continue; + std::vector> Output; + for (const auto &ResultTuple : + zip(DQ->getInputs(), PredicateResult.IntegerValues)) { + const Decl *D = std::get<0>(ResultTuple); + int Result = std::get<1>(ResultTuple); + Output.push_back(PersistentDeclRef::create( + (IsNot ? !Result : !!Result) ? D : nullptr)); + } + DQ->setOutput(std::move(Output)); + break; + } + } else if (auto *AQ = + dyn_cast(Query)) + AQ->setResult(Result.FilenameResult); + } + return llvm::Error::success(); +} diff --git a/clang/lib/Tooling/Refactor/LocalizeObjCStringLiteral.cpp b/clang/lib/Tooling/Refactor/LocalizeObjCStringLiteral.cpp new file mode 100644 index 0000000000000..d2ad11ec15366 --- /dev/null +++ b/clang/lib/Tooling/Refactor/LocalizeObjCStringLiteral.cpp @@ -0,0 +1,79 @@ +//===--- LocalizeObjCString.cpp - ----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements the "Wrap in NSLocalizedString" refactoring operation. +// +//===----------------------------------------------------------------------===// + +#include "RefactoringOperations.h" +#include "SourceLocationUtilities.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" + +using namespace clang; +using namespace clang::tooling; + +namespace { + +class LocalizeObjCStringLiteralOperation : public RefactoringOperation { +public: + LocalizeObjCStringLiteralOperation(const ObjCStringLiteral *E) : E(E) {} + + const Stmt *getTransformedStmt() const override { return E; } + + llvm::Expected perform(ASTContext &Context, const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) override; + + const ObjCStringLiteral *E; +}; + +} // end anonymous namespace + +RefactoringOperationResult +clang::tooling::initiateLocalizeObjCStringLiteralOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + const ObjCStringLiteral *E; + if (SelectionRange.isValid()) { + auto SelectedSet = Slice.getSelectedStmtSet(); + if (!SelectedSet) + return None; + E = dyn_cast_or_null( + SelectedSet->containsSelectionRange); + } else + E = cast_or_null( + Slice.nearestStmt(Stmt::ObjCStringLiteralClass)); + if (!E) + return None; + + RefactoringOperationResult Result; + Result.Initiated = true; + if (!CreateOperation) + return Result; + auto Operation = llvm::make_unique(E); + Result.RefactoringOp = std::move(Operation); + return Result; +} + +llvm::Expected +LocalizeObjCStringLiteralOperation::perform(ASTContext &Context, + const Preprocessor &ThePreprocessor, + const RefactoringOptionSet &Options, + unsigned SelectedCandidateIndex) { + std::vector Replacements; + SourceLocation LocStart = E->getLocStart(); + Replacements.emplace_back(SourceRange(LocStart, LocStart), + StringRef("NSLocalizedString(")); + SourceLocation LocEnd = getPreciseTokenLocEnd( + E->getLocEnd(), Context.getSourceManager(), Context.getLangOpts()); + Replacements.emplace_back(SourceRange(LocEnd, LocEnd), StringRef(", @\"\")")); + return std::move(Replacements); +} diff --git a/clang/lib/Tooling/Refactor/RefactoringActionFinder.cpp b/clang/lib/Tooling/Refactor/RefactoringActionFinder.cpp new file mode 100644 index 0000000000000..2fd7cd5c137a1 --- /dev/null +++ b/clang/lib/Tooling/Refactor/RefactoringActionFinder.cpp @@ -0,0 +1,57 @@ +//===--- RefactoringActionFinder.cpp - Clang refactoring library ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Implements methods that find the refactoring actions that can be +/// performed at specific locations / source ranges in a translation unit. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/RefactoringActionFinder.h" +#include "clang/Tooling/Refactor/RefactoringOperation.h" +#include "clang/Tooling/Refactor/USRFinder.h" + +namespace clang { +namespace tooling { + +RefactoringActionSet findActionSetAt(SourceLocation Location, + SourceRange SelectionRange, + ASTContext &Context) { + RefactoringActionSet Result; + if (const auto *ND = rename::getNamedDeclAt(Context, Location)) + Result.Actions.push_back(isLocalSymbol(ND, Context.getLangOpts()) + ? RefactoringActionType::Rename_Local + : RefactoringActionType::Rename); + + // FIXME: We can avoid checking if some actions can be initiated when they're + // not allowed in the current language mode. + RefactoringActionType Actions[] = { +#define REFACTORING_OPERATION_ACTION(Name, Spelling, Command) \ + RefactoringActionType::Name, +#include "clang/Tooling/Refactor/RefactoringActions.def" + }; + + for (auto Action : Actions) { + auto Op = initiateRefactoringOperationAt(Location, SelectionRange, Context, + Action, + /*CreateOperation=*/true); + if (Op.Initiated) { + Result.Actions.push_back(Action); + if (Op.RefactoringOp) { + for (const auto &SubAction : Op.RefactoringOp->getAvailableSubActions()) + Result.Actions.push_back(SubAction); + } + } + } + + return Result; +} + +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/RefactoringActions.cpp b/clang/lib/Tooling/Refactor/RefactoringActions.cpp new file mode 100644 index 0000000000000..4b2b735b3d54c --- /dev/null +++ b/clang/lib/Tooling/Refactor/RefactoringActions.cpp @@ -0,0 +1,30 @@ +//===--- RefactoringActions.cpp - Clang refactoring library ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Contains a list of all the supported refactoring actions. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/RefactoringActions.h" + +namespace clang { +namespace tooling { + +StringRef getRefactoringActionTypeName(RefactoringActionType Action) { + switch (Action) { +#define REFACTORING_ACTION(Name, Spelling) \ + case RefactoringActionType::Name: \ + return Spelling; +#include "clang/Tooling/Refactor/RefactoringActions.def" + } +} + +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/RefactoringContinuations.h b/clang/lib/Tooling/Refactor/RefactoringContinuations.h new file mode 100644 index 0000000000000..25c3a39f89428 --- /dev/null +++ b/clang/lib/Tooling/Refactor/RefactoringContinuations.h @@ -0,0 +1,351 @@ +//===--- RefactoringContinuations.h - Defines refactoring continuations ---===// +// +// 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_TOOLING_REFACTOR_REFACTORING_CONTINUATIONS_H +#define LLVM_CLANG_LIB_TOOLING_REFACTOR_REFACTORING_CONTINUATIONS_H + +#include "clang/AST/Decl.h" +#include "clang/Tooling/Refactor/IndexerQuery.h" +#include "clang/Tooling/Refactor/RefactoringOperation.h" +#include "llvm/ADT/StringMap.h" +#include + +namespace clang { +namespace tooling { + +namespace detail { + +struct ValidBase {}; + +/// The ContinuationPassType determine which type is passed into the refactoring +/// continuation. +template struct ContinuationPassType { using Type = T; }; + +template struct ContinuationPassType> { + using Type = ArrayRef; +}; + +/// Refactoring operations can pass state to the continuations. Valid state +/// values should have a corresponding \c StateTraits specialization. +template struct StateTraits { + /// Specializations should define the following types: + /// + /// StoredResultType: The TU-specific type which is then passed into the + /// continuation function. The continuation receives the result whose type is + /// \c ContinuationPassType::Type. + /// + /// PersistentType: The TU-independent type that's persisted even after the + /// TU in which the continuation was created is disposed. +}; + +template +struct StateTraits + : std::enable_if::value, ValidBase>::type { + using StoredResultType = const T *; + using PersistentType = PersistentDeclRef; +}; + +template +struct StateTraits> + : std::enable_if::value, ValidBase>::type { + using StoredResultType = std::vector; + using PersistentType = std::vector>; +}; + +template +struct StateTraits>> + : std::enable_if::value, ValidBase>::type { + using StoredResultType = std::vector; + using PersistentType = std::vector>; +}; + +/// Conversion functions convert the TU-specific state to a TU independent +/// state and vice-versa. +template +PersistentDeclRef convertToPersistentRepresentation( + const T *Declaration, + typename std::enable_if::value>::type * = + nullptr) { + return PersistentDeclRef::create(Declaration); +} + +template +std::vector> convertToPersistentRepresentation( + ArrayRef Declarations, + typename std::enable_if::value>::type * = + nullptr) { + std::vector> Result; + Result.reserve(Declarations.size()); + for (const T *D : Declarations) + Result.push_back(PersistentDeclRef::create(D)); + return Result; +} + +template +std::vector> convertToPersistentRepresentation( + std::unique_ptr> &Query, + typename std::enable_if::value>::type * = + nullptr) { + Query->invalidateTUSpecificState(); + return Query->getOutput(); +} + +/// Converts the TU-independent state to the TU-specific state. +class PersistentToASTSpecificStateConverter { + ASTContext &Context; + llvm::StringMap ConvertedDeclRefs; + + const Decl *lookupDecl(StringRef USR); + +public: + // FIXME: We can hide the addConvertible/convert interface so that + // the continuation will just invoke one conversion function for the entire + // tuple. + PersistentToASTSpecificStateConverter(ASTContext &Context) + : Context(Context) {} + + template + bool addConvertible( + const PersistentDeclRef &Ref, + typename std::enable_if::value>::type * = + nullptr) { + ConvertedDeclRefs[Ref.USR] = nullptr; + return true; + } + + template + const T * + convert(const PersistentDeclRef &Ref, + typename std::enable_if::value>::type * = + nullptr) { + return dyn_cast_or_null(lookupDecl(Ref.USR)); + } + + template + bool addConvertible( + const std::vector> &Refs, + typename std::enable_if::value>::type * = + nullptr) { + for (const auto &Ref : Refs) + ConvertedDeclRefs[Ref.USR] = nullptr; + return true; + } + + template + std::vector + convert(const std::vector> &Refs, + typename std::enable_if::value>::type * = + nullptr) { + std::vector Results; + Results.reserve(Refs.size()); + // Allow nulls in the produced array, the continuation will have to deal + // with them by itself. + for (const auto &Ref : Refs) + Results.push_back(dyn_cast_or_null(lookupDecl(Ref.USR))); + return Results; + } + + bool addConvertible(const PersistentFileID &) { + // Do nothing since FileIDs are converted one-by-one. + return true; + } + + FileID convert(const PersistentFileID &Ref); + + /// Converts the added persistent state into TU-specific state using one + /// efficient operation. + void runCoalescedConversions(); +}; + +template +struct ContinuationFunction { + using Type = llvm::Expected (*)( + ASTContext &, const T &, + typename ContinuationPassType< + typename StateTraits::StoredResultType>::Type...); + + template + static llvm::Expected dispatch( + Type Fn, detail::PersistentToASTSpecificStateConverter &Converter, + ASTContext &Context, const ASTQueryType &Query, + const std::tuple::StoredResultType...> + &Arguments, + llvm::index_sequence) { + auto ASTQueryResult = Converter.convert(Query.getResult()); + return Fn(Context, ASTQueryResult, std::get(Arguments)...); + } +}; + +template +struct ContinuationFunction { + using Type = llvm::Expected (*)( + ASTContext &, + typename ContinuationPassType< + typename StateTraits::StoredResultType>::Type...); + + template + static llvm::Expected dispatch( + Type Fn, detail::PersistentToASTSpecificStateConverter &, + ASTContext &Context, const ASTQueryType &, + const std::tuple::StoredResultType...> + &Arguments, + llvm::index_sequence) { + return Fn(Context, std::get(Arguments)...); + } +}; + +/// The refactoring contination contains a set of structures that implement +/// the refactoring operation continuation mechanism. +template +class SpecificRefactoringContinuation final : public RefactoringContinuation { +public: + static_assert(std::is_base_of::value, + "Invalid AST Query"); + // TODO: Validate the QueryOrState types. + + /// The consumer function is the actual continuation. It receives the state + /// that was passed-in in the request or the results of the indexing queries + /// that were passed-in in the request. + using ConsumerFn = + typename ContinuationFunction::Type; + +private: + ConsumerFn Consumer; + std::unique_ptr ASTQuery; + /// Inputs store state that's dependent on the original TU. + llvm::Optional> Inputs; + /// State contains TU-independent values. + llvm::Optional< + std::tuple::PersistentType...>> + State; + + /// Converts a tuple that contains the TU dependent state to a tuple with + /// TU independent state. + template + std::tuple::PersistentType...> + convertToPersistentImpl(llvm::index_sequence) { + assert(Inputs && "TU-dependent state is already converted"); + return std::make_tuple( + detail::convertToPersistentRepresentation(std::get(*Inputs))...); + } + + template + bool gatherQueries( + std::vector &Queries, + const std::unique_ptr &Query, + typename std::enable_if< + std::is_base_of::value>::type * = nullptr) { + Queries.push_back(Query.get()); + return true; + } + + template + bool gatherQueries(std::vector &, const T &) { + // This input element is not a query. + return true; + } + + template + std::vector + gatherQueries(llvm::index_sequence) { + assert(Inputs && "TU-dependent state is already converted"); + std::vector Queries; + std::make_tuple(gatherQueries(Queries, std::get(*Inputs))...); + return Queries; + } + + /// Calls the consumer function with the given \p Context and the state + /// whose values are converted from the TU-independent to TU-specific values. + template + llvm::Expected dispatch(ASTContext &Context, + llvm::index_sequence Seq) { + assert(State && "TU-independent state is not yet produced"); + detail::PersistentToASTSpecificStateConverter Converter(Context); + (void)std::make_tuple(Converter.addConvertible(std::get(*State))...); + Converter.runCoalescedConversions(); + auto Results = std::make_tuple(Converter.convert(std::get(*State))...); + // TODO: Check for errors? + return detail::ContinuationFunction< + typename ASTQueryType::ResultTy, ASTQueryType, + QueryOrState...>::dispatch(Consumer, Converter, Context, *ASTQuery, + Results, Seq); + } + +public: + SpecificRefactoringContinuation(ConsumerFn Consumer, + std::unique_ptr ASTQuery, + QueryOrState... Inputs) + : Consumer(Consumer), ASTQuery(std::move(ASTQuery)), + Inputs(std::make_tuple(std::move(Inputs)...)) {} + + SpecificRefactoringContinuation(SpecificRefactoringContinuation &&) = default; + SpecificRefactoringContinuation & + operator=(SpecificRefactoringContinuation &&) = default; + + indexer::ASTProducerQuery *getASTUnitIndexerQuery() override { + return ASTQuery.get(); + } + + std::vector getAdditionalIndexerQueries() override { + return gatherQueries(llvm::index_sequence_for()); + } + + /// Query results are fetched. State is converted to a persistent + /// representation. + void persistTUSpecificState() override { + ASTQuery->invalidateTUSpecificState(); + State = + convertToPersistentImpl(llvm::index_sequence_for()); + Inputs = None; + } + + /// The state is converted to the AST representation in the given ASTContext + /// and the continuation is dispatched. + llvm::Expected + runInExternalASTUnit(ASTContext &Context) override { + return dispatch(Context, llvm::index_sequence_for()); + } +}; + +} // end namespace detail + +/// Returns a refactoring continuation that will run within the context of a +/// single external AST unit. +/// +/// The indexer determines which AST unit should receive the continuation by +/// evaluation the AST query operation \p ASTQuery. +/// +/// \param ASTQuery The query that will determine which AST unit should the +/// continuation run in. +/// +/// \param Consumer The continuation function that will be called once the +/// external AST unit is loaded. +/// +/// \param Inputs Each individiual input element can contain either some +/// state value that will be passed into the \p Consumer function or an +/// indexer query whose results will be passed into the \p Consumer function. +template +typename std::enable_if< + std::is_base_of::value, + std::unique_ptr>::type +continueInExternalASTUnit( + std::unique_ptr ASTQuery, + typename detail::SpecificRefactoringContinuation< + ASTQueryType, QueryOrState...>::ConsumerFn Consumer, + QueryOrState... Inputs) { + return llvm::make_unique< + detail::SpecificRefactoringContinuation>( + Consumer, std::move(ASTQuery), std::move(Inputs)...); +} + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTOR_REFACTORING_CONTINUATIONS_H diff --git a/clang/lib/Tooling/Refactor/RefactoringOperation.cpp b/clang/lib/Tooling/Refactor/RefactoringOperation.cpp new file mode 100644 index 0000000000000..a9f431f98d0a6 --- /dev/null +++ b/clang/lib/Tooling/Refactor/RefactoringOperation.cpp @@ -0,0 +1,93 @@ +//===--- RefactoringOperation.cpp - Defines a refactoring operation -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/RefactoringOperation.h" +#include "ASTSlice.h" +#include "RefactoringOperations.h" +#include "SourceLocationUtilities.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/Tooling/Refactor/SymbolOperation.h" +#include "clang/Tooling/Refactor/USRFinder.h" +#include "llvm/Support/Errc.h" + +using namespace clang; +using namespace clang::tooling; + +char RefactoringOperationError::ID; + +void RefactoringOperationError::log(raw_ostream &OS) const { + OS << "Refactoring operation failed: " << FailureReason; +} + +std::error_code RefactoringOperationError::convertToErrorCode() const { + return make_error_code(llvm::errc::operation_not_permitted); +} + +RefactoringOperationResult clang::tooling::initiateRefactoringOperationAt( + SourceLocation Location, SourceRange SelectionRange, ASTContext &Context, + RefactoringActionType ActionType, bool CreateOperation) { + if (Location.isInvalid()) + return None; + if (ActionType == RefactoringActionType::Rename || + ActionType == RefactoringActionType::Rename_Local) { + const NamedDecl *FoundDecl = rename::getNamedDeclAt(Context, Location); + if (!FoundDecl) + return None; + RefactoringOperationResult Result; + Result.Initiated = true; + if (CreateOperation) + Result.SymbolOp = llvm::make_unique(FoundDecl, Context); + return Result; + } + SourceManager &SM = Context.getSourceManager(); + if (Location.isMacroID()) + Location = SM.getSpellingLoc(Location); + assert(Location.isFileID() && "Invalid location"); + + // TODO: Don't perform duplicate work when initiateRefactoringOperationAt is + // called from findRefactoringActionsAt. + if (SelectionRange.isValid()) { + if (SelectionRange.getBegin().isMacroID() || + SelectionRange.getEnd().isMacroID()) + SelectionRange = SourceRange(SM.getSpellingLoc(SelectionRange.getBegin()), + SM.getSpellingLoc(SelectionRange.getEnd())); + SelectionRange = trimSelectionRange( + SelectionRange, Context.getSourceManager(), Context.getLangOpts()); + } + ASTSlice Slice(Location, SelectionRange, Context); + + switch (ActionType) { +#define REFACTORING_OPERATION_ACTION(Name, Spelling, Command) \ + case RefactoringActionType::Name: \ + return initiate##Name##Operation(Slice, Context, Location, SelectionRange, \ + CreateOperation); +#define REFACTORING_OPERATION_SUB_ACTION(Name, Parent, Spelling, Command) \ + case RefactoringActionType::Parent##_##Name: \ + return initiate##Parent##Name##Operation(Slice, Context, Location, \ + SelectionRange, CreateOperation); +#include "clang/Tooling/Refactor/RefactoringActions.def" + default: + break; + } + return RefactoringOperationResult(); +} + +RefactoringOperationResult clang::tooling::initiateRefactoringOperationOnDecl( + StringRef DeclUSR, ASTContext &Context, RefactoringActionType ActionType) { + if (ActionType != RefactoringActionType::Rename) + return None; + const NamedDecl *FoundDecl = rename::getNamedDeclWithUSR(Context, DeclUSR); + if (!FoundDecl) + return None; + RefactoringOperationResult Result; + Result.Initiated = true; + Result.SymbolOp = llvm::make_unique(FoundDecl, Context); + return Result; +} diff --git a/clang/lib/Tooling/Refactor/RefactoringOperations.h b/clang/lib/Tooling/Refactor/RefactoringOperations.h new file mode 100644 index 0000000000000..e02a3cf0b3df4 --- /dev/null +++ b/clang/lib/Tooling/Refactor/RefactoringOperations.h @@ -0,0 +1,37 @@ +//===--- RefactoringOperations.h - The supported refactoring operations ---===// +// +// 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_TOOLING_REFACTOR_REFACTORINGOPERATIONS_H +#define LLVM_CLANG_LIB_TOOLING_REFACTOR_REFACTORINGOPERATIONS_H + +#include "ASTSlice.h" +#include "clang/Tooling/Refactor/RefactoringOperation.h" + +namespace clang { + +class Expr; +class IfStmt; +class VarDecl; + +namespace tooling { + +#define REFACTORING_OPERATION_ACTION(Name, Spelling, Command) \ + RefactoringOperationResult initiate##Name##Operation( \ + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, \ + SourceRange SelectionRange, bool CreateOperation = true); +#define REFACTORING_OPERATION_SUB_ACTION(Name, Parent, Spelling, Command) \ + RefactoringOperationResult initiate##Parent##Name##Operation( \ + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, \ + SourceRange SelectionRange, bool CreateOperation = true); +#include "clang/Tooling/Refactor/RefactoringActions.def" + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTOR_REFACTORINGOPERATIONS_H diff --git a/clang/lib/Tooling/Refactor/RefactoringOptions.cpp b/clang/lib/Tooling/Refactor/RefactoringOptions.cpp new file mode 100644 index 0000000000000..fd2d8d1583a6a --- /dev/null +++ b/clang/lib/Tooling/Refactor/RefactoringOptions.cpp @@ -0,0 +1,65 @@ +//===--- RefactoringOptions.cpp - A set of all the refactoring options ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/RefactoringOptions.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace clang; +using namespace clang::tooling; +using namespace clang::tooling::option; +using namespace llvm::yaml; + +void RefactoringOptionSet::print(llvm::raw_ostream &OS) const { + Output YamlOut(OS); + if (YamlOut.preflightDocument(0)) { + YamlOut.beginFlowMapping(); + for (const auto &Option : Options) + Option.getValue()->serialize(YamlOut); + YamlOut.endFlowMapping(); + YamlOut.postflightDocument(); + } +} + +template <> struct CustomMappingTraits { + static void inputOne(IO &YamlIn, StringRef Key, + RefactoringOptionSet &Result) { +#define HANDLE(Type) \ + if (Key == Type::Name) { \ + Type Value; \ + Value.serialize(YamlIn); \ + Result.add(Value); \ + return; \ + } + HANDLE(AvoidTextualMatches) +#undef HANDLE + YamlIn.setError(Twine("Unknown refactoring option ") + Key); + } + static void output(IO &, RefactoringOptionSet &) { + llvm_unreachable("Output is done without mapping traits"); + } +}; + +llvm::Expected +RefactoringOptionSet::parse(StringRef Source) { + Input YamlIn(Source); + // FIXME: Don't dump errors to stderr. + RefactoringOptionSet Result; + YamlIn >> Result; + if (YamlIn.error()) + return llvm::make_error("Failed to parse the option set", + YamlIn.error()); + return std::move(Result); +} + +void RefactoringOption::serialize(const SerializationContext &) {} + +void clang::tooling::option::detail::BoolOptionBase::serializeImpl( + const SerializationContext &Context, const char *Name) { + Context.IO.mapRequired(Name, Value); +} diff --git a/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp b/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp new file mode 100644 index 0000000000000..5ec491c557b6b --- /dev/null +++ b/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp @@ -0,0 +1,562 @@ +//===--- RenameIndexedFile.cpp - ------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/RenameIndexedFile.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/Refactor/RefactoringOptions.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Path.h" + +using namespace clang; + +namespace clang { +namespace tooling { +namespace rename { + +IndexedFileOccurrenceProducer::IndexedFileOccurrenceProducer( + ArrayRef Symbols, IndexedFileOccurrenceConsumer &Consumer, + const RefactoringOptionSet *Options) + : Symbols(Symbols), Consumer(Consumer), Options(Options) { + IsMultiPiece = false; + for (const auto &Symbol : Symbols) { + if (Symbol.Name.size() > 1) { + IsMultiPiece = true; + break; + } + } + if (IsMultiPiece) { + for (const auto &Symbol : Symbols) { + (void)Symbol; + assert(Symbol.Name.size() > 1 && + "Mixed multi-piece and single piece symbols " + "are unsupported"); + } + } +} + +namespace { + +enum class MatchKind { SourceMatch, MacroExpansion, None }; + +} // end anonymous namespace + +static MatchKind checkOccurrence(const IndexedOccurrence &Occurrence, + const IndexedSymbol &Symbol, + const SourceManager &SM, + const LangOptions &LangOpts, + SourceLocation &BeginLoc) { + if (!Occurrence.Line || !Occurrence.Column) + return MatchKind::None; // Ignore any invalid indexed locations. + + // Ensure that the first string in the name is present at the given + // location. + BeginLoc = SM.translateLineCol(SM.getMainFileID(), Occurrence.Line, + Occurrence.Column); + if (BeginLoc.isInvalid()) + return MatchKind::None; + StringRef SymbolNameStart = Symbol.Name[0]; + SourceLocation EndLoc = BeginLoc.getLocWithOffset(SymbolNameStart.size()); + if (!SM.isBeforeInTranslationUnit(BeginLoc, EndLoc)) { + // Ignore any invalid source ranges. This can occur if the indexed + // location is invalid. + return MatchKind::None; + } + // Extract the token at the location. + auto DecomposedLoc = SM.getDecomposedLoc(BeginLoc); + const llvm::MemoryBuffer *File = SM.getBuffer(DecomposedLoc.first); + Lexer RawLex( + BeginLoc, LangOpts, File->getBufferStart() + DecomposedLoc.second, + File->getBufferStart() + DecomposedLoc.second, File->getBufferEnd()); + Token Tok; + RawLex.LexFromRawLexer(Tok); + if (Tok.isNot(tok::raw_identifier) || Tok.getLocation() != BeginLoc) + return MatchKind::None; + return Tok.getRawIdentifier() == SymbolNameStart ? MatchKind::SourceMatch + : MatchKind::MacroExpansion; +} + +static void +findObjCMultiPieceSelectorOccurrences(CompilerInstance &CI, + ArrayRef Symbols, + IndexedFileOccurrenceConsumer &Consumer); + +namespace { + +struct TextualMatchOccurrence { + SourceLocation Location; + unsigned SymbolIndex; +}; + +/// Finds '@selector' expressions by looking at tokens one-by-one. +class SelectorParser { + enum ParseState { + None, + At, + Selector, + ExpectingSelectorPiece, + ExpectingColon, + ExpectingRParenOrColon, + ExpectingRParen, + Success + }; + ParseState State = None; + const SymbolName &Name; + + ParseState stateForToken(const Token &RawTok); + +public: + unsigned SymbolIndex; + llvm::SmallVector SelectorLocations; + + SelectorParser(const SymbolName &Name, unsigned SymbolIndex) + : Name(Name), SymbolIndex(SymbolIndex) {} + + /// Returns true if the parses has found a '@selector' expression. + bool handleToken(const Token &RawTok); +}; + +class InclusionLexer final : public Lexer { +public: + InclusionLexer(SourceLocation FileLoc, const LangOptions &LangOpts, + const char *BufStart, const char *BufEnd) + : Lexer(FileLoc, LangOpts, BufStart, BufStart, BufEnd) {} + + void IndirectLex(Token &Result) override { LexFromRawLexer(Result); } +}; + +} // end anonymous namespace + +SelectorParser::ParseState SelectorParser::stateForToken(const Token &RawTok) { + assert(RawTok.isNot(tok::comment) && "unexpected comment token"); + switch (State) { + case None: + break; + case At: + if (RawTok.is(tok::raw_identifier) && + RawTok.getRawIdentifier() == "selector") + return Selector; + break; + case Selector: + if (RawTok.isNot(tok::l_paren)) + break; + SelectorLocations.clear(); + return ExpectingSelectorPiece; + case ExpectingSelectorPiece: + assert(SelectorLocations.size() < Name.size() && + "Expecting invalid selector piece"); + if (RawTok.isNot(tok::raw_identifier) || + RawTok.getRawIdentifier() != Name[SelectorLocations.size()]) + break; + SelectorLocations.push_back(RawTok.getLocation()); + if (SelectorLocations.size() == Name.size()) { + // We found the selector that we were looking for, now check for ')'. + return ExpectingRParenOrColon; + } + return ExpectingColon; + case ExpectingColon: + if (RawTok.is(tok::colon)) + return ExpectingSelectorPiece; + break; + case ExpectingRParenOrColon: + if (RawTok.is(tok::colon)) + return ExpectingRParen; + // Fallthrough + case ExpectingRParen: + if (RawTok.is(tok::r_paren)) { + // We found the selector that we were looking for. + return Success; + } + break; + case Success: + llvm_unreachable("should not get here"); + } + // Look for the start of the selector expression. + return RawTok.is(tok::at) ? At : None; +} + +bool SelectorParser::handleToken(const Token &RawTok) { + State = stateForToken(RawTok); + if (State != Success) + return false; + State = None; + return true; +} + +static void collectTextualMatchesInComment( + ArrayRef Symbols, SourceLocation CommentLoc, + StringRef Comment, llvm::SmallVectorImpl &Result) { + for (const auto &Symbol : llvm::enumerate(Symbols)) { + size_t Offset = 0; + while (true) { + Offset = Comment.find(Symbol.value().Name[0], /*From=*/Offset); + if (Offset == StringRef::npos) + break; + Result.push_back( + {CommentLoc.getLocWithOffset(Offset), (unsigned)Symbol.index()}); + Offset += Symbol.value().Name[0].size(); + } + } +} + +/// Lex the comment to figure out if textual matches in a comment are standalone +/// tokens. +static void findTextualMatchesInComment( + const SourceManager &SM, const LangOptions &LangOpts, + ArrayRef Symbols, + ArrayRef TextualMatches, SourceRange CommentRange, + llvm::function_ref Locations, + unsigned SymbolIndex)> + MatchHandler) { + std::string Source = + Lexer::getSourceText(CharSourceRange::getCharRange(CommentRange), SM, + LangOpts) + .str(); + SymbolOccurrence::OccurrenceKind Kind = + RawComment(SM, CommentRange, /*Merged=*/false, /*ParseAllComments=*/false) + .isDocumentation() + ? SymbolOccurrence::MatchingDocComment + : SymbolOccurrence::MatchingComment; + // Replace some special characters with ' ' to avoid comments and literals. + std::replace_if( + Source.begin(), Source.end(), + [](char c) -> bool { return c == '/' || c == '"' || c == '\''; }, ' '); + Lexer RawLex(CommentRange.getBegin(), LangOpts, Source.c_str(), + Source.c_str(), Source.c_str() + Source.size()); + Token RawTok; + RawLex.LexFromRawLexer(RawTok); + while (RawTok.isNot(tok::eof)) { + auto It = std::find_if(TextualMatches.begin(), TextualMatches.end(), + [&](const TextualMatchOccurrence &Match) { + return Match.Location == RawTok.getLocation(); + }); + if (It != TextualMatches.end()) { + StringRef TokenName = + Lexer::getSourceText(CharSourceRange::getCharRange( + RawTok.getLocation(), RawTok.getEndLoc()), + SM, LangOpts); + // Only report matches that are identical to the symbol. When dealing with + // multi-piece selectors we only look for the first selector piece as we + // assume that textual matches correspond to a match of the first selector + // piece. + if (TokenName == Symbols[It->SymbolIndex].Name[0]) + MatchHandler(Kind, It->Location, It->SymbolIndex); + } + RawLex.LexFromRawLexer(RawTok); + } +} + +static void findMatchingTextualOccurrences( + const SourceManager &SM, const LangOptions &LangOpts, + ArrayRef Symbols, + llvm::function_ref Locations, + unsigned SymbolIndex)> + MatchHandler) { + const llvm::MemoryBuffer *FromFile = SM.getBuffer(SM.getMainFileID()); + Lexer RawLex(SM.getMainFileID(), FromFile, SM, LangOpts); + RawLex.SetCommentRetentionState(true); + + llvm::SmallVector CommentMatches; + llvm::SmallVector SelectorParsers; + for (const auto &Symbol : llvm::enumerate(Symbols)) { + if (Symbol.value().IsObjCSelector) + SelectorParsers.push_back( + SelectorParser(Symbol.value().Name, Symbol.index())); + } + + Token RawTok; + RawLex.LexFromRawLexer(RawTok); + while (RawTok.isNot(tok::eof)) { + if (RawTok.is(tok::comment)) { + SourceRange Range(RawTok.getLocation(), RawTok.getEndLoc()); + StringRef Comment = Lexer::getSourceText( + CharSourceRange::getCharRange(Range), SM, LangOpts); + collectTextualMatchesInComment(Symbols, Range.getBegin(), Comment, + CommentMatches); + if (!CommentMatches.empty()) { + findTextualMatchesInComment(SM, LangOpts, Symbols, CommentMatches, + Range, MatchHandler); + CommentMatches.clear(); + } + } else if (!SelectorParsers.empty()) { + for (auto &Parser : SelectorParsers) { + if (Parser.handleToken(RawTok)) + MatchHandler(SymbolOccurrence::MatchingSelector, + Parser.SelectorLocations, Parser.SymbolIndex); + } + } + RawLex.LexFromRawLexer(RawTok); + } +} + +static void findInclusionDirectiveOccurrence( + const IndexedOccurrence &Occurrence, const IndexedSymbol &Symbol, + unsigned SymbolIndex, SourceManager &SM, const LangOptions &LangOpts, + IndexedFileOccurrenceConsumer &Consumer) { + if (!Occurrence.Line || !Occurrence.Column) + return; // Ignore any invalid indexed locations. + + SourceLocation Loc = SM.translateLineCol(SM.getMainFileID(), Occurrence.Line, + Occurrence.Column); + if (Loc.isInvalid()) + return; + unsigned Offset = SM.getDecomposedLoc(Loc).second; + const llvm::MemoryBuffer *File = SM.getBuffer(SM.getMainFileID()); + + InclusionLexer RawLex(Loc, LangOpts, File->getBufferStart() + Offset, + File->getBufferEnd()); + Token RawTok; + RawLex.LexFromRawLexer(RawTok); + if (RawTok.isNot(tok::hash)) + return; + // include/import + RawLex.LexFromRawLexer(RawTok); + if (RawTok.isNot(tok::raw_identifier)) + return; + // string literal/angled literal. + RawLex.setParsingPreprocessorDirective(true); + RawLex.LexIncludeFilename(RawTok); + if (RawTok.isNot(tok::string_literal) && + RawTok.isNot(tok::angle_string_literal)) + return; + StringRef Filename = llvm::sys::path::filename( + StringRef(RawTok.getLiteralData(), RawTok.getLength()) + .drop_front() + .drop_back()); + size_t NameOffset = Filename.rfind_lower(Symbol.Name[0]); + if (NameOffset == StringRef::npos) + return; + SymbolOccurrence Result( + SymbolOccurrence::MatchingFilename, + /*IsMacroExpansion=*/false, SymbolIndex, + RawTok.getLocation().getLocWithOffset( + NameOffset + (Filename.data() - RawTok.getLiteralData()))); + Consumer.handleOccurrence(Result, SM, LangOpts); +} + +void IndexedFileOccurrenceProducer::ExecuteAction() { + Preprocessor &PP = getCompilerInstance().getPreprocessor(); + PP.EnterMainSourceFile(); + + SourceManager &SM = getCompilerInstance().getSourceManager(); + const LangOptions &LangOpts = getCompilerInstance().getLangOpts(); + if (IsMultiPiece) { + findObjCMultiPieceSelectorOccurrences(getCompilerInstance(), Symbols, + Consumer); + } else { + for (const auto &Symbol : llvm::enumerate(Symbols)) { + for (const IndexedOccurrence &Occurrence : + Symbol.value().IndexedOccurrences) { + if (Occurrence.Kind == IndexedOccurrence::InclusionDirective) { + findInclusionDirectiveOccurrence(Occurrence, Symbol.value(), + Symbol.index(), SM, LangOpts, + Consumer); + continue; + } + SourceLocation BeginLoc; + MatchKind Match = + checkOccurrence(Occurrence, Symbol.value(), SM, LangOpts, BeginLoc); + if (Match == MatchKind::None) + continue; + + SymbolOccurrence Result(SymbolOccurrence::MatchingSymbol, + /*IsMacroExpansion=*/Match == + MatchKind::MacroExpansion, + Symbol.index(), BeginLoc); + Consumer.handleOccurrence(Result, SM, LangOpts); + } + } + } + + if (Options && Options->get(option::AvoidTextualMatches())) + return; + findMatchingTextualOccurrences( + SM, LangOpts, Symbols, + [&](SymbolOccurrence::OccurrenceKind Kind, + ArrayRef Locations, unsigned SymbolIndex) { + SymbolOccurrence Result(Kind, /*IsMacroExpansion=*/false, SymbolIndex, + Locations); + Consumer.handleOccurrence(Result, SM, LangOpts); + }); +} + +namespace { + +/// Maps from source locations to the indexed occurrences. +typedef llvm::DenseMap> + SourceLocationsToIndexedOccurrences; + +enum class ObjCSymbolSelectorKind { MessageSend, MethodDecl }; + +} // end anonymous namespace + +static bool +findObjCSymbolSelectorPieces(ArrayRef Tokens, const SymbolName &Name, + SmallVectorImpl &Pieces, + ObjCSymbolSelectorKind Kind) { + assert(!Tokens.empty() && "no tokens"); + assert(Tokens[0].getRawIdentifier() == Name[0]); + assert(Name.size() > 1); + assert(Pieces.empty()); + + Pieces.push_back(Tokens[0].getLocation()); + + // We have to track square brackets, parens and braces as we want to skip the + // tokens inside them. This ensures that we don't use identical selector + // pieces in inner message sends, blocks, lambdas and @selector expressions. + unsigned SquareCount = 0; + unsigned ParenCount = 0; + unsigned BraceCount = 0; + + // Start looking for the next selector piece. + unsigned Last = Tokens.size() - 1; + // Skip the ':' or any other token after the first selector piece token. + for (unsigned Index = 2; Index < Last; ++Index) { + const auto &Tok = Tokens[Index]; + + bool NoScoping = SquareCount == 0 && BraceCount == 0 && ParenCount == 0; + if (NoScoping && Tok.is(tok::raw_identifier) && + Tokens[Index + 1].is(tok::colon) && + Tok.getRawIdentifier() == Name[Pieces.size()]) { + Pieces.push_back(Tok.getLocation()); + // All the selector pieces have been found. + if (Pieces.size() == Name.size()) + return true; + } else if (Tok.is(tok::r_square)) { + // Stop scanning at the end of the message send. + // Also account for spurious ']' in blocks or lambdas. + if (Kind == ObjCSymbolSelectorKind::MessageSend && !SquareCount && + !BraceCount) + break; + if (SquareCount) + --SquareCount; + } else if (Tok.is(tok::l_square)) + ++SquareCount; + else if (Tok.is(tok::l_paren)) + ++ParenCount; + else if (Tok.is(tok::r_paren)) { + if (!ParenCount) + break; + --ParenCount; + } else if (Tok.is(tok::l_brace)) { + // Stop scanning at the start of the of the method's body. + // Also account for any spurious blocks inside argument parameter types + // or parameter attributes. + if (Kind == ObjCSymbolSelectorKind::MethodDecl && !BraceCount && + !ParenCount) + break; + ++BraceCount; + } else if (Tok.is(tok::r_brace)) { + if (!BraceCount) + break; + --BraceCount; + } + // Stop scanning at the end of the method's declaration. + if (Kind == ObjCSymbolSelectorKind::MethodDecl && NoScoping && + (Tok.is(tok::semi) || Tok.is(tok::minus) || Tok.is(tok::plus))) + break; + } + return false; +} + +// Scan the file and find multi-piece selector occurrences in a token stream. +static void +findObjCMultiPieceSelectorOccurrences(CompilerInstance &CI, + ArrayRef Symbols, + IndexedFileOccurrenceConsumer &Consumer) { + for (const auto &Symbol : Symbols) { + (void)Symbol; + assert(Symbol.Name.size() > 1 && "Not a multi-piece symbol!"); + } + + SourceManager &SM = CI.getSourceManager(); + const LangOptions &LangOpts = CI.getLangOpts(); + // Create a mapping from source locations to the indexed occurrences. + SourceLocationsToIndexedOccurrences MappedIndexedOccurrences; + for (const auto &Symbol : llvm::enumerate(Symbols)) { + for (const IndexedOccurrence &Occurrence : + Symbol.value().IndexedOccurrences) { + // Selectors and names in #includes shouldn't really mix. + if (Occurrence.Kind == IndexedOccurrence::InclusionDirective) + continue; + SourceLocation Loc; + MatchKind Match = + checkOccurrence(Occurrence, Symbol.value(), SM, LangOpts, Loc); + if (Match == MatchKind::None) + continue; + if (Match == MatchKind::MacroExpansion) { + SymbolOccurrence Result(SymbolOccurrence::MatchingSymbol, + /*IsMacroExpansion=*/true, Symbol.index(), Loc); + Consumer.handleOccurrence(Result, SM, LangOpts); + continue; + } + MappedIndexedOccurrences.try_emplace(Loc.getRawEncoding(), Occurrence, + Symbol.index()); + } + } + + // Lex the file and look for tokens. + // Start lexing the specified input file. + const llvm::MemoryBuffer *FromFile = SM.getBuffer(SM.getMainFileID()); + Lexer RawLex(SM.getMainFileID(), FromFile, SM, LangOpts); + + std::vector Tokens; + bool SaveTokens = false; + Token RawTok; + RawLex.LexFromRawLexer(RawTok); + while (RawTok.isNot(tok::eof)) { + // Start saving tokens only when we've got a match + if (!SaveTokens) { + if (MappedIndexedOccurrences.find( + RawTok.getLocation().getRawEncoding()) != + MappedIndexedOccurrences.end()) + SaveTokens = true; + } + if (SaveTokens) + Tokens.push_back(RawTok); + RawLex.LexFromRawLexer(RawTok); + } + + for (const auto &I : llvm::enumerate(Tokens)) { + const auto &Tok = I.value(); + auto It = MappedIndexedOccurrences.find(Tok.getLocation().getRawEncoding()); + if (It == MappedIndexedOccurrences.end()) + continue; + if (Tok.getKind() != tok::raw_identifier) + continue; + const IndexedOccurrence &Occurrence = It->second.first; + unsigned SymbolIndex = It->second.second; + + // Scan the source for the remaining selector pieces. + SmallVector SelectorPieces; + ObjCSymbolSelectorKind Kind = + Occurrence.Kind == IndexedOccurrence::IndexedObjCMessageSend + ? ObjCSymbolSelectorKind::MessageSend + : ObjCSymbolSelectorKind::MethodDecl; + if (findObjCSymbolSelectorPieces( + llvm::makeArrayRef(Tokens).drop_front(I.index()), + Symbols[SymbolIndex].Name, SelectorPieces, Kind)) { + SymbolOccurrence Result(SymbolOccurrence::MatchingSymbol, + /*IsMacroExpansion=*/false, SymbolIndex, + std::move(SelectorPieces)); + Consumer.handleOccurrence(Result, SM, LangOpts); + } + } +} + +} // end namespace rename +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/RenamedSymbol.cpp b/clang/lib/Tooling/Refactor/RenamedSymbol.cpp new file mode 100644 index 0000000000000..16ec01f7f04cc --- /dev/null +++ b/clang/lib/Tooling/Refactor/RenamedSymbol.cpp @@ -0,0 +1,41 @@ +//===--- RenamedSymbol.cpp - ----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/RenamedSymbol.h" +#include "clang/AST/DeclObjC.h" +#include + +using namespace clang; + +namespace clang { +namespace tooling { +namespace rename { + +Symbol::Symbol(const NamedDecl *FoundDecl, unsigned SymbolIndex, + const LangOptions &LangOpts) + : Name(FoundDecl->getNameAsString(), LangOpts), SymbolIndex(SymbolIndex), + FoundDecl(FoundDecl) { + if (const auto *MD = dyn_cast(FoundDecl)) + ObjCSelector = MD->getSelector(); +} + +bool operator<(const SymbolOccurrence &LHS, const SymbolOccurrence &RHS) { + assert(!LHS.Locations.empty() && !RHS.Locations.empty()); + return LHS.Locations[0] < RHS.Locations[0]; +} + +bool operator==(const SymbolOccurrence &LHS, const SymbolOccurrence &RHS) { + return LHS.Kind == RHS.Kind && LHS.SymbolIndex == RHS.SymbolIndex && + std::equal(LHS.Locations.begin(), LHS.Locations.end(), + RHS.Locations.begin()); +} + +} // end namespace rename +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/RenamingOperation.cpp b/clang/lib/Tooling/Refactor/RenamingOperation.cpp new file mode 100644 index 0000000000000..80aa1bc6dd2c7 --- /dev/null +++ b/clang/lib/Tooling/Refactor/RenamingOperation.cpp @@ -0,0 +1,98 @@ +//===--- RenamingOperation.cpp - ------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/RenamingOperation.h" +#include "clang/AST/DeclObjC.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Refactor/SymbolOperation.h" + +using namespace clang; + +/// \brief Lexes the given name string. +/// +/// \return False if the name was consumed fully, true otherwise. +static bool lexNameString(StringRef Name, Token &Result, + const LangOptions &LangOpts) { + Lexer Lex(SourceLocation(), LangOpts, Name.data(), Name.data(), + Name.data() + Name.size()); + return !Lex.LexFromRawLexer(Result); +} + +namespace clang { +namespace tooling { +namespace rename { + +bool isNewNameValid(const SymbolName &NewName, bool IsSymbolObjCSelector, + IdentifierTable &IDs, const LangOptions &LangOpts) { + Token Tok; + if (IsSymbolObjCSelector) { + // Check if the name is a valid selector. + for (const auto &Name : NewName.strings()) { + // Lex the name and verify that it was fully consumed. Then make sure that + // it's a valid identifier. + if (lexNameString(Name, Tok, LangOpts) || !Tok.isAnyIdentifier()) + return false; + } + return true; + } + + for (const auto &Name : NewName.strings()) { + // Lex the name and verify that it was fully consumed. Then make sure that + // it's a valid identifier that's also not a language keyword. + if (lexNameString(Name, Tok, LangOpts) || !Tok.isAnyIdentifier() || + !tok::isAnyIdentifier(IDs.get(Name).getTokenID())) + return false; + } + return true; +} + +bool isNewNameValid(const SymbolName &NewName, const SymbolOperation &Operation, + IdentifierTable &IDs, const LangOptions &LangOpts) { + assert(!Operation.symbols().empty()); + return isNewNameValid(NewName, + Operation.symbols().front().ObjCSelector.hasValue(), + IDs, LangOpts); +} + +void determineNewNames(SymbolName NewName, const SymbolOperation &Operation, + SmallVectorImpl &NewNames, + const LangOptions &LangOpts) { + auto Symbols = Operation.symbols(); + assert(!Symbols.empty()); + NewNames.push_back(std::move(NewName)); + if (const auto *PropertyDecl = + dyn_cast(Symbols.front().FoundDecl)) { + assert(NewNames.front().size() == 1 && + "Property's name should have one string only"); + StringRef PropertyName = NewNames.front()[0]; + Symbols = Symbols.drop_front(); + + auto AddName = [&](const NamedDecl *D, StringRef Name) { + assert(Symbols.front().FoundDecl == D && "decl is missing"); + NewNames.push_back(SymbolName(Name, LangOpts)); + Symbols = Symbols.drop_front(); + }; + + if (!PropertyDecl->hasExplicitGetterName()) { + if (const auto *Getter = PropertyDecl->getGetterMethodDecl()) + AddName(Getter, PropertyName); + } + if (!PropertyDecl->hasExplicitSetterName()) { + if (const auto *Setter = PropertyDecl->getSetterMethodDecl()) { + auto SetterName = SelectorTable::constructSetterName(PropertyName); + AddName(Setter, SetterName); + } + } + } +} + +} // end namespace rename +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/SourceLocationUtilities.cpp b/clang/lib/Tooling/Refactor/SourceLocationUtilities.cpp new file mode 100644 index 0000000000000..0a0c4cdc9d3b5 --- /dev/null +++ b/clang/lib/Tooling/Refactor/SourceLocationUtilities.cpp @@ -0,0 +1,260 @@ +//===--- SourceLocationUtilities.cpp - Source location helper functions ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SourceLocationUtilities.h" +#include "clang/AST/Stmt.h" +#include "clang/Lex/Lexer.h" +#include + +namespace clang { +namespace tooling { + +SourceLocation findLastLocationOfSourceConstruct(SourceLocation HeaderEnd, + const Stmt *Body, + const SourceManager &SM) { + SourceLocation BodyStart = SM.getSpellingLoc(Body->getLocStart()); + unsigned BodyLine = SM.getSpellingLineNumber(BodyStart); + unsigned HeaderLine = SM.getSpellingLineNumber(HeaderEnd); + + if (BodyLine > HeaderLine) { + // The Last location on the previous line if the body is not on the same + // line as the last known location. + SourceLocation LineLocThatPrecedesBody = + SM.translateLineCol(SM.getFileID(BodyStart), BodyLine - 1, + std::numeric_limits::max()); + if (LineLocThatPrecedesBody.isValid()) + return LineLocThatPrecedesBody; + } + // We want to include the location of the '{'. + return isa(Body) ? BodyStart : BodyStart.getLocWithOffset(-1); +} + +SourceLocation findFirstLocationOfSourceConstruct(SourceLocation HeaderStart, + const Stmt *PreviousBody, + const SourceManager &SM) { + if (!isa(PreviousBody)) + return HeaderStart; + SourceLocation BodyEnd = SM.getSpellingLoc(PreviousBody->getLocEnd()); + unsigned BodyLine = SM.getSpellingLineNumber(BodyEnd); + unsigned HeaderLine = SM.getSpellingLineNumber(HeaderStart); + if (BodyLine >= HeaderLine) + return BodyEnd; + return HeaderStart; +} + +bool isLocationInAnyRange(SourceLocation Location, ArrayRef Ranges, + const SourceManager &SM) { + for (const SourceRange &Range : Ranges) { + if (!isPointWithin(Location, Range.getBegin(), Range.getEnd(), SM)) + continue; + return true; + } + return false; +} + +SourceLocation getPreciseTokenLocEnd(SourceLocation Loc, + const SourceManager &SM, + const LangOptions &LangOpts) { + return Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts); +} + +SourceLocation findClosingParenLocEnd(SourceLocation LastKnownLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + return Lexer::findLocationAfterToken( + LastKnownLoc, tok::r_paren, SM, LangOpts, + /*SkipTrailingWhitespaceAndNewLine=*/false); +} + +SourceRange getRangeOfNextToken(SourceLocation Loc, tok::TokenKind Kind, + const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation NextLoc = + Lexer::findLocationAfterToken(Loc, Kind, SM, LangOpts, + /*SkipTrailingWhitespaceAndNewLine=*/false); + if (NextLoc.isInvalid()) + return SourceRange(); + return SourceRange( + Lexer::GetBeginningOfToken(NextLoc.getLocWithOffset(-1), SM, LangOpts), + NextLoc); +} + +SourceLocation findLastNonCompoundLocation(const Stmt *S) { + const auto *CS = dyn_cast(S); + if (!CS) + return S->getLocEnd(); + return CS->body_back() ? CS->body_back()->getLocEnd() : SourceLocation(); +} + +bool areOnSameLine(SourceLocation Loc1, SourceLocation Loc2, + const SourceManager &SM) { + return !Loc1.isMacroID() && !Loc2.isMacroID() && + SM.getSpellingLineNumber(Loc1) == SM.getSpellingLineNumber(Loc2); +} + +SourceLocation +getLastLineLocationUnlessItHasOtherTokens(SourceLocation SpellingLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + assert(!SpellingLoc.isMacroID() && "Expecting a spelling location"); + SourceLocation NextTokenLoc = + Lexer::findNextTokenLocationAfterTokenAt(SpellingLoc, SM, LangOpts); + if (NextTokenLoc.isValid()) { + bool IsSameLine = areOnSameLine(SpellingLoc, NextTokenLoc, SM); + if (IsSameLine) { + // Could be a ';' on the same line, so try looking after the ';' + if (isSemicolonAtLocation(NextTokenLoc, SM, LangOpts)) + return getLastLineLocationUnlessItHasOtherTokens(NextTokenLoc, SM, + LangOpts); + } else { + SourceLocation LastLoc = SM.translateLineCol( + SM.getFileID(SpellingLoc), SM.getSpellingLineNumber(SpellingLoc), + std::numeric_limits::max()); + if (LastLoc.isValid()) + return LastLoc; + } + } + return getPreciseTokenLocEnd(SpellingLoc, SM, LangOpts); +} + +bool isSemicolonAtLocation(SourceLocation TokenLoc, const SourceManager &SM, + const LangOptions &LangOpts) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(TokenLoc, TokenLoc), SM, + LangOpts) == ";"; +} + +SourceRange trimSelectionRange(SourceRange Range, const SourceManager &SM, + const LangOptions &LangOpts) { + bool IsInvalid = false; + StringRef Text = Lexer::getSourceText(CharSourceRange::getCharRange(Range), + SM, LangOpts, &IsInvalid); + if (IsInvalid || Text.empty()) + return Range; + assert(Range.getBegin().isFileID() && "Not a file range!"); + + std::string Source = Text.str(); + Lexer Lex(Range.getBegin(), LangOpts, Source.c_str(), Source.c_str(), + Source.c_str() + Source.size()); + // Get comment tokens as well. + Lex.SetCommentRetentionState(true); + SourceLocation StartLoc, EndLoc; + while (true) { + Token Tok; + Lex.LexFromRawLexer(Tok); + if (Tok.getKind() == tok::eof) + break; + if (StartLoc.isInvalid()) + StartLoc = Tok.getLocation(); + if (Tok.getKind() != tok::semi) + EndLoc = Tok.getEndLoc(); + } + return StartLoc.isValid() && EndLoc.isValid() ? SourceRange(StartLoc, EndLoc) + : SourceRange(); +} + +/// Tokenize the given file and check if it contains a comment that ends at the +/// given location. +static SourceLocation findCommentThatEndsAt(FileID FID, + SourceLocation StartOfFile, + const SourceManager &SM, + const LangOptions &LangOpts, + SourceLocation ExpectedEndLoc) { + // Try to load the file buffer. + bool InvalidTemp = false; + StringRef File = SM.getBufferData(FID, &InvalidTemp); + if (InvalidTemp) + return SourceLocation(); + + // Search for the comment that ends at the given location. + Lexer Lex(StartOfFile, LangOpts, File.begin(), File.begin(), File.end()); + Lex.SetCommentRetentionState(true); + Token Tok; + while (!Lex.LexFromRawLexer(Tok)) { + if (Tok.is(tok::comment) && Tok.getEndLoc() == ExpectedEndLoc) + return Tok.getLocation(); + } + // Find the token. + return SourceLocation(); +} + +SourceLocation getLocationOfPrecedingComment(SourceLocation Location, + const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation PrevResult = Location; + SourceLocation Result = Location; + if (Result.isMacroID()) + Result = SM.getExpansionLoc(Result); + FileID FID = SM.getFileID(Result); + SourceLocation StartOfFile = SM.getLocForStartOfFile(FID); + Token Tok; + Tok.setKind(tok::unknown); + SourceLocation TokenLoc = Result; + auto GetPreviousToken = [&]() -> bool { + TokenLoc = + Lexer::GetBeginningOfToken(TokenLoc.getLocWithOffset(-1), SM, LangOpts); + return !Lexer::getRawToken(TokenLoc, Tok, SM, LangOpts); + }; + // Look for a comment token. + while (TokenLoc != StartOfFile) { + bool LocHasToken = GetPreviousToken(); + if (LocHasToken && Tok.is(tok::slash)) { + // Check if this the end of a multiline '/*' comment before returning. + SourceLocation CommentLoc = findCommentThatEndsAt( + FID, StartOfFile, SM, LangOpts, Tok.getEndLoc()); + return CommentLoc.isInvalid() ? Result : CommentLoc; + } + if (LocHasToken && Tok.isNot(tok::comment)) + break; + if (!LocHasToken) + continue; + // We found a preceding comment. Check if there are other preceding + // comments. + PrevResult = Result; + Result = Tok.getLocation(); + while (TokenLoc != StartOfFile) { + bool LocHasToken = GetPreviousToken(); + if (LocHasToken && Tok.isNot(tok::comment)) { + // Reset the result to the previous location if this comment trails + // another token on the same line. + if (SM.getSpellingLineNumber(Tok.getEndLoc()) == + SM.getSpellingLineNumber(Result)) + Result = PrevResult; + break; + } + if (!LocHasToken) + continue; + // The location of this comment is accepted only when the next comment + // is located immediately after this comment. + if (SM.getSpellingLineNumber(Tok.getEndLoc()) != + SM.getSpellingLineNumber(Result) - 1) + break; + PrevResult = Result; + Result = Tok.getLocation(); + } + break; + } + return Result; +} + +SourceLocation getLocationOfPrecedingToken(SourceLocation Loc, + const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation Result = Loc; + if (Result.isMacroID()) + Result = SM.getExpansionLoc(Result); + FileID FID = SM.getFileID(Result); + SourceLocation StartOfFile = SM.getLocForStartOfFile(FID); + if (Loc == StartOfFile) + return SourceLocation(); + return Lexer::GetBeginningOfToken(Result.getLocWithOffset(-1), SM, LangOpts); +} + +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/SourceLocationUtilities.h b/clang/lib/Tooling/Refactor/SourceLocationUtilities.h new file mode 100644 index 0000000000000..ba7425bf88c65 --- /dev/null +++ b/clang/lib/Tooling/Refactor/SourceLocationUtilities.h @@ -0,0 +1,174 @@ +//===--- SourceLocationUtilities.h - Source location helper functions -----===// +// +// 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_TOOLING_REFACTOR_SOURCE_LOCATION_UTILITIES_H +#define LLVM_CLANG_LIB_TOOLING_REFACTOR_SOURCE_LOCATION_UTILITIES_H + +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.h" + +namespace clang { + +class Stmt; +class LangOptions; + +namespace tooling { + +inline bool isPairOfFileLocations(SourceLocation Start, SourceLocation End) { + return Start.isValid() && Start.isFileID() && End.isValid() && End.isFileID(); +} + +/// Return true if the Point is within Start and End. +inline bool isPointWithin(SourceLocation Location, SourceLocation Start, + SourceLocation End, const SourceManager &SM) { + return Location == Start || Location == End || + (SM.isBeforeInTranslationUnit(Start, Location) && + SM.isBeforeInTranslationUnit(Location, End)); +} + +/// Return true if the two given ranges overlap with each other. +inline bool areRangesOverlapping(SourceRange R1, SourceRange R2, + const SourceManager &SM) { + return isPointWithin(R1.getBegin(), R2.getBegin(), R2.getEnd(), SM) || + isPointWithin(R2.getBegin(), R1.getBegin(), R1.getEnd(), SM); +} + +/// \brief Return the source location that can be considered the last location +/// of the source construct before its body. +/// +/// The returned location is determined using the following rules: +/// +/// 1) If the source construct has a compound body that starts on the same line, +/// then this function will return the location of the opening '{'. +/// +/// if (condition) { +/// ^ +/// +/// 2) If the source construct's body is not a compound statement that starts +/// on the same line, then this function will return the location just before +/// the starting location of the body. +/// +/// if (condition) foo() +/// ^ +/// +/// 3) Otherwise, this function will return the last location on the line prior +/// to the the line on which the body starts. +/// +/// if (condition) +/// ^ +/// foo() +/// +/// \param HeaderEnd The last known location of the pre-body portion of the +/// source construct. For example, for an if statement, HeaderEnd should +/// be the ending location of its conditional expression. +SourceLocation findLastLocationOfSourceConstruct(SourceLocation HeaderEnd, + const Stmt *Body, + const SourceManager &SM); + +/// \brief Return the source location that can be considered the first location +/// of the source construct prior to the previous portion of its body. +/// +/// The returned location is determined using the following rules: +/// +/// 1) If the source construct's body is a compound statement that ends +/// on the same line, then this function will return the location of the +/// closing '}'. +/// +/// } else if (condition) +/// ^ +/// +/// 2) Otherwise, this function will return the starting location of the source +/// construct. +/// +/// foo(); +/// else if (condition) +/// ^ +/// +/// } +/// else if (condition) +/// ^ +/// +/// \param HeaderStart The first known location of the post-body portion of the +/// source construct. For example, for an if statement, HeaderStart should +/// be the starting location of the if keyword. +SourceLocation findFirstLocationOfSourceConstruct(SourceLocation HeaderStart, + const Stmt *PreviousBody, + const SourceManager &SM); + +/// Return true if the given \p Location is within any range. +bool isLocationInAnyRange(SourceLocation Location, ArrayRef Ranges, + const SourceManager &SM); + +/// Return the precise end location for the given token. +SourceLocation getPreciseTokenLocEnd(SourceLocation Loc, + const SourceManager &SM, + const LangOptions &LangOpts); + +/// \brief Find the source location right after the location of the next ')'. +/// +/// If the token that's located after \p LastKnownLoc isn't ')', then this +/// function returns an invalid source location. +SourceLocation findClosingParenLocEnd(SourceLocation LastKnownLoc, + const SourceManager &SM, + const LangOptions &LangOpts); + +/// Return the range of the next token if it has the given kind. +SourceRange getRangeOfNextToken(SourceLocation Loc, tok::TokenKind Kind, + const SourceManager &SM, + const LangOptions &LangOpts); + +/// Return the end location of the body when \p S is a compound statement or an +/// invalid location when \p S is an empty compound statement. Otherwise, +/// return the end location of the given statement \p S. +SourceLocation findLastNonCompoundLocation(const Stmt *S); + +/// Return true if the two locations are on the same line and aren't +/// macro locations. +bool areOnSameLine(SourceLocation Loc1, SourceLocation Loc2, + const SourceManager &SM); + +/// Return the last location of the line which contains the given spellling +/// location \p SpellingLoc unless that line has other tokens after the given +/// location. +SourceLocation +getLastLineLocationUnlessItHasOtherTokens(SourceLocation SpellingLoc, + const SourceManager &SM, + const LangOptions &LangOpts); + +/// Return true if the token at the given location is a semicolon. +bool isSemicolonAtLocation(SourceLocation TokenLoc, const SourceManager &SM, + const LangOptions &LangOpts); + +/// Shrink the given range by ignoring leading whitespace and trailing +/// whitespace and semicolons. +/// +/// Returns an invalid source range if the source range consists of whitespace +/// or semicolons only. +SourceRange trimSelectionRange(SourceRange Range, const SourceManager &SM, + const LangOptions &LangOpts); + +/// Return the source location of the conjoined comment(s) that precede the +/// given location \p Loc, or the same location if there's no comment before +/// \p Loc. +SourceLocation getLocationOfPrecedingComment(SourceLocation Loc, + const SourceManager &SM, + const LangOptions &LangOpts); + +/// Return the source location of the token that comes before the token at the +/// given location. +SourceLocation getLocationOfPrecedingToken(SourceLocation Loc, + const SourceManager &SM, + const LangOptions &LangOpts); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTOR_SOURCE_LOCATION_UTILITIES_H diff --git a/clang/lib/Tooling/Refactor/StmtUtils.cpp b/clang/lib/Tooling/Refactor/StmtUtils.cpp new file mode 100644 index 0000000000000..53d426b8b39c0 --- /dev/null +++ b/clang/lib/Tooling/Refactor/StmtUtils.cpp @@ -0,0 +1,54 @@ +//===--- StmtUtils.cpp - Statement helper functions -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StmtUtils.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/StmtCXX.h" +#include "clang/AST/StmtObjC.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; + +SourceLocation +clang::tooling::getLexicalEndLocForDecl(const Decl *D, const SourceManager &SM, + const LangOptions &LangOpts) { + if (!isa(D)) + return D->getSourceRange().getEnd(); + auto AtEnd = D->getSourceRange().getEnd(); + auto AdjustedEnd = + Lexer::findNextTokenLocationAfterTokenAt(AtEnd, SM, LangOpts); + return AdjustedEnd.isValid() ? AdjustedEnd : AtEnd; +} + +bool clang::tooling::isSemicolonRequiredAfter(const Stmt *S) { + if (isa(S)) + return false; + if (const auto *If = dyn_cast(S)) + return isSemicolonRequiredAfter(If->getElse() ? If->getElse() + : If->getThen()); + if (const auto *While = dyn_cast(S)) + return isSemicolonRequiredAfter(While->getBody()); + if (const auto *For = dyn_cast(S)) + return isSemicolonRequiredAfter(For->getBody()); + if (const auto *CXXFor = dyn_cast(S)) + return isSemicolonRequiredAfter(CXXFor->getBody()); + if (const auto *ObjCFor = dyn_cast(S)) + return isSemicolonRequiredAfter(ObjCFor->getBody()); + switch (S->getStmtClass()) { + case Stmt::SwitchStmtClass: + case Stmt::CXXTryStmtClass: + case Stmt::ObjCAtSynchronizedStmtClass: + case Stmt::ObjCAutoreleasePoolStmtClass: + case Stmt::ObjCAtTryStmtClass: + return false; + default: + return true; + } +} diff --git a/clang/lib/Tooling/Refactor/StmtUtils.h b/clang/lib/Tooling/Refactor/StmtUtils.h new file mode 100644 index 0000000000000..8f458770ae23b --- /dev/null +++ b/clang/lib/Tooling/Refactor/StmtUtils.h @@ -0,0 +1,33 @@ +//===--- StmtUtils.h - Statement helper functions -------------------------===// +// +// 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_TOOLING_REFACTOR_STMT_UTILS_H +#define LLVM_CLANG_LIB_TOOLING_REFACTOR_STMT_UTILS_H + +#include "clang/Basic/SourceLocation.h" + +namespace clang { + +class Decl; +class LangOptions; +class Stmt; + +namespace tooling { + +SourceLocation getLexicalEndLocForDecl(const Decl *D, const SourceManager &SM, + const LangOptions &LangOpts); + +/// \brief Returns true if there should be a semicolon after the given +/// statement. +bool isSemicolonRequiredAfter(const Stmt *S); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTOR_STMT_UTILS_H diff --git a/clang/lib/Tooling/Refactor/SymbolName.cpp b/clang/lib/Tooling/Refactor/SymbolName.cpp new file mode 100644 index 0000000000000..2d30d1834e479 --- /dev/null +++ b/clang/lib/Tooling/Refactor/SymbolName.cpp @@ -0,0 +1,58 @@ +//===--- SymbolName.cpp - Clang refactoring library -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/SymbolName.h" +#include "clang/Basic/LangOptions.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace tooling { + +static void initNames(std::vector &Strings, StringRef Name, + bool IsObjectiveCSelector) { + if (!IsObjectiveCSelector) { + Strings.push_back(Name.str()); + return; + } + // Decompose an Objective-C selector name into multiple strings. + do { + auto StringAndName = Name.split(':'); + Strings.push_back(StringAndName.first.str()); + Name = StringAndName.second; + } while (!Name.empty()); +} + +SymbolName::SymbolName(StringRef Name, const LangOptions &LangOpts) { + initNames(Strings, Name, LangOpts.ObjC1); +} + +SymbolName::SymbolName(StringRef Name, bool IsObjectiveCSelector) { + initNames(Strings, Name, IsObjectiveCSelector); +} + +SymbolName::SymbolName(ArrayRef Name) { + for (const auto &Piece : Name) + Strings.push_back(Piece.str()); +} + +void SymbolName::print(raw_ostream &OS) const { + for (size_t I = 0, E = Strings.size(); I != E; ++I) { + if (I != 0) + OS << ':'; + OS << Strings[I]; + } +} + +raw_ostream &operator<<(raw_ostream &OS, const SymbolName &N) { + N.print(OS); + return OS; +} + +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/SymbolOccurrenceFinder.cpp b/clang/lib/Tooling/Refactor/SymbolOccurrenceFinder.cpp new file mode 100644 index 0000000000000..38f4cc8d401fb --- /dev/null +++ b/clang/lib/Tooling/Refactor/SymbolOccurrenceFinder.cpp @@ -0,0 +1,412 @@ +//===--- SymbolOccurrenceFinder.cpp - Clang refactoring library -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Methods for finding all instances of a USR. Our strategy is very +/// simple; we just compare the USR at every relevant AST node with the one +/// provided. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/SymbolOccurrenceFinder.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DependentASTVisitor.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Refactor/USRFinder.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" + +using namespace llvm; + +namespace clang { +namespace tooling { +namespace rename { + +namespace { +// \brief This visitor recursively searches for all instances of a USR in a +// translation unit and stores them for later usage. +class SymbolOccurrenceFinderASTVisitor + : public DependentASTVisitor { +public: + explicit SymbolOccurrenceFinderASTVisitor( + const SymbolOperation &Operation, const ASTContext &Context, + std::vector &Occurrences) + : Operation(Operation), Context(Context), Occurrences(Occurrences) {} + + /// Returns a \c Symbol if the given declaration corresponds to the symbol + /// that we're looking for. + const Symbol *symbolForDecl(const Decl *D) const { + if (!D) + return nullptr; + std::string USR = getUSRForDecl(D); + return Operation.getSymbolForUSR(USR); + } + + void checkDecl(const Decl *D, SourceLocation Loc, + SymbolOccurrence::OccurrenceKind Kind = + SymbolOccurrence::MatchingSymbol) { + if (!D) + return; + std::string USR = getUSRForDecl(D); + if (const Symbol *S = Operation.getSymbolForUSR(USR)) + checkAndAddLocations(S->SymbolIndex, Loc, Kind); + } + + // Declaration visitors: + + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { + for (const auto *Initializer : ConstructorDecl->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) + checkDecl(FieldDecl, Initializer->getSourceLocation()); + } + return true; + } + + bool VisitNamedDecl(const NamedDecl *Decl) { + checkDecl(Decl, Decl->getLocation()); + return true; + } + + bool WalkUpFromTypedefNameDecl(const TypedefNameDecl *D) { + // Don't visit the NamedDecl for TypedefNameDecl. + return VisitTypedefNamedDecl(D); + } + + bool VisitTypedefNamedDecl(const TypedefNameDecl *D) { + if (D->isTransparentTag()) { + if (const auto *Underlying = D->getUnderlyingType()->getAsTagDecl()) { + checkDecl(Underlying, D->getLocation()); + return true; + } + } + return VisitNamedDecl(D); + } + + bool WalkUpFromUsingDecl(const UsingDecl *D) { + // Don't visit the NamedDecl for UsingDecl. + return VisitUsingDecl(D); + } + + bool VisitUsingDecl(const UsingDecl *D) { + for (const auto *Shadow : D->shadows()) { + const NamedDecl *UD = Shadow->getUnderlyingDecl(); + if (UD->isImplicit() || UD == D) + continue; + if (const auto *FTD = dyn_cast(UD)) { + UD = FTD->getTemplatedDecl(); + if (!UD) + continue; + } + checkDecl(UD, D->getLocation()); + } + return true; + } + + bool WalkUpFromUsingDirectiveDecl(const UsingDirectiveDecl *D) { + // Don't visit the NamedDecl for UsingDirectiveDecl. + return VisitUsingDirectiveDecl(D); + } + + bool VisitUsingDirectiveDecl(const UsingDirectiveDecl *D) { + checkDecl(D->getNominatedNamespaceAsWritten(), D->getLocation()); + return true; + } + + bool WalkUpFromUnresolvedUsingValueDecl(const UnresolvedUsingValueDecl *D) { + // Don't visit the NamedDecl for UnresolvedUsingValueDecl. + // FIXME: Can we try to lookup the name? + return true; + } + + bool + WalkUpFromUnresolvedUsingTypenameDecl(const UnresolvedUsingTypenameDecl *D) { + // Don't visit the NamedDecl for UnresolvedUsingTypenameDecl. + // FIXME: Can we try to lookup the name? + return true; + } + + bool WalkUpFromObjCMethodDecl(const ObjCMethodDecl *Decl) { + // Don't visit the NamedDecl for Objective-C methods. + return VisitObjCMethodDecl(Decl); + } + + bool VisitObjCMethodDecl(const ObjCMethodDecl *Decl) { + const Symbol *S = symbolForDecl(Decl); + if (!S) + return true; + SmallVector SelectorLocs; + Decl->getSelectorLocs(SelectorLocs); + checkAndAddLocations(S->SymbolIndex, SelectorLocs); + return true; + } + + bool handleObjCProtocolList(const ObjCProtocolList &Protocols) { + for (auto It : enumerate(Protocols)) + checkDecl(It.value(), Protocols.loc_begin()[It.index()]); + return true; + } + + bool VisitObjCInterfaceDecl(const ObjCInterfaceDecl *Decl) { + if (!Decl->hasDefinition()) + return true; + return handleObjCProtocolList(Decl->getReferencedProtocols()); + } + + bool VisitObjCProtocolDecl(const ObjCProtocolDecl *Decl) { + if (!Decl->hasDefinition()) + return true; + return handleObjCProtocolList(Decl->getReferencedProtocols()); + } + + bool VisitObjCCategoryDecl(const ObjCCategoryDecl *Decl) { + checkDecl(Decl, Decl->getCategoryNameLoc()); + // The location of the class name is the location of the declaration. + checkDecl(Decl->getClassInterface(), Decl->getLocation()); + return handleObjCProtocolList(Decl->getReferencedProtocols()); + } + + bool VisitObjCCategoryImplDecl(const ObjCCategoryImplDecl *Decl) { + checkDecl(Decl, Decl->getCategoryNameLoc()); + // The location of the class name is the location of the declaration. + checkDecl(Decl->getClassInterface(), Decl->getLocation()); + return true; + } + + bool VisitObjCCompatibleAliasDecl(const ObjCCompatibleAliasDecl *Decl) { + checkDecl(Decl->getClassInterface(), Decl->getClassInterfaceLoc()); + return true; + } + + bool VisitObjCPropertyDecl(const ObjCPropertyDecl *Decl) { + if (Decl->hasExplicitGetterName()) + checkDecl(Decl->getGetterMethodDecl(), Decl->getGetterNameLoc()); + if (Decl->hasExplicitSetterName()) + checkDecl(Decl->getSetterMethodDecl(), Decl->getSetterNameLoc()); + return true; + } + + bool VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *Decl) { + checkDecl(Decl->getPropertyDecl(), Decl->getLocation()); + if (Decl->isIvarNameSpecified()) + checkDecl(Decl->getPropertyIvarDecl(), Decl->getPropertyIvarDeclLoc()); + return true; + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + checkDecl(Expr->getFoundDecl(), Expr->getLocation()); + return true; + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + checkDecl(Expr->getFoundDecl().getDecl(), Expr->getMemberLoc()); + return true; + } + + bool VisitObjCMessageExpr(const ObjCMessageExpr *Expr) { + const Symbol *S = symbolForDecl(Expr->getMethodDecl()); + if (!S) + return true; + SmallVector SelectorLocs; + Expr->getSelectorLocs(SelectorLocs); + checkAndAddLocations(S->SymbolIndex, SelectorLocs); + return true; + } + + bool VisitObjCProtocolExpr(const ObjCProtocolExpr *Expr) { + checkDecl(Expr->getProtocol(), Expr->getProtocolIdLoc()); + return true; + } + + bool VisitObjCIvarRefExpr(const ObjCIvarRefExpr *Expr) { + checkDecl(Expr->getDecl(), Expr->getLocation()); + return true; + } + + bool VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *Expr) { + if (Expr->isClassReceiver()) + checkDecl(Expr->getClassReceiver(), Expr->getReceiverLocation()); + if (Expr->isImplicitProperty()) { + // Class properties that are explicitly defined using @property + // declarations are represented implicitly as there is no ivar for class + // properties. + if (const ObjCMethodDecl *Getter = Expr->getImplicitPropertyGetter()) { + if (Getter->isClassMethod()) + if (const auto *PD = Getter->getCanonicalDecl()->findPropertyDecl()) { + checkDecl(PD, Expr->getLocation()); + return true; + } + } + + checkDecl(Expr->getImplicitPropertyGetter(), Expr->getLocation(), + SymbolOccurrence::MatchingImplicitProperty); + // Add a manual location for a setter since a token like 'property' won't + // match the the name of the renamed symbol like 'setProperty'. + if (const auto *S = symbolForDecl(Expr->getImplicitPropertySetter())) + addLocation(S->SymbolIndex, Expr->getLocation(), + SymbolOccurrence::MatchingImplicitProperty); + return true; + } + checkDecl(Expr->getExplicitProperty(), Expr->getLocation()); + return true; + } + + // Other visitors: + + bool VisitTypeLoc(const TypeLoc Loc) { + TypedefTypeLoc TTL = Loc.getAs(); + if (TTL) { + const auto *TND = TTL.getTypedefNameDecl(); + if (TND->isTransparentTag()) { + if (const auto *Underlying = TND->getUnderlyingType()->getAsTagDecl()) { + checkDecl(Underlying, TTL.getNameLoc()); + return true; + } + } + checkDecl(TND, TTL.getNameLoc()); + return true; + } + TypeSpecTypeLoc TSTL = Loc.getAs(); + if (TSTL) { + checkDecl(Loc.getType()->getAsTagDecl(), TSTL.getNameLoc()); + } + if (const auto *TemplateTypeParm = + dyn_cast(Loc.getType())) { + checkDecl(TemplateTypeParm->getDecl(), Loc.getBeginLoc()); + } + if (const auto *TemplateSpecType = + dyn_cast(Loc.getType())) { + checkDecl(TemplateSpecType->getTemplateName().getAsTemplateDecl(), + Loc.getBeginLoc()); + } + return true; + } + + bool VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc Loc) { + checkDecl(Loc.getIFaceDecl(), Loc.getNameLoc()); + return true; + } + + bool VisitObjCObjectTypeLoc(ObjCObjectTypeLoc Loc) { + for (unsigned I = 0, E = Loc.getNumProtocols(); I < E; ++I) + checkDecl(Loc.getProtocol(I), Loc.getProtocolLoc(I)); + return true; + } + + bool VisitDependentSymbolReference(const NamedDecl *Symbol, + SourceLocation SymbolNameLoc) { + checkDecl(Symbol, SymbolNameLoc); + return true; + } + + // Non-visitors: + + // Namespace traversal: + void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + checkDecl(NameLoc.getNestedNameSpecifier()->getAsNamespace(), + NameLoc.getLocalBeginLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + +private: + size_t getOffsetForString(SourceLocation Loc, StringRef PrevNameString) { + const SourceLocation BeginLoc = Loc; + const SourceLocation EndLoc = Lexer::getLocForEndOfToken( + BeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); + StringRef TokenName = + Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), + Context.getSourceManager(), Context.getLangOpts()); + return TokenName.find(PrevNameString); + } + + void checkAndAddLocations(unsigned SymbolIndex, + ArrayRef Locations, + SymbolOccurrence::OccurrenceKind Kind = + SymbolOccurrence::MatchingSymbol) { + if (Locations.size() != Operation.symbols()[SymbolIndex].Name.size()) + return; + + SmallVector StringLocations; + for (size_t I = 0, E = Locations.size(); I != E; ++I) { + SourceLocation Loc = Locations[I]; + bool IsMacroExpansion = Loc.isMacroID(); + if (IsMacroExpansion) { + const SourceManager &SM = Context.getSourceManager(); + if (SM.isMacroArgExpansion(Loc)) { + Loc = SM.getSpellingLoc(Loc); + IsMacroExpansion = false; + } else + Loc = SM.getExpansionLoc(Loc); + } + if (IsMacroExpansion) { + Occurrences.push_back(SymbolOccurrence(Kind, /*IsMacroExpansion=*/true, + SymbolIndex, Loc)); + return; + } + size_t Offset = + getOffsetForString(Loc, Operation.symbols()[SymbolIndex].Name[I]); + if (Offset == StringRef::npos) + return; + StringLocations.push_back(Loc.getLocWithOffset(Offset)); + } + + Occurrences.push_back(SymbolOccurrence(Kind, /*IsMacroExpansion=*/false, + SymbolIndex, StringLocations)); + } + + /// Adds a location without checking if the name is actually there. + void addLocation(unsigned SymbolIndex, SourceLocation Location, + SymbolOccurrence::OccurrenceKind Kind) { + if (1 != Operation.symbols()[SymbolIndex].Name.size()) + return; + bool IsMacroExpansion = Location.isMacroID(); + if (IsMacroExpansion) { + const SourceManager &SM = Context.getSourceManager(); + if (SM.isMacroArgExpansion(Location)) { + Location = SM.getSpellingLoc(Location); + IsMacroExpansion = false; + } else + Location = SM.getExpansionLoc(Location); + } + Occurrences.push_back( + SymbolOccurrence(Kind, IsMacroExpansion, SymbolIndex, Location)); + } + + const SymbolOperation &Operation; + const ASTContext &Context; + std::vector &Occurrences; +}; +} // namespace + +std::vector +findSymbolOccurrences(const SymbolOperation &Operation, Decl *Decl) { + std::vector Occurrences; + SymbolOccurrenceFinderASTVisitor Visitor(Operation, Decl->getASTContext(), + Occurrences); + Visitor.TraverseDecl(Decl); + NestedNameSpecifierLocFinder Finder(Decl->getASTContext()); + + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) + Visitor.handleNestedNameSpecifierLoc(Location); + + return Occurrences; +} + +} // end namespace rename +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/SymbolOperation.cpp b/clang/lib/Tooling/Refactor/SymbolOperation.cpp new file mode 100644 index 0000000000000..111b6c0762ba0 --- /dev/null +++ b/clang/lib/Tooling/Refactor/SymbolOperation.cpp @@ -0,0 +1,210 @@ +//===--- SymbolOperation.cpp - --------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/SymbolOperation.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Tooling/Refactor/RefactoringActionFinder.h" + +using namespace clang; + +/// Return true if the given local record decl escapes the given enclosing +/// function or block \p Ctx. +static bool escapesEnclosingDecl(const DeclContext *Ctx, const RecordDecl *RD) { + QualType ReturnType; + bool DependentBlock = false; + if (const auto *FD = dyn_cast(Ctx)) + ReturnType = FD->getReturnType(); + else if (const auto *BD = dyn_cast(Ctx)) { + ReturnType = BD->getSignatureAsWritten()->getType(); + // Blocks that don't have an explicitly specified type (represented with a + // dependent type) could potentially return the record, e.g. + // auto block = ^ { + // struct Foo { }; + // return Foo(); + // }; + if (const auto *FT = ReturnType->getAs()) + ReturnType = FT->getReturnType(); + if (ReturnType->isDependentType()) + DependentBlock = true; + } else + return false; + + // The record can be returned from its enclosing function when the function's + // return type is auto. + // + // FIXME: Use a smarter heuristic that detects if the record type is + // actually returned from the function. Have to account for inner records, + // like in the example below: + // + // auto foo() { + // struct Foo { struct Bar { }; }; + // return Foo::Bar(); + // }; + // + // for types that depend on the record, like in the example below: + // + // auto foo() { + // template struct C { T x; }; + // struct Foo { struct Bar { }; }; + // return C(); + // } + // + // and for things like typedefs and function types as well. + if (!DependentBlock && !ReturnType->getContainedAutoType()) + return false; + + // Even if the enclosing function returns the local record, this record is + // still local if the enclosing function is inside a function/method that + // doesn't return this record. + const auto *D = cast(Ctx); + if (D->isLexicallyWithinFunctionOrMethod()) + return escapesEnclosingDecl(D->getParentFunctionOrMethod(), RD); + + return true; +} + +static bool escapesEnclosingDecl(const RecordDecl *RD, + const LangOptions &LangOpts) { + // We only care about things that escape in header files since things that + // escape in source files will be used only in the initial TU. + return LangOpts.IsHeaderFile && + escapesEnclosingDecl(RD->getParentFunctionOrMethod(), RD); +} + +/// Return true if the given declaration corresponds to a local symbol. +bool clang::tooling::isLocalSymbol(const NamedDecl *FoundDecl, + const LangOptions &LangOpts) { + // Template parameters aren't indexed, so use local rename. + if (isa(FoundDecl) || + isa(FoundDecl) || + isa(FoundDecl)) + return true; + + if (const auto *VD = dyn_cast(FoundDecl)) + return VD->isLocalVarDeclOrParm(); + + // Objective-C selector renames must be global. + if (isa(FoundDecl)) + return false; + + // Local declarations are defined in a function or a method, or are anonymous. + if (!FoundDecl->isLexicallyWithinFunctionOrMethod()) + return false; + + // A locally defined record is global when it is returned from the enclosing + // function because we can refer to its destructor externally. + if (const auto *RD = dyn_cast(FoundDecl)) + return !escapesEnclosingDecl(RD, LangOpts); + + // A locally defined field is global when its record is returned from the + // enclosing function. + if (const auto *FD = dyn_cast(FoundDecl)) + return !escapesEnclosingDecl(FD->getParent(), LangOpts); + + if (const auto *MD = dyn_cast(FoundDecl)) { + // A locally defined method is global when its record is returned from the + // enclosing function. + if (escapesEnclosingDecl(MD->getParent(), LangOpts)) + return false; + + // Method renames can be local only iff this method doesn't override + // a global method, for example: + // + // void func() { + // struct Foo: GlobalSuper { + // // When renaming foo we should also rename GlobalSuper's foo + // void foo() override; + // } + // } + // + // FIXME: We can try to be smarter about it and check if we override + // a local method, which would make this method local as well. + return !MD->isVirtual(); + } + + return true; +} + +static const NamedDecl * +findDeclThatRequiresImplementationTU(const NamedDecl *FoundDecl) { + // TODO: implement the rest. + if (const ObjCIvarDecl *IVarDecl = dyn_cast(FoundDecl)) { + // We need the implementation TU when the IVAR is declared in an @interface + // without an @implementation. + if (const auto *ID = + dyn_cast(IVarDecl->getDeclContext())) { + if (!ID->getImplementation()) + return IVarDecl; + } + } + return nullptr; +} + +namespace clang { +namespace tooling { + +SymbolOperation::SymbolOperation(const NamedDecl *FoundDecl, + ASTContext &Context) + : IsLocal(isLocalSymbol(FoundDecl, Context.getLangOpts())) { + // Take the category declaration if this is a category implementation. + if (const auto *CategoryImplDecl = + dyn_cast(FoundDecl)) { + if (const auto *CategoryDecl = CategoryImplDecl->getCategoryDecl()) + FoundDecl = CategoryDecl; + } + // Use the property if this method is a getter/setter. + else if (const auto *MethodDecl = dyn_cast(FoundDecl)) { + if (const auto *PropertyDecl = + MethodDecl->getCanonicalDecl()->findPropertyDecl()) { + // Don't use the property if the getter/setter method has an explicitly + // specified name. + if (MethodDecl->param_size() == 0 + ? !PropertyDecl->hasExplicitGetterName() + : !PropertyDecl->hasExplicitSetterName()) + FoundDecl = PropertyDecl; + } + } + + DeclThatRequiresImplementationTU = + findDeclThatRequiresImplementationTU(FoundDecl); + + // TODO: Split into initiation that works after implementation TU is loaded. + + // Find the set of symbols that this operation has to work on. + auto AddSymbol = [this, &Context](const NamedDecl *FoundDecl) { + unsigned Index = Symbols.size(); + Symbols.push_back(rename::Symbol(FoundDecl, Index, Context.getLangOpts())); + for (const auto &USR : findSymbolsUSRSet(FoundDecl, Context)) + USRToSymbol.insert(std::make_pair(USR.getKey(), Index)); + }; + AddSymbol(FoundDecl); + // Take getters, setters and ivars into account when dealing with + // Objective-C @property declarations. + if (const auto *PropertyDecl = dyn_cast(FoundDecl)) { + // FIXME: findSymbolsUSRSet is called for every symbol we add, which is + // inefficient since we currently have to traverse the AST every time it is + // called. Fix this so that the AST isn't traversed more than once. + if (!PropertyDecl->hasExplicitGetterName()) { + if (const auto *Getter = PropertyDecl->getGetterMethodDecl()) + AddSymbol(Getter); + } + if (!PropertyDecl->hasExplicitSetterName()) { + if (const auto *Setter = PropertyDecl->getSetterMethodDecl()) + AddSymbol(Setter); + } + } +} + +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/SymbolUSRFinder.cpp b/clang/lib/Tooling/Refactor/SymbolUSRFinder.cpp new file mode 100644 index 0000000000000..267f90c7ed98a --- /dev/null +++ b/clang/lib/Tooling/Refactor/SymbolUSRFinder.cpp @@ -0,0 +1,206 @@ +//===--- SymbolUSRFinder.cpp - Clang refactoring library ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Implements methods that find the set of USRs that correspond to +/// a symbol that's required for a refactoring operation. +/// +//===----------------------------------------------------------------------===// + +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Tooling/Refactor/RefactoringActionFinder.h" +#include "clang/Tooling/Refactor/USRFinder.h" +#include "llvm/ADT/StringRef.h" + +#include + +using namespace clang; +using namespace clang::tooling::rename; + +namespace { + +/// \brief NamedDeclFindingConsumer delegates finding USRs of a found Decl to +/// \c AdditionalUSRFinder. \c AdditionalUSRFinder adds USRs of ctors and dtor +/// if the found declaration refers to a class and adds USRs of all overridden +/// methods if the declaration refers to a virtual C++ method or an ObjC method. +class AdditionalUSRFinder : public RecursiveASTVisitor { +public: + AdditionalUSRFinder(const Decl *FoundDecl, ASTContext &Context) + : FoundDecl(FoundDecl), Context(Context) {} + + llvm::StringSet<> Find() { + llvm::StringSet<> USRSet; + + // Fill OverriddenMethods and PartialSpecs storages. + TraverseDecl(Context.getTranslationUnitDecl()); + if (const auto *MethodDecl = dyn_cast(FoundDecl)) { + addUSRsOfOverridenFunctions(MethodDecl, USRSet); + // FIXME: Use a more efficient/optimal algorithm to find the related + // methods. + for (const auto &OverriddenMethod : OverriddenMethods) { + if (checkIfOverriddenFunctionAscends(OverriddenMethod, USRSet)) + USRSet.insert(getUSRForDecl(OverriddenMethod)); + } + } else if (const auto *RecordDecl = dyn_cast(FoundDecl)) { + handleCXXRecordDecl(RecordDecl, USRSet); + } else if (const auto *TemplateDecl = + dyn_cast(FoundDecl)) { + handleClassTemplateDecl(TemplateDecl, USRSet); + } else if (const auto *MethodDecl = dyn_cast(FoundDecl)) { + addUSRsOfOverriddenObjCMethods(MethodDecl, USRSet); + for (const auto &PotentialOverrider : PotentialObjCMethodOverridders) + if (checkIfPotentialObjCMethodOverriddes(PotentialOverrider, USRSet)) + USRSet.insert(getUSRForDecl(PotentialOverrider)); + } else { + USRSet.insert(getUSRForDecl(FoundDecl)); + } + return USRSet; + } + + bool VisitCXXMethodDecl(const CXXMethodDecl *MethodDecl) { + if (MethodDecl->isVirtual()) + OverriddenMethods.push_back(MethodDecl); + return true; + } + + bool VisitObjCMethodDecl(const ObjCMethodDecl *MethodDecl) { + if (const auto *FoundMethodDecl = dyn_cast(FoundDecl)) + if (DeclarationName::compare(MethodDecl->getDeclName(), + FoundMethodDecl->getDeclName()) == 0 && + MethodDecl->isOverriding()) + PotentialObjCMethodOverridders.push_back(MethodDecl); + return true; + } + + bool VisitClassTemplatePartialSpecializationDecl( + const ClassTemplatePartialSpecializationDecl *PartialSpec) { + if (!isa(FoundDecl) && !isa(FoundDecl)) + return true; + PartialSpecs.push_back(PartialSpec); + return true; + } + +private: + void handleCXXRecordDecl(const CXXRecordDecl *RecordDecl, + llvm::StringSet<> &USRSet) { + const auto *RD = RecordDecl->getDefinition(); + if (!RD) { + USRSet.insert(getUSRForDecl(RecordDecl)); + return; + } + if (const auto *ClassTemplateSpecDecl = + dyn_cast(RD)) + handleClassTemplateDecl(ClassTemplateSpecDecl->getSpecializedTemplate(), + USRSet); + addUSRsOfCtorDtors(RD, USRSet); + } + + void handleClassTemplateDecl(const ClassTemplateDecl *TemplateDecl, + llvm::StringSet<> &USRSet) { + for (const auto *Specialization : TemplateDecl->specializations()) + addUSRsOfCtorDtors(Specialization, USRSet); + + for (const auto *PartialSpec : PartialSpecs) { + if (PartialSpec->getSpecializedTemplate() == TemplateDecl) + addUSRsOfCtorDtors(PartialSpec, USRSet); + } + addUSRsOfCtorDtors(TemplateDecl->getTemplatedDecl(), USRSet); + } + + void addUSRsOfCtorDtors(const CXXRecordDecl *RecordDecl, + llvm::StringSet<> &USRSet) { + const CXXRecordDecl *RD = RecordDecl; + RecordDecl = RD->getDefinition(); + if (!RecordDecl) { + USRSet.insert(getUSRForDecl(RD)); + return; + } + + for (const auto *CtorDecl : RecordDecl->ctors()) { + auto USR = getUSRForDecl(CtorDecl); + if (!USR.empty()) + USRSet.insert(USR); + } + + auto USR = getUSRForDecl(RecordDecl->getDestructor()); + if (!USR.empty()) + USRSet.insert(USR); + USRSet.insert(getUSRForDecl(RecordDecl)); + } + + void addUSRsOfOverridenFunctions(const CXXMethodDecl *MethodDecl, + llvm::StringSet<> &USRSet) { + USRSet.insert(getUSRForDecl(MethodDecl)); + // Recursively visit each OverridenMethod. + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) + addUSRsOfOverridenFunctions(OverriddenMethod, USRSet); + } + + bool checkIfOverriddenFunctionAscends(const CXXMethodDecl *MethodDecl, + const llvm::StringSet<> &USRSet) { + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) { + if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end()) + return true; + return checkIfOverriddenFunctionAscends(OverriddenMethod, USRSet); + } + return false; + } + + /// \brief Recursively visit all the methods which the given method + /// declaration overrides and adds them to the USR set. + void addUSRsOfOverriddenObjCMethods(const ObjCMethodDecl *MethodDecl, + llvm::StringSet<> &USRSet) { + // Exit early if this method was already visited. + if (!USRSet.insert(getUSRForDecl(MethodDecl)).second) + return; + SmallVector Overrides; + MethodDecl->getOverriddenMethods(Overrides); + for (const auto &OverriddenMethod : Overrides) + addUSRsOfOverriddenObjCMethods(OverriddenMethod, USRSet); + } + + /// \brief Returns true if the given Objective-C method overrides the + /// found Objective-C method declaration. + bool checkIfPotentialObjCMethodOverriddes(const ObjCMethodDecl *MethodDecl, + const llvm::StringSet<> &USRSet) { + SmallVector Overrides; + MethodDecl->getOverriddenMethods(Overrides); + for (const auto &OverriddenMethod : Overrides) { + if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end()) + return true; + if (checkIfPotentialObjCMethodOverriddes(OverriddenMethod, USRSet)) + return true; + } + return false; + } + + const Decl *FoundDecl; + ASTContext &Context; + std::vector OverriddenMethods; + std::vector PartialSpecs; + /// \brief An array of Objective-C methods that potentially override the + /// found Objective-C method declaration \p FoundDecl. + std::vector PotentialObjCMethodOverridders; +}; +} // end anonymous namespace + +namespace clang { +namespace tooling { + +llvm::StringSet<> findSymbolsUSRSet(const NamedDecl *FoundDecl, + ASTContext &Context) { + return AdditionalUSRFinder(FoundDecl, Context).Find(); +} + +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/TypeUtils.cpp b/clang/lib/Tooling/Refactor/TypeUtils.cpp new file mode 100644 index 0000000000000..3a36aec1d5ba4 --- /dev/null +++ b/clang/lib/Tooling/Refactor/TypeUtils.cpp @@ -0,0 +1,193 @@ +//===--- TypeUtils.cpp - Type helper functions ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TypeUtils.h" +#include "clang/AST/Expr.h" +#include "clang/AST/NSAPI.h" +#include "clang/AST/RecursiveASTVisitor.h" + +using namespace clang; + +namespace { + +/// Returns false if a BOOL expression is found. +class BOOLUseFinder : public RecursiveASTVisitor { +public: + NSAPI API; + + BOOLUseFinder(const ASTContext &Context) + : API(const_cast(Context)) {} + + bool VisitStmt(const Stmt *S) { + if (const auto *E = dyn_cast(S)) + return !API.isObjCBOOLType(E->getType()); + return true; + } + + static bool hasUseOfObjCBOOL(const ASTContext &Ctx, const Expr *E) { + return !BOOLUseFinder(Ctx).TraverseStmt(const_cast(E)); + } +}; + +} // end anonymous namespace + +static QualType preferredBoolType(const Decl *FunctionLikeParentDecl, + const Expr *E, QualType T, + const PrintingPolicy &Policy, + const ASTContext &Ctx) { + // We want to target expressions that return either 'int' or 'bool' + const auto *BTy = T->getAs(); + if (!BTy) + return T; + switch (BTy->getKind()) { + case BuiltinType::Int: + case BuiltinType::Bool: + // In Objective-C[++] we want to try to use 'BOOL' when the 'BOOL' typedef + // is defined. + if (Ctx.getLangOpts().ObjC1 && Ctx.getBOOLDecl()) { + if (Ctx.getLangOpts().CPlusPlus && FunctionLikeParentDecl) { + // When extracting expression from a standalone function in + // Objective-C++ we should use BOOL when expression uses BOOL, otherwise + // we should use bool. + if (isa(FunctionLikeParentDecl)) { + if (BOOLUseFinder::hasUseOfObjCBOOL(Ctx, E)) + return Ctx.getBOOLType(); + return T; + } + } + return Ctx.getBOOLType(); + } + // In C mode we want to use 'bool' instead of 'int' when the 'bool' macro + // is defined. + if (!Ctx.getLangOpts().CPlusPlus && Policy.Bool) + return Ctx.BoolTy; + break; + default: + break; + } + return T; +} + +static bool isInStdNamespace(const Decl *D) { + const DeclContext *DC = D->getDeclContext()->getEnclosingNamespaceContext(); + const NamespaceDecl *ND = dyn_cast(DC); + if (!ND) + return false; + + while (const DeclContext *Parent = ND->getParent()) { + if (!isa(Parent)) + break; + ND = cast(Parent); + } + + return ND->isStdNamespace(); +} + +static QualType desugarStdTypedef(QualType T) { + const auto *TT = T->getAs(); + if (!TT) + return QualType(); + const TypedefNameDecl *TND = TT->getDecl(); + if (!isInStdNamespace(TND)) + return QualType(); + return TT->desugar(); +} + +// Desugars a typedef of a typedef that are both defined in STL. +// +// This is used to find the right type for a c_str() call on a std::string +// object: we want to return const char *, not const value_type *. +static QualType desugarStdType(QualType T) { + QualType DesugaredType = T; + if (const auto *PT = T->getAs()) + DesugaredType = PT->getPointeeType(); + DesugaredType = desugarStdTypedef(DesugaredType); + if (DesugaredType.isNull()) + return T; + if (const auto *ET = DesugaredType->getAs()) + DesugaredType = ET->desugar(); + DesugaredType = desugarStdTypedef(DesugaredType); + if (DesugaredType.isNull()) + return T; + return T.getCanonicalType(); +} + +// Given an operator call like std::string() + "", we would like to ensure +// that we return std::string instead of std::basic_string. +static QualType canonicalizeStdOperatorReturnType(const Expr *E, QualType T) { + const auto *OCE = dyn_cast(E->IgnoreParenImpCasts()); + if (!OCE) + return T; + if (OCE->getNumArgs() < 2 || !isInStdNamespace(OCE->getCalleeDecl())) + return T; + QualType CanonicalReturn = T.getCanonicalType(); + if (const auto *RD = CanonicalReturn->getAsCXXRecordDecl()) { + if (!isInStdNamespace(RD)) + return T; + } else + return T; + for (unsigned I = 0, E = OCE->getNumArgs(); I < E; ++I) { + const Expr *Arg = OCE->getArgs()[I]; + QualType T = Arg->getType(); + if (const auto *ET = dyn_cast(T)) + T = ET->desugar(); + if (desugarStdTypedef(T).isNull()) + continue; + QualType CanonicalArg = Arg->getType().getCanonicalType(); + CanonicalArg.removeLocalFastQualifiers(); + if (CanonicalArg == CanonicalReturn) { + QualType Result = Arg->getType(); + Result.removeLocalFastQualifiers(); + return Result; + } + } + return T; +} + +namespace clang { +namespace tooling { + +/// Tthe return type of the extracted function should match user's intent, +/// e.g. we want to use bool type whenever possible. +QualType findExpressionLexicalType(const Decl *FunctionLikeParentDecl, + const Expr *E, QualType T, + const PrintingPolicy &Policy, + const ASTContext &Ctx) { + // Get the correct property type. + if (const auto *PRE = dyn_cast(E)) { + if (PRE->isMessagingGetter()) { + if (PRE->isExplicitProperty()) + return PRE->getExplicitProperty()->getType(); + if (const ObjCMethodDecl *M = PRE->getImplicitPropertyGetter()) + return M->getReturnType(); + } + } + + // Perform STL-specific type corrections. + if (Ctx.getLangOpts().CPlusPlus) { + T = desugarStdType(T); + T = canonicalizeStdOperatorReturnType(E, T); + } + + // The bool type adjustment is required only in C or Objective-C[++]. + if (Ctx.getLangOpts().CPlusPlus && !Ctx.getLangOpts().ObjC1) + return T; + E = E->IgnoreParenImpCasts(); + if (const auto *BinOp = dyn_cast(E)) { + if (BinOp->isLogicalOp() || BinOp->isComparisonOp()) + return preferredBoolType(FunctionLikeParentDecl, E, T, Policy, Ctx); + } else if (const auto *UnOp = dyn_cast(E)) { + if (UnOp->getOpcode() == UO_LNot) + return preferredBoolType(FunctionLikeParentDecl, E, T, Policy, Ctx); + } + return T; +} + +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactor/TypeUtils.h b/clang/lib/Tooling/Refactor/TypeUtils.h new file mode 100644 index 0000000000000..8d575e8bb9245 --- /dev/null +++ b/clang/lib/Tooling/Refactor/TypeUtils.h @@ -0,0 +1,35 @@ +//===--- TypeUtils.h - Type helper functions ------------------------------===// +// +// 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_TOOLING_REFACTOR_TYPE_UTILS_H +#define LLVM_CLANG_LIB_TOOLING_REFACTOR_TYPE_UTILS_H + +#include "clang/AST/Type.h" + +namespace clang { + +class Decl; + +namespace tooling { + +/// \brief Find the most lexically appropriate type that can be used to describe +/// the return type of the given expression \p E. +/// +/// When extracting code, we want to produce a function that returns a type +/// that matches the user's intent. This function can be used to find such a +/// type. +QualType findExpressionLexicalType(const Decl *FunctionLikeParentDecl, + const Expr *E, QualType T, + const PrintingPolicy &Policy, + const ASTContext &Ctx); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTOR_TYPE_UTILS_H diff --git a/clang/lib/Tooling/Refactor/USRFinder.cpp b/clang/lib/Tooling/Refactor/USRFinder.cpp new file mode 100644 index 0000000000000..d4e78bd28a6b3 --- /dev/null +++ b/clang/lib/Tooling/Refactor/USRFinder.cpp @@ -0,0 +1,698 @@ +//===--- USRFinder.cpp - Clang refactoring library ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file Implements a recursive AST visitor that finds the USR of a symbol at a +/// point. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactor/USRFinder.h" +#include "SourceLocationUtilities.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DependentASTVisitor.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Core/RefactoringDiagnostic.h" +#include "llvm/ADT/SmallVector.h" +#include + +using namespace llvm; + +namespace clang { +namespace tooling { +namespace rename { + +typedef std::function + OccurrenceCheckerType; + +// NamedDeclFindingASTVisitor recursively visits each AST node to find the +// symbol underneath the cursor. +// FIXME: move to seperate .h/.cc file if this gets too large. +namespace { +class NamedDeclFindingASTVisitor + : public DependentASTVisitor { +public: + // \brief Finds the NamedDecl at a point in the source. + // \param Point the location in the source to search for the NamedDecl. + explicit NamedDeclFindingASTVisitor( + const OccurrenceCheckerType &OccurrenceChecker, const ASTContext &Context) + : Result(nullptr), OccurrenceChecker(OccurrenceChecker), + Context(Context) {} + + // Declaration visitors: + + // \brief Checks if the point falls within the NameDecl. This covers every + // declaration of a named entity that we may come across. Usually, just + // checking if the point lies within the length of the name of the declaration + // and the start location is sufficient. + bool VisitNamedDecl(const NamedDecl *Decl) { + return dyn_cast(Decl) + ? true + : checkOccurrence(Decl, Decl->getLocation(), + Decl->getNameAsString().length()); + } + + bool WalkUpFromTypedefNameDecl(const TypedefNameDecl *D) { + // Don't visit the NamedDecl for TypedefNameDecl. + return VisitTypedefNamedDecl(D); + } + + bool VisitTypedefNamedDecl(const TypedefNameDecl *D) { + if (D->isTransparentTag()) { + if (const auto *Underlying = D->getUnderlyingType()->getAsTagDecl()) + return checkOccurrence(Underlying, D->getLocation(), + D->getNameAsString().size()); + } + return VisitNamedDecl(D); + } + + bool WalkUpFromUsingDecl(const UsingDecl *D) { + // Don't visit the NamedDecl for UsingDecl. + return VisitUsingDecl(D); + } + + bool VisitUsingDecl(const UsingDecl *D) { + for (const auto *Shadow : D->shadows()) { + // Currently we always find the first declaration, but is this the right + // behaviour? + const NamedDecl *UD = Shadow->getUnderlyingDecl(); + if (UD->isImplicit() || UD == D) + continue; + if (const auto *FTD = dyn_cast(UD)) { + UD = FTD->getTemplatedDecl(); + if (!UD) + continue; + } + if (!checkOccurrence(UD, D->getLocation())) + return false; + } + return true; + } + + bool WalkUpFromUsingDirectiveDecl(const UsingDirectiveDecl *D) { + // Don't visit the NamedDecl for UsingDirectiveDecl. + return VisitUsingDirectiveDecl(D); + } + + bool VisitUsingDirectiveDecl(const UsingDirectiveDecl *D) { + return checkOccurrence(D->getNominatedNamespaceAsWritten(), + D->getLocation()); + } + + bool WalkUpFromUnresolvedUsingValueDecl(const UnresolvedUsingValueDecl *D) { + // Don't visit the NamedDecl for UnresolvedUsingValueDecl. + // FIXME: Can we try to lookup the name? + return true; + } + + bool + WalkUpFromUnresolvedUsingTypenameDecl(const UnresolvedUsingTypenameDecl *D) { + // Don't visit the NamedDecl for UnresolvedUsingTypenameDecl. + // FIXME: Can we try to lookup the name? + return true; + } + + bool WalkUpFromObjCMethodDecl(const ObjCMethodDecl *Decl) { + // Don't visit the NamedDecl for Objective-C methods. + return VisitObjCMethodDecl(Decl); + } + + bool VisitObjCMethodDecl(const ObjCMethodDecl *Decl) { + // Check all of the selector source ranges. + for (unsigned I = 0, E = Decl->getNumSelectorLocs(); I != E; ++I) { + if (!checkOccurrence(Decl, Decl->getSelectorLoc(I), + Decl->getSelector().getNameForSlot(I).size())) + return false; + } + return true; + } + + bool VisitObjCProtocolList(const ObjCProtocolList &Protocols) { + for (unsigned I = 0, E = Protocols.size(); I != E; ++I) { + if (!checkOccurrence(Protocols[I], Protocols.loc_begin()[I])) + return false; + } + return true; + } + + bool VisitObjCInterfaceDecl(const ObjCInterfaceDecl *Decl) { + if (!Decl->hasDefinition()) + return true; + return VisitObjCProtocolList(Decl->getReferencedProtocols()); + } + + bool VisitObjCProtocolDecl(const ObjCProtocolDecl *Decl) { + if (!Decl->hasDefinition()) + return true; + return VisitObjCProtocolList(Decl->getReferencedProtocols()); + } + + bool WalkUpFromObjCCategoryDecl(const ObjCCategoryDecl *Decl) { + // Don't visit the NamedDecl for Objective-C categories because the location + // of the name refers to the interface declaration. + return VisitObjCCategoryDecl(Decl); + } + + bool VisitObjCCategoryDecl(const ObjCCategoryDecl *Decl) { + if (!checkOccurrence(Decl, Decl->getCategoryNameLoc())) + return false; + if (const auto *Class = Decl->getClassInterface()) { + // The location of the class name is the location of the declaration. + if (!checkOccurrence(Class, Decl->getLocation())) + return false; + } + return VisitObjCProtocolList(Decl->getReferencedProtocols()); + } + + bool WalkUpFromObjCCategoryImplDecl(const ObjCCategoryImplDecl *Decl) { + // Don't visit the NamedDecl for Objective-C categories because the location + // of the name refers to the interface declaration. + return VisitObjCCategoryImplDecl(Decl); + } + + bool VisitObjCCategoryImplDecl(const ObjCCategoryImplDecl *Decl) { + if (!checkOccurrence(Decl, Decl->getCategoryNameLoc())) + return false; + if (const auto *Class = Decl->getClassInterface()) { + // The location of the class name is the location of the declaration. + if (!checkOccurrence(Class, Decl->getLocation())) + return false; + } + return true; + } + + bool VisitObjCCompatibleAliasDecl(const ObjCCompatibleAliasDecl *Decl) { + return checkOccurrence(Decl->getClassInterface(), + Decl->getClassInterfaceLoc()); + } + + bool WalkUpFromObjCIvarDecl(ObjCIvarDecl *Decl) { + // Don't visit the NamedDecl for automatically synthesized ivars as the + // implicit ivars have the same location as the property declarations, and + // we want to find the property declarations. + if (Decl->getSynthesize()) + return true; + return RecursiveASTVisitor::WalkUpFromObjCIvarDecl(Decl); + } + + bool VisitObjCPropertyDecl(const ObjCPropertyDecl *Decl) { + if (Decl->hasExplicitGetterName()) { + if (const auto *Getter = Decl->getGetterMethodDecl()) + if (!checkOccurrence(Getter, Decl->getGetterNameLoc(), + Decl->getGetterName().getNameForSlot(0).size())) + return false; + } + if (Decl->hasExplicitSetterName()) { + if (const auto *Setter = Decl->getSetterMethodDecl()) + return checkOccurrence(Setter, Decl->getSetterNameLoc(), + Decl->getSetterName().getNameForSlot(0).size()); + } + return true; + } + + bool VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *Decl) { + if (!checkOccurrence(Decl->getPropertyDecl(), Decl->getLocation())) + return false; + if (Decl->isIvarNameSpecified()) + return checkOccurrence(Decl->getPropertyIvarDecl(), + Decl->getPropertyIvarDeclLoc()); + return true; + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + return checkOccurrence(Decl, Expr->getLocation(), + Decl->getNameAsString().length()); + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); + return checkOccurrence(Decl, Expr->getMemberLoc(), + Decl->getNameAsString().length()); + } + + bool VisitObjCMessageExpr(const ObjCMessageExpr *Expr) { + const ObjCMethodDecl *Decl = Expr->getMethodDecl(); + if (Decl == nullptr) + return true; + + // Check all of the selector source ranges. + for (unsigned I = 0, E = Expr->getNumSelectorLocs(); I != E; ++I) { + if (!checkOccurrence(Decl, Expr->getSelectorLoc(I), + Decl->getSelector().getNameForSlot(I).size())) + return false; + } + return true; + } + + bool VisitObjCProtocolExpr(const ObjCProtocolExpr *Expr) { + return checkOccurrence(Expr->getProtocol(), Expr->getProtocolIdLoc()); + } + + bool VisitObjCIvarRefExpr(const ObjCIvarRefExpr *Expr) { + return checkOccurrence(Expr->getDecl(), Expr->getLocation()); + } + + bool VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *Expr) { + if (Expr->isClassReceiver()) + checkOccurrence(Expr->getClassReceiver(), Expr->getReceiverLocation()); + if (Expr->isImplicitProperty()) { + // Class properties that are explicitly defined using @property + // declarations are represented implicitly as there is no ivar for class + // properties. + if (const ObjCMethodDecl *Getter = Expr->getImplicitPropertyGetter()) { + if (Getter->isClassMethod()) { + if (const auto *PD = Getter->getCanonicalDecl()->findPropertyDecl()) + return checkOccurrence(PD, Expr->getLocation()); + } + } + + if (Expr->isMessagingGetter()) { + if (const ObjCMethodDecl *Getter = Expr->getImplicitPropertyGetter()) + return checkOccurrence(Getter, Expr->getLocation()); + } else if (const ObjCMethodDecl *Setter = + Expr->getImplicitPropertySetter()) { + return checkOccurrence(Setter, Expr->getLocation()); + } + + return true; + } + return checkOccurrence(Expr->getExplicitProperty(), Expr->getLocation()); + } + + // Other visitors: + + bool VisitTypeLoc(const TypeLoc Loc) { + const SourceLocation TypeBeginLoc = Loc.getBeginLoc(); + const SourceLocation TypeEndLoc = Lexer::getLocForEndOfToken( + TypeBeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); + if (const auto *TemplateTypeParm = + dyn_cast(Loc.getType())) + return checkOccurrence(TemplateTypeParm->getDecl(), TypeBeginLoc, + TypeEndLoc); + if (const auto *TemplateSpecType = + dyn_cast(Loc.getType())) { + return checkOccurrence( + TemplateSpecType->getTemplateName().getAsTemplateDecl(), TypeBeginLoc, + TypeEndLoc); + } + TypedefTypeLoc TTL = Loc.getAs(); + if (TTL) { + const auto *TND = TTL.getTypedefNameDecl(); + if (TND->isTransparentTag()) { + if (const auto *Underlying = TND->getUnderlyingType()->getAsTagDecl()) + return checkOccurrence(Underlying, TTL.getNameLoc()); + } + return checkOccurrence(TND, TTL.getNameLoc()); + } + TypeSpecTypeLoc TSTL = Loc.getAs(); + if (TSTL) { + return checkOccurrence(Loc.getType()->getAsTagDecl(), TSTL.getNameLoc()); + } + return true; + } + + bool VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc Loc) { + return checkOccurrence(Loc.getIFaceDecl(), Loc.getNameLoc()); + } + + bool VisitObjCObjectTypeLoc(ObjCObjectTypeLoc Loc) { + for (unsigned I = 0, E = Loc.getNumProtocols(); I < E; ++I) { + if (!checkOccurrence(Loc.getProtocol(I), Loc.getProtocolLoc(I))) + return false; + } + return true; + } + + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { + for (const auto *Initializer : ConstructorDecl->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { + const SourceLocation InitBeginLoc = Initializer->getSourceLocation(), + InitEndLoc = Lexer::getLocForEndOfToken( + InitBeginLoc, 0, Context.getSourceManager(), + Context.getLangOpts()); + if (!checkOccurrence(FieldDecl, InitBeginLoc, InitEndLoc)) + return false; + } + } + return true; + } + + bool VisitDependentSymbolReference(const NamedDecl *Symbol, + SourceLocation SymbolNameLoc) { + return checkOccurrence(Symbol, SymbolNameLoc); + } + + // Other: + + const NamedDecl *getNamedDecl() { return Result; } + + bool isDone() const { return Result; } + + // \brief Determines if a namespace qualifier contains the point. + // \returns false on success and sets Result. + void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const NamespaceDecl *Decl = + NameLoc.getNestedNameSpecifier()->getAsNamespace(); + checkOccurrence(Decl, NameLoc.getLocalBeginLoc(), + NameLoc.getLocalEndLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + +private: + /// \brief Sets Result to \p Decl if the occurrence checker returns true. + /// + /// \returns false on success. + bool checkRange(const NamedDecl *Decl, SourceLocation Start, + SourceLocation End) { + assert(!Start.isMacroID() && !End.isMacroID() && "Macro location?"); + if (!Decl) + return true; + if (isa(Decl)) + return true; + if (const auto *FD = dyn_cast(Decl)) { + // Don't match operators. + if (FD->isOverloadedOperator()) + return true; + } + if (!OccurrenceChecker(Decl, Start, End)) + return true; + Result = Decl; + return false; + } + + /// Checks if the given declaration is valid, and if it is, sets Result to + /// \p Decl if the occurrence checker returns true. + /// + /// \returns false if the point of interest is inside the range that + /// corresponds the occurrence of this declaration. + bool checkOccurrence(const NamedDecl *Decl, SourceLocation Loc) { + if (!Decl) + return true; + return checkOccurrence(Decl, Loc, Decl->getNameAsString().size()); + } + + /// \brief Sets Result to \p Decl if the occurrence checker returns true. + /// + /// \returns false on success. + bool checkOccurrence(const NamedDecl *Decl, SourceLocation Loc, + unsigned Length) { + if (Loc.isMacroID()) { + const SourceManager &SM = Context.getSourceManager(); + if (SM.isMacroArgExpansion(Loc)) + Loc = SM.getSpellingLoc(Loc); + else + return true; + } + + return Length == 0 || + checkRange(Decl, Loc, Loc.getLocWithOffset(Length - 1)); + } + + bool checkOccurrence(const NamedDecl *ND, SourceLocation Start, + SourceLocation End) { + const SourceManager &SM = Context.getSourceManager(); + if (Start.isMacroID()) { + if (SM.isMacroArgExpansion(Start)) + Start = SM.getSpellingLoc(Start); + else + return true; + } + if (End.isMacroID()) { + if (SM.isMacroArgExpansion(End)) + End = SM.getSpellingLoc(End); + else + return true; + } + return checkRange(ND, Start, End); + } + + const NamedDecl *Result; + const OccurrenceCheckerType &OccurrenceChecker; + const ASTContext &Context; +}; + +} // namespace + +static const ExternalSourceSymbolAttr *getExternalSymAttr(const Decl *D) { + if (const auto *A = D->getAttr()) + return A; + if (const auto *DCD = dyn_cast(D->getDeclContext())) { + if (const auto *A = DCD->getAttr()) + return A; + } + return nullptr; +} + +static bool overridesSystemMethod(const ObjCMethodDecl *MD, + const SourceManager &SM) { + SmallVector Overrides; + MD->getOverriddenMethods(Overrides); + for (const auto *Override : Overrides) { + SourceLocation Loc = Override->getLocStart(); + if (Loc.isValid()) { + if (SM.getFileCharacteristic(Loc) != SrcMgr::C_User) + return true; + } + if (overridesSystemMethod(Override, SM)) + return true; + } + return false; +} + +// TODO: Share with the indexer? +static bool isTemplateImplicitInstantiation(const Decl *D) { + TemplateSpecializationKind TKind = TSK_Undeclared; + if (const ClassTemplateSpecializationDecl *SD = + dyn_cast(D)) { + TKind = SD->getSpecializationKind(); + } else if (const FunctionDecl *FD = dyn_cast(D)) { + TKind = FD->getTemplateSpecializationKind(); + } else if (auto *VD = dyn_cast(D)) { + TKind = VD->getTemplateSpecializationKind(); + } else if (const auto *RD = dyn_cast(D)) { + if (RD->getInstantiatedFromMemberClass()) + TKind = RD->getTemplateSpecializationKind(); + } else if (const auto *ED = dyn_cast(D)) { + if (ED->getInstantiatedFromMemberEnum()) + TKind = ED->getTemplateSpecializationKind(); + } else if (isa(D) || isa(D) || + isa(D)) { + if (const auto *Parent = dyn_cast(D->getDeclContext())) + return isTemplateImplicitInstantiation(Parent); + } + switch (TKind) { + case TSK_Undeclared: + case TSK_ExplicitSpecialization: + return false; + case TSK_ImplicitInstantiation: + case TSK_ExplicitInstantiationDeclaration: + case TSK_ExplicitInstantiationDefinition: + return true; + } + llvm_unreachable("invalid TemplateSpecializationKind"); +} + +static const CXXRecordDecl * +getDeclContextForTemplateInstationPattern(const Decl *D) { + if (const auto *CTSD = + dyn_cast(D->getDeclContext())) + return CTSD->getTemplateInstantiationPattern(); + else if (const auto *RD = dyn_cast(D->getDeclContext())) + return RD->getInstantiatedFromMemberClass(); + return nullptr; +} + +static const NamedDecl * +adjustTemplateImplicitInstantiation(const NamedDecl *D) { + if (const ClassTemplateSpecializationDecl *SD = + dyn_cast(D)) { + return SD->getTemplateInstantiationPattern(); + } else if (const FunctionDecl *FD = dyn_cast(D)) { + return FD->getTemplateInstantiationPattern(); + } else if (auto *VD = dyn_cast(D)) { + return VD->getTemplateInstantiationPattern(); + } else if (const auto *RD = dyn_cast(D)) { + return RD->getInstantiatedFromMemberClass(); + } else if (const auto *ED = dyn_cast(D)) { + return ED->getInstantiatedFromMemberEnum(); + } else if (isa(D) || isa(D)) { + const auto *ND = cast(D); + if (const CXXRecordDecl *Pattern = + getDeclContextForTemplateInstationPattern(ND)) { + for (const NamedDecl *BaseND : Pattern->lookup(ND->getDeclName())) { + if (BaseND->isImplicit()) + continue; + if (BaseND->getKind() == ND->getKind()) + return BaseND; + } + } + } else if (const auto *ECD = dyn_cast(D)) { + if (const auto *ED = dyn_cast(ECD->getDeclContext())) { + if (const EnumDecl *Pattern = ED->getInstantiatedFromMemberEnum()) { + for (const NamedDecl *BaseECD : Pattern->lookup(ECD->getDeclName())) + return BaseECD; + } + } + } + return D; +} + +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + SourceLocation Point) { + if (Point.isMacroID()) + Point = Context.getSourceManager().getSpellingLoc(Point); + + OccurrenceCheckerType PointChecker = [Point, &Context]( + const NamedDecl *Decl, SourceLocation Start, SourceLocation End) -> bool { + return Start.isValid() && Start.isFileID() && End.isValid() && + End.isFileID() && + isPointWithin(Point, Start, End, Context.getSourceManager()); + }; + NamedDeclFindingASTVisitor Visitor(PointChecker, Context); + + // We only want to search the decls that exist in the same file as the point. + StringRef SearchFile = Context.getSourceManager().getFilename(Point); + for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { + const SourceLocation FileLoc = CurrDecl->getLocStart(); + StringRef FileName = Context.getSourceManager().getFilename(FileLoc); + // FIME: Skip system headers. + // FIXME: Add test. + if (FileName == SearchFile) + Visitor.TraverseDecl(CurrDecl); + if (Visitor.isDone()) + break; + } + + if (!Visitor.isDone()) { + NestedNameSpecifierLocFinder Finder(const_cast(Context)); + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) { + Visitor.handleNestedNameSpecifierLoc(Location); + if (Visitor.isDone()) + break; + } + } + + const auto Diag = [&](unsigned DiagID) -> DiagnosticBuilder { + return Context.getDiagnostics().Report(Point, DiagID); + }; + const auto *ND = Visitor.getNamedDecl(); + if (!ND) + return nullptr; + + // Canonicalize the found declaration. + // + // If FoundDecl is a constructor or destructor, we want to instead take + // the Decl of the corresponding class. + if (const auto *CtorDecl = dyn_cast(ND)) + ND = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast(ND)) + ND = DtorDecl->getParent(); + + if (isTemplateImplicitInstantiation(ND)) + ND = adjustTemplateImplicitInstantiation(ND); + + // Builtins can't be renamed. + if (const auto *FD = dyn_cast(ND)) { + if (FD->getBuiltinID()) { + Diag(diag::err_rename_builtin_function) << ND->getDeclName(); + return nullptr; + } + } + // Declarations with invalid locations are probably implicit. + if (ND->getLocStart().isInvalid()) + return nullptr; + // Declarations in system headers can't be renamed. + auto CheckSystemLoc = [&](SourceLocation Loc) -> bool { + if (Context.getSourceManager().getFileCharacteristic(Loc) != + SrcMgr::C_User) { + Diag(diag::err_rename_sys_header) << ND->getDeclName(); + return true; + } + return false; + }; + if (CheckSystemLoc(ND->getLocStart())) + return nullptr; + if (const auto *TD = dyn_cast(ND)) { + if (const TypedefNameDecl *CTD = TD->getCanonicalDecl()) { + if (CheckSystemLoc(CTD->getLocStart())) + return nullptr; + } + } else if (const auto *TD = dyn_cast(ND)) { + if (const TagDecl *CTD = TD->getCanonicalDecl()) { + if (CheckSystemLoc(CTD->getLocStart())) + return nullptr; + } + } else if (const auto *FD = dyn_cast(ND)) { + if (const FunctionDecl *CFD = FD->getCanonicalDecl()) { + if (CheckSystemLoc(CFD->getLocStart())) + return nullptr; + } + } else if (const auto *VD = dyn_cast(ND)) { + if (const VarDecl *CVD = VD->getCanonicalDecl()) { + if (CheckSystemLoc(CVD->getLocStart())) + return nullptr; + } + } + // Declarations from other languages can't be renamed. + if (const ExternalSourceSymbolAttr *ESSA = getExternalSymAttr(ND)) { + Diag(diag::err_rename_external_source_symbol) << ND->getDeclName() + << ESSA->getLanguage(); + return nullptr; + } + // Methods that override methods from system headers can't be renamed. + if (const auto *MD = dyn_cast(ND)) { + if (overridesSystemMethod(MD, Context.getSourceManager())) { + Diag(diag::err_method_rename_override_sys_framework) << ND->getDeclName(); + return nullptr; + } + } + return ND; +} + +const NamedDecl *getNamedDeclWithUSR(const ASTContext &Context, StringRef USR) { + // TODO: Remove in favour of the new converter. + OccurrenceCheckerType USRChecker = + [USR](const NamedDecl *Decl, SourceLocation Start, SourceLocation End) { + return USR == getUSRForDecl(Decl); + }; + NamedDeclFindingASTVisitor Visitor(USRChecker, Context); + + for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { + Visitor.TraverseDecl(CurrDecl); + if (Visitor.isDone()) + break; + } + + // Don't need to visit nested name specifiers as they refer to previously + // declared declarations that we've already seen. + return Visitor.getNamedDecl(); +} + +std::string getUSRForDecl(const Decl *Decl) { + llvm::SmallVector Buff; + + // FIXME: Add test for the nullptr case. + if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff)) + return ""; + + return std::string(Buff.data(), Buff.size()); +} + +} // end namespace rename +} // end namespace tooling +} // end namespace clang diff --git a/clang/lib/Tooling/Refactoring/CMakeLists.txt b/clang/lib/Tooling/Refactoring/CMakeLists.txt index b2f9b4f4c0cdd..2520bb9598a82 100644 --- a/clang/lib/Tooling/Refactoring/CMakeLists.txt +++ b/clang/lib/Tooling/Refactoring/CMakeLists.txt @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS Support ) -add_clang_library(clangToolingRefactor +add_clang_library(clangToolingRefactoring AtomicChange.cpp LINK_LIBS diff --git a/clang/test/Analysis/misc-ps.m b/clang/test/Analysis/misc-ps.m index 9a75cfd87b623..c610a7d922c42 100644 --- a/clang/test/Analysis/misc-ps.m +++ b/clang/test/Analysis/misc-ps.m @@ -1086,7 +1086,7 @@ void test_enum_cases(enum Cases C) { } void test_enum_cases_positive(enum Cases C) { - switch (C) { // expected-warning{{enumeration value 'C4' not handled in switch}} + switch (C) { // expected-warning{{enumeration value 'C4' not handled in switch}} expected-note {{add missing switch cases}} case C1: case C2: case C3: diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt index 23d23bcddcc19..50e33449ed0e2 100644 --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -46,9 +46,10 @@ list(APPEND CLANG_TEST_DEPS c-index-test diagtool clang-tblgen clang-offload-bundler + clang-refactor-test clang-import-test ) - + if(CLANG_ENABLE_STATIC_ANALYZER) list(APPEND CLANG_TEST_DEPS clang-check diff --git a/clang/test/FixIt/fixit-fill-in-missing-switch-cases.cpp b/clang/test/FixIt/fixit-fill-in-missing-switch-cases.cpp new file mode 100644 index 0000000000000..20b948f46195e --- /dev/null +++ b/clang/test/FixIt/fixit-fill-in-missing-switch-cases.cpp @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 | FileCheck %s + +enum Color { + Black, + Blue, + White, + Gold +}; + +void fillInCases(Color c) { + switch (c) { + case Black: + break; + } +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:3-[[@LINE-1]]:3}:"case Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" + switch (c) { + } +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:3-[[@LINE-1]]:3}:"case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" +} + +enum class NoDenseMap: long long { + Baddie = 0x7fffffffffffffffLL, + BigBaddie = -0x7fffffffffffffffLL-1 +}; + +void fillInAllCases(NoDenseMap v) { + switch (v) { + } +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:3-[[@LINE-1]]:3}:"case NoDenseMap::Baddie:\n<#code#>\nbreak;\ncase NoDenseMap::BigBaddie:\n<#code#>\nbreak;\n" +} + diff --git a/clang/test/FixIt/fixit-fill-in-protocol-requirements.m b/clang/test/FixIt/fixit-fill-in-protocol-requirements.m new file mode 100644 index 0000000000000..c86b8c28672c8 --- /dev/null +++ b/clang/test/FixIt/fixit-fill-in-protocol-requirements.m @@ -0,0 +1,54 @@ +// RUN: %clang_cc1 -verify -Wno-objc-root-class -serialize-diagnostic-file /dev/null %s +// RUN: %clang_cc1 -fdiagnostics-parseable-fixits -serialize-diagnostic-file /dev/null %s 2>&1 | FileCheck %s + +@protocol P1 + +- (void)p2Method; + +@end + +@protocol P2 + +- (void)p1Method; + +@end + +@interface I + +@end + +@implementation I // expected-warning {{warning: class 'I' does not conform to protocols 'P2' and 'P1'}} expected-note {{add stubs for missing protocol requirements}} + +@end +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:1-[[@LINE-1]]:1}:"- (void)p2Method { \n <#code#>\n}\n\n- (void)p1Method { \n <#code#>\n}\n\n" + +@protocol P3 + ++ (void)p3ClassMethod; + +@end + +@interface I (Category) // expected-warning {{warning: category 'Category' does not conform to protocols 'P3'}} expected-note {{add stubs for missing protocol requirements}} + +@end + +@implementation I (Category) + +@end +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:1-[[@LINE-1]]:1}:"+ (void)p3ClassMethod { \n <#code#>\n}\n\n" + +@protocol P4 + +- (void)anotherMethod; + +@end + +@interface ThreeProtocols // expected-warning {{class 'ThreeProtocols' does not conform to protocols 'P2', 'P1' and 'P3'}} expected-note {{add stubs for missing protocol requirements}} +@end +@implementation ThreeProtocols +@end + +@interface FourProtocols // expected-warning {{class 'ThreeProtocols' does not conform to protocols 'P2', 'P1', 'P3', ...}} expected-note {{add stubs for missing protocol requirements}} +@end +@implementation FourProtocols +@end diff --git a/clang/test/Misc/ast-dump-decl.m b/clang/test/Misc/ast-dump-decl.m index 4cfb8aa0c41d4..bc5a0588cabf8 100644 --- a/clang/test/Misc/ast-dump-decl.m +++ b/clang/test/Misc/ast-dump-decl.m @@ -143,3 +143,6 @@ void f() { __typeof__(B.foo) Test; } // CHECK: VarDecl{{.*}}Test 'typeof (B.foo)':'int' + +@compatibility_alias TestCompatibilityAlias A; +// CHECK: ObjCCompatibleAliasDecl{{.*}}col:22 TestCompatibilityAlias col:45 diff --git a/clang/test/Parser/switch-recovery.cpp b/clang/test/Parser/switch-recovery.cpp index 4b06d55ba5944..019682f626a6a 100644 --- a/clang/test/Parser/switch-recovery.cpp +++ b/clang/test/Parser/switch-recovery.cpp @@ -21,7 +21,7 @@ struct B { void test2() { enum X { Xa, Xb } x; - switch (x) { // expected-warning {{enumeration value 'Xb' not handled in switch}} + switch (x) { // expected-warning {{enumeration value 'Xb' not handled in switch}} expected-note {{add missing switch cases}} case Xa; // expected-error {{expected ':' after 'case'}} break; } diff --git a/clang/test/Refactor/Extract/captured-variable-block-types.m b/clang/test/Refactor/Extract/captured-variable-block-types.m new file mode 100644 index 0000000000000..ef20181844bbf --- /dev/null +++ b/clang/test/Refactor/Extract/captured-variable-block-types.m @@ -0,0 +1,30 @@ + +int capturedBlock(void (^block)(int x, int (^)())) { + return capturedBlock(block); +} + +// CHECK1: extracted(void (^block)(int, int (^)())) + +typedef void (^BlockTypedef)(); + +int capturedBlockTypedef(BlockTypedef fp) { + return capturedBlockTypedef(fp); +} +// CHECK1: extracted(BlockTypedef fp) + +// RUN: clang-refactor-test perform -action extract -selected=%s:3:10-3:29 -selected=%s:11:10-11:34 %s -fblocks | FileCheck --check-prefix=CHECK1 %s + +@interface I +@end + +@implementation I + +- (void)method { + void (^block)(int x, int (^)()); + block(2, ^ (void) { return 0; }); +} +// CHECK2: - (void)extracted:(void (^)(int, int (^)()))block { + +@end + +// RUN: clang-refactor-test perform -action extract-method -selected=%s:24:3-24:35 %s -fblocks | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Extract/captured-variable-function-types.cpp b/clang/test/Refactor/Extract/captured-variable-function-types.cpp new file mode 100644 index 0000000000000..cf6ccee1a4673 --- /dev/null +++ b/clang/test/Refactor/Extract/captured-variable-function-types.cpp @@ -0,0 +1,17 @@ + +int capturedFunc(void (*fp)(int x)) { + auto fp2 = fp; + capturedFunc(fp); + return capturedFunc(fp2); +} + +// CHECK1: extracted(void (*fp)(int)) + +typedef void (*FuncTypedef)(); + +int capturedFuncTypedef(FuncTypedef fp) { + return capturedFuncTypedef(fp); +} +// CHECK1: extracted(FuncTypedef fp) + +// RUN: clang-refactor-test perform -action extract -selected=%s:4:3-4:19 -selected=%s:13:10-13:33 %s | FileCheck --check-prefix=CHECK1 %s diff --git a/clang/test/Refactor/Extract/captured-variable-lambda-type.cpp b/clang/test/Refactor/Extract/captured-variable-lambda-type.cpp new file mode 100644 index 0000000000000..6042830639cd6 --- /dev/null +++ b/clang/test/Refactor/Extract/captured-variable-lambda-type.cpp @@ -0,0 +1,18 @@ + +int capturedLambda(int x) { + auto Lambda = [] () { }; + Lambda(); + auto Lambda2 = [] (int x, int y) -> int { return x + y * 2; }; + int y = Lambda2(x, 1); + auto Lambda3 = [&] (int y) { + x = y + 2; + }; + Lambda3(3); + return Lambda2(x, 1); +} + +// CHECK1: extracted(const std::function &Lambda) +// CHECK1: extracted(const std::function int> &Lambda2, int x) +// CHECK1: extracted(const std::function int> &Lambda2, const std::function &Lambda3, int x) + +// RUN: clang-refactor-test perform -action extract -selected=%s:4:3-4:11 -selected=%s:6:3-6:24 -selected=%s:10:3-11:23 %s -std=c++11 | FileCheck --check-prefix=CHECK1 %s diff --git a/clang/test/Refactor/Extract/captured-variable-typedef.cpp b/clang/test/Refactor/Extract/captured-variable-typedef.cpp new file mode 100644 index 0000000000000..f6991ead44cef --- /dev/null +++ b/clang/test/Refactor/Extract/captured-variable-typedef.cpp @@ -0,0 +1,15 @@ +typedef signed int NSInteger; + +int capturedTypedef(NSInteger x) { + return capturedTypedef(x); +} +// CHECK1: "static int extracted(NSInteger x) {\nreturn capturedTypedef(x);\n}\n\n" [[@LINE-3]] +// RUN: clang-refactor-test perform -action extract -selected=%s:4:10-4:28 %s -std=c++14 | FileCheck --check-prefix=CHECK1 %s + +using NSUInteger = unsigned int; + +int capturedUsing(NSUInteger x) { + return capturedUsing(x); +} +// CHECK2: "static int extracted(NSUInteger x) {\nreturn capturedUsing(x);\n}\n\n" [[@LINE-3]] +// RUN: clang-refactor-test perform -action extract -selected=%s:12:10-12:26 %s -std=c++14 | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Extract/captured-variable-types.cpp b/clang/test/Refactor/Extract/captured-variable-types.cpp new file mode 100644 index 0000000000000..1a83884300c47 --- /dev/null +++ b/clang/test/Refactor/Extract/captured-variable-types.cpp @@ -0,0 +1,223 @@ + +typedef struct { + int width, height; +} Rectangle; + +int basicTypes(int i, float f, char c, const int *ip, float *fp, + const Rectangle *structPointer) { + return basicTypes(i, f, c, ip, fp, structPointer); +} +// CHECK1: "static int extracted(char c, float f, float *fp, int i, const int *ip, const Rectangle *structPointer) {\nreturn basicTypes(i, f, c, ip, fp, structPointer);\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK1-NEXT: "extracted(c, f, fp, i, ip, structPointer)" [[@LINE-3]]:10 -> [[@LINE-3]]:52 + +// RUN: clang-refactor-test perform -action extract -selected=%s:8:10-8:52 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:8:10-8:52 %s -x c | FileCheck --check-prefix=CHECK1 %s + +#ifndef __cplusplus +#define bool _Bool +#define true 1 +#define false 0 +#endif + +int boolType(bool b) { + bool b2 = true; + return boolType(b && b2 && true && false); +} +// CHECK2: "static int extracted(bool b, bool b2) {\nreturn boolType(b && b2 && true && false);\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK2-NEXT: "extracted(b, b2)" [[@LINE-3]]:10 -> [[@LINE-3]]:44 + +// RUN: clang-refactor-test perform -action extract -selected=%s:24:10-24:44 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:24:10-24:44 %s -x c | FileCheck --check-prefix=CHECK2 %s + +int global = 0; + +void dontCaptureGlobalVariable() { + static int staticInFunction = 0; + int i = global + staticInFunction; +} +// CHECK3: "static int extracted(int staticInFunction) {\nreturn global + staticInFunction;\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK3-NEXT: "extracted(staticInFunction)" [[@LINE-3]]:11 -> [[@LINE-3]]:36 + +// RUN: clang-refactor-test perform -action extract -selected=%s:36:11-36:36 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:36:11-36:36 %s -x c | FileCheck --check-prefix=CHECK3 %s + +#ifdef __cplusplus + +int referenceType(const Rectangle &r, int &i) { + return referenceType(r, i); +} +// CHECK4: "static int extracted(int &i, const Rectangle &r) {\nreturn referenceType(r, i);\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK4-NEXT: "extracted(i, r)" [[@LINE-3]]:10 -> [[@LINE-3]]:29 + +#endif + +// RUN: clang-refactor-test perform -action extract -selected=%s:47:10-47:29 %s | FileCheck --check-prefix=CHECK4 %s + +typedef union { + int x; + Rectangle r; +} Union; + +int aggregateTypeNotMutated(Rectangle r, Union u) { + Rectangle r2 = r; + Union u2 = u; + aggregateTypeNotMutated(r, u); + return aggregateTypeNotMutated(r2, u2); +} +// CHECK5-CPP: "static int extracted(const Rectangle &r, const Union &u) {\nreturn aggregateTypeNotMutated(r, u);\n}\n\n" [[@LINE-6]]:1 -> [[@LINE-6]]:1 +// CHECK5-CPP-NEXT: "extracted(r, u)" [[@LINE-4]]:3 -> [[@LINE-4]]:32 +// CHECK5-C: "static int extracted(const Rectangle *r, const Union *u) {\nreturn aggregateTypeNotMutated(*r, *u);\n}\n\n" [[@LINE-8]]:1 -> [[@LINE-8]]:1 +// CHECK5-C-NEXT: "extracted(&r, &u)" [[@LINE-6]]:3 -> [[@LINE-6]]:32 +// CHECK5-CPP: "static int extracted(const Rectangle &r2, const Union &u2) {\nreturn aggregateTypeNotMutated(r2, u2);\n}\n\n" [[@LINE-10]]:1 -> [[@LINE-10]]:1 +// CHECK5-CPP-NEXT: "extracted(r2, u2)" [[@LINE-7]]:10 -> [[@LINE-7]]:41 +// CHECK5-C: "static int extracted(const Rectangle *r2, const Union *u2) {\nreturn aggregateTypeNotMutated(*r2, *u2);\n}\n\n" [[@LINE-12]]:1 -> [[@LINE-12]]:1 +// CHECK5-C-NEXT: "extracted(&r2, &u2)" [[@LINE-9]]:10 -> [[@LINE-9]]:41 + +// RUN: clang-refactor-test perform -action extract -selected=%s:64:3-64:32 -selected=%s:65:10-65:41 %s | FileCheck --check-prefix=CHECK5-CPP %s +// RUN: clang-refactor-test perform -action extract -selected=%s:64:3-64:32 -selected=%s:65:10-65:41 %s -x c | FileCheck --check-prefix=CHECK5-C %s +; +int arrayType(const int x[]) { + int v[2] = {1, 2}; + arrayType(x); + return arrayType(v); +} +// CHECK6: "static int extracted(const int *x) {\nreturn arrayType(x);\n}\n\n" [[@LINE-5]]:1 -> [[@LINE-5]]:1 +// CHECK6-NEXT: "extracted(x)" [[@LINE-4]]:3 -> [[@LINE-4]]:15 +// CHECK6: "static int extracted(int *v) {\nreturn arrayType(v);\n}\n\n" [[@LINE-7]]:1 -> [[@LINE-7]]:1 +// CHECK6-NEXT: "extracted(v)" [[@LINE-5]]:10 -> [[@LINE-5]]:22 + +// RUN: clang-refactor-test perform -action extract -selected=%s:81:3-81:15 -selected=%s:82:10-82:22 %s | FileCheck --check-prefix=CHECK6 %s + +typedef enum { + EnumA, EnumB +} Enum; + +int enumType(Enum e) { + return enumType(e); +} +// CHECK7: "static int extracted(Enum e) {\nreturn enumType(e);\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK7-NEXT: "extracted(e)" [[@LINE-3]]:10 -> [[@LINE-3]]:21 + +// RUN: clang-refactor-test perform -action extract -selected=%s:96:10-96:21 %s | FileCheck --check-prefix=CHECK7 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:96:10-96:21 %s -x c | FileCheck --check-prefix=CHECK7 %s +; +int qualifierReduction(const int i, const int * const p, int *volatile pv, int *__restrict__ rv, + const Enum e) { + return qualifierReduction(i, p, pv, rv, e); +} +// CHECK8: "static int extracted(Enum e, int i, const int *p, int *volatile pv, int *__restrict rv) {\nreturn qualifierReduction(i, p, pv, rv, e);\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK8-NEXT: "extracted(e, i, p, pv, rv)" [[@LINE-3]]:10 -> [[@LINE-3]]:45 + +// RUN: clang-refactor-test perform -action extract -selected=%s:106:10-106:45 %s | FileCheck --check-prefix=CHECK8 %s + +#ifdef __cplusplus + +int autoTypeHandling(int x, Rectangle &ref) { + auto i = x; + auto &r = ref; + return autoTypeHandling(i, r); +} +// CHECK9: "static int extracted(int i, Rectangle &r) {\nreturn autoTypeHandling(i, r);\n}\n\n" [[@LINE-5]]:1 -> [[@LINE-5]]:1 +// CHECK9-NEXT: "extracted(i, r)" [[@LINE-3]]:10 -> [[@LINE-3]]:32 + +#endif + +// RUN: clang-refactor-test perform -action extract -selected=%s:118:10-118:32 %s | FileCheck --check-prefix=CHECK9 %s + +Rectangle globalRect; +int capturedVariableMutation(int x, int y, int z, const int *ip, Rectangle r) { + return x = 0, y += 2, z &= 3, ip = 0, r = globalRect, capturedVariableMutation(x, 1, 2, ip, r); +} +// CHECK10-CPP: "static int extracted(const int *&ip, Rectangle &r, int &x, int &y, int &z) {\nreturn x = 0, y += 2, z &= 3, ip = 0, r = globalRect, capturedVariableMutation(x, 1, 2, ip, r);\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK10-CPP-NEXT: "extracted(ip, r, x, y, z)" [[@LINE-3]]:10 -> [[@LINE-3]]:97 + +// CHECK10-C: "static int extracted(const int **ip, Rectangle *r, int *x, int *y, int *z) {\nreturn *x = 0, *y += 2, *z &= 3, *ip = 0, *r = globalRect, capturedVariableMutation(*x, 1, 2, *ip, *r);\n}\n\n" [[@LINE-6]]:1 -> [[@LINE-6]]:1 +// CHECK10-C-NEXT: "extracted(&ip, &r, &x, &y, &z)" [[@LINE-6]]:10 -> [[@LINE-6]]:97 + +#ifdef __cplusplus + +int capturedMutatedRef(Rectangle &r) { + return r = globalRect, capturedMutatedRef(r); +} +// CHECK10-CPP: "static int extracted(Rectangle &r) {\nreturn r = globalRect, capturedMutatedRef(r);\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK10-CPP-NEXT: "extracted(r)" [[@LINE-3]]:10 -> [[@LINE-3]]:47 + +#endif + +int capturedArrayMutation(int x[]) { + return (x) = 0, capturedArrayMutation(x); +} +// CHECK10-CPP: "static int extracted(int *&x) {\nreturn (x) = 0, capturedArrayMutation(x);\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK10-CPP-NEXT: "extracted(x)" [[@LINE-3]]:10 -> [[@LINE-3]]:43 +// CHECK10-C: "static int extracted(int **x) {\nreturn (*x) = 0, capturedArrayMutation(*x);\n}\n\n" [[@LINE-5]]:1 -> [[@LINE-5]]:1 +// CHECK10-C-NEXT: "extracted(&x)" [[@LINE-5]]:10 -> [[@LINE-5]]:43 +; +void mutationOutsideOfExtractedCode(int x) { + x = 0; + x = 1, mutationOutsideOfExtractedCode(x + 1); + x = 2; + return 0; +} +// CHECK10-CPP: "static void extracted(int x) {\nmutationOutsideOfExtractedCode(x + 1);\n}\n\n" [[@LINE-6]]:1 -> [[@LINE-6]]:1 +// CHECK10-CPP-NEXT: "extracted(x)" [[@LINE-5]]:10 -> [[@LINE-5]]:47 + +// RUN: clang-refactor-test perform -action extract -selected=%s:129:10-129:97 -selected=%s:140:10-140:47 -selected=%s:148:10-148:43 -selected=%s:157:10-157:47 %s | FileCheck --check-prefix=CHECK10-CPP %s +// RUN: clang-refactor-test perform -action extract -selected=%s:129:10-129:97 -selected=%s:148:10-148:43 %s -x c | FileCheck --check-prefix=CHECK10-C %s +; +void extractStatementRangeRewritePointerUse(int x) { + extractStatementRangeRewritePointerUse(x); + x = 1; + extractStatementRangeRewritePointerUse(x); + extractStatementRangeRewritePointerUse(x); +} +// CHECK11: "static void extracted(int *x) {\n*x = 1;\n extractStatementRangeRewritePointerUse(*x);\n}\n\n" [[@LINE-6]]:1 +// CHECK11-NEXT: "extracted(&x)" [[@LINE-5]]:3 -> [[@LINE-4]]:44 + +// RUN: clang-refactor-test perform -action extract -selected=%s:169:3-170:10 %s -x c | FileCheck --check-prefix=CHECK11 %s + +void mutationAfterUse(int x) { + int y = x; + (void)y; + y = 2; +} +// CHECK12: "static void extracted(int &y) {\n(void)y;\n y = 2;\n}\n\n" +// RUN: clang-refactor-test perform -action extract -selected=%s:180:3-181:4 %s | FileCheck --check-prefix=CHECK12 %s + +enum TagEnum { + TagEnumA, TagEnumB +}; + +int tagEnumType(enum TagEnum e) { + return tagEnumType(e); +} +// CHECK13: (enum TagEnum e) +// RUN: clang-refactor-test perform -action extract -selected=%s:191:10-191:24 %s | FileCheck --check-prefix=CHECK13 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:191:10-191:24 %s -x c | FileCheck --check-prefix=CHECK13 %s + +struct TagStruct { + int x; +}; + +int tagStructType(struct TagStruct *s) { + return tagStructType(s); +} +// CHECK14: (struct TagStruct *s) +// RUN: clang-refactor-test perform -action extract -selected=%s:202:10-202:26 %s | FileCheck --check-prefix=CHECK14 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:202:10-202:26 %s -x c | FileCheck --check-prefix=CHECK14 %s + +namespace { +struct Foo { int x; }; +} + +void anonymousNamespaceTypes() { + Foo x; +// anonymous-ns-type1-begin: +1:1 + x.x = 0; +// anonymous-ns-type1-end: +0:1 +// CHECK-ANON: "static void extracted(Foo &x) {\nx.x = 0;\n}\n\n" +// anonymous-ns-type2-begin: +1:7 + x = Foo { 0 }; +// anonymous-ns-type2-end: -1:16 +// CHECK-ANON: "static Foo extracted() {\nreturn Foo { 0 };\n}\n\n" +} +// RUN: clang-refactor-test perform -action extract -selected=anonymous-ns-type1 -selected=anonymous-ns-type2 %s -std=c++11 | FileCheck --check-prefix=CHECK-ANON %s diff --git a/clang/test/Refactor/Extract/captured-variable-types.m b/clang/test/Refactor/Extract/captured-variable-types.m new file mode 100644 index 0000000000000..d764217144914 --- /dev/null +++ b/clang/test/Refactor/Extract/captured-variable-types.m @@ -0,0 +1,56 @@ + +@interface Interface + +@end + +@protocol Protocol + +@end + +int basicObjCTypes(Interface *ip, id p, id pp, Interface *ipp) { + return basicObjCTypes(ip, p, pp, ipp); +} +// CHECK1: "static int extracted(Interface *ip, Interface *ipp, id p, id pp) {\nreturn basicObjCTypes(ip, p, pp, ipp);\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK1-NEXT: "extracted(ip, ipp, p, pp)" [[@LINE-3]]:10 -> [[@LINE-3]]:40 + +// RUN: clang-refactor-test perform -action extract -selected=%s:11:10-11:40 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:11:10-11:40 %s -x objective-c++ | FileCheck --check-prefix=CHECK1 %s + +typedef signed char BOOL; +#define YES __objc_yes +#define NO __objc_no + +int boolType(BOOL b) { + BOOL b2 = YES; + return boolType(b && b2 && YES && NO); +} +// CHECK2: "static int extracted(BOOL b, BOOL b2) {\nreturn boolType(b && b2 && YES && NO);\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK2-NEXT: "extracted(b, b2)" [[@LINE-3]]:10 -> [[@LINE-3]]:40 + +// RUN: clang-refactor-test perform -action extract -selected=%s:25:10-25:40 %s | FileCheck --check-prefix=CHECK2 %s +; +int mutationOfObjCPointer(Interface *ip) { + return ip = 0, mutationOfObjCPointer(ip); +} +// CHECK3-C: "static int extracted(Interface **ip) {\nreturn *ip = 0, mutationOfObjCPointer(*ip);\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK3-C-NEXT: "extracted(&ip)" [[@LINE-3]]:10 -> [[@LINE-3]]:43 +// CHECK3-CPP: "static int extracted(Interface *&ip) {\nreturn ip = 0, mutationOfObjCPointer(ip);\n}\n\n" [[@LINE-5]]:1 -> [[@LINE-5]]:1 +// CHECK3-CPP-NEXT: "extracted(ip)" [[@LINE-5]]:10 -> [[@LINE-5]]:43 + +// RUN: clang-refactor-test perform -action extract -selected=%s:33:10-33:43 %s | FileCheck --check-prefix=CHECK3-C %s +// RUN: clang-refactor-test perform -action extract -selected=%s:33:10-33:43 %s -x objective-c++ | FileCheck --check-prefix=CHECK3-CPP %s + +void silenceStrongInARC() { + Interface *pointer; +// silence-strong-in-arc-begin: +1:1 + mutationOfObjCPointer(pointer); +// silence-strong-in-arc-end: +0:1 +// silence-strong2-in-arc-begin: +1:1 + pointer = 0; +// silence-strong2-in-arc-end: +0:1 + (void)pointer; +} +// CHECK-ARC: "static int extracted(Interface *pointer) {\nreturn mutationOfObjCPointer(pointer);\n}\n\n" +// CHECK-ARC: "static void extracted(Interface **pointer) {\n*pointer = 0;\n}\n\n" + +// RUN: clang-refactor-test perform -action extract -selected=silence-strong-in-arc -selected=silence-strong2-in-arc %s -fobjc-arc | FileCheck --check-prefix=CHECK-ARC %s diff --git a/clang/test/Refactor/Extract/disallowed-expressions.cpp b/clang/test/Refactor/Extract/disallowed-expressions.cpp new file mode 100644 index 0000000000000..522cb598b9e08 --- /dev/null +++ b/clang/test/Refactor/Extract/disallowed-expressions.cpp @@ -0,0 +1,50 @@ +void disallowExtractOfSimpleExpressions(int param) { + int var = param; + short var2 = (short)(var); + int x = 0; + const char *s = "Test"; + char c = 'C'; + double y = ((2.1)); + const char *f = __func__; +} + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:2:13-2:18 -selected=%s:2:14-2:15 -selected=%s:2:16-2:18 %s 2>&1 | FileCheck %s +// RUN: not clang-refactor-test initiate -action extract -selected=%s:3:16-3:28 -selected=%s:4:11-4:12 -selected=%s:5:19-5:25 %s 2>&1 | FileCheck %s +// RUN: not clang-refactor-test initiate -action extract -selected=%s:6:12-6:15 -selected=%s:7:14-7:21 -selected=%s:8:19-8:27 %s 2>&1 | FileCheck %s + +// CHECK: Failed to initiate the refactoring action (the selected expression is too simple)! + +void allowOperationsOnSimpleExpression(int x, int y) { + int z = x + y; + int zz = 0 + 1; + allowOperationsOnSimpleExpression(1, y); +} + +// RUN: clang-refactor-test initiate -action extract -selected=%s:18:11-18:16 %s | FileCheck --check-prefix=CHECK1 %s +// CHECK1: Initiated the 'extract' action at 18:11 -> 18:16 + +// RUN: clang-refactor-test initiate -action extract -selected=%s:19:12-19:17 %s | FileCheck --check-prefix=CHECK2 %s +// CHECK2: Initiated the 'extract' action at 19:12 -> 19:17 + +// RUN: clang-refactor-test initiate -action extract -selected=%s:20:37-20:41 %s | FileCheck --check-prefix=CHECK3 %s +// CHECK3: Initiated the 'extract' action at 20:3 -> 20:42 + +void defaultParameter(int x = 0 + 1) { + +} + +struct Struct { + int y = 22 + 21; + + Struct(int x) : y(x + 1) { } +}; + +int initializerExpression = 1 + 2; + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:32:31-32:36 -selected=%s:37:11-37:18 -selected=%s:39:21-39:26 -selected=%s:42:29-42:34 %s 2>&1 | FileCheck --check-prefix=NOT-IN-FUNC %s +// NOT-IN-FUNC: Failed to initiate the refactoring action (the selected expression is not in a function)! + +void disallowWholeFunctionBody() { + int x = 0; +} +// RUN: not clang-refactor-test initiate -action extract -selected=%s:47:34-49:2 %s 2>&1 | FileCheck --check-prefix=NOT-IN-FUNC %s diff --git a/clang/test/Refactor/Extract/extract-address-of-captured-variable.cpp b/clang/test/Refactor/Extract/extract-address-of-captured-variable.cpp new file mode 100644 index 0000000000000..c2d4a79e232d7 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-address-of-captured-variable.cpp @@ -0,0 +1,194 @@ + +void takesVoidPtr(void *x) { } +void takesPtr(int *x) { } +void takesPtrPtr(int **x) { } + +void addressOfVariableImpliesPossibleMutation(int x, int *ip) { + takesPtr(&x); +// CHECK1-C: (int *x) {\ntakesPtr(x);\n} +// CHECK1-CPP: (int &x) {\ntakesPtr(&x);\n} + takesVoidPtr(&x); +// CHECK1-C: (int *x) {\ntakesVoidPtr(x);\n} +// CHECK1-CPP: (int &x) {\ntakesVoidPtr(&x);\n} + &x; +// CHECK1-C: (int *x) {\nreturn x;\n} +// CHECK1-CPP: (int &x) {\nreturn &x;\n} + *(&x) = 0; +// CHECK1-C: extracted(int *x) {\n*(x) = 0;\n} +// CHECK1-CPP: extracted(int &x) {\n*(&x) = 0;\n} + takesPtrPtr(&ip); +// CHECK1-C: (int **ip) {\ntakesPtrPtr(ip);\n} +// CHECK1-CPP: (int *&ip) {\ntakesPtrPtr(&ip);\n} + takesPtr(ip); +// CHECK1-C: (int *ip) {\ntakesPtr(ip);\n} +// CHECK1-CPP: (int *ip) {\ntakesPtr(ip);\n} + takesVoidPtr(ip); +// CHECK1-C: (int *ip) {\ntakesVoidPtr(ip);\n} +// CHECK1-CPP: (int *ip) {\ntakesVoidPtr(ip);\n} + takesPtr(&((x))); +// CHECK1-C: (int *x) {\ntakesPtr(((x)));\n} +// CHECK1-CPP: (int &x) {\ntakesPtr(&((x)));\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:7:3-7:15 -selected=%s:10:3-10:19 -selected=%s:13:3-13:5 -selected=%s:16:3-16:12 -selected=%s:19:3-19:19 -selected=%s:22:3-22:15 -selected=%s:25:3-25:19 -selected=%s:28:3-28:19 %s -x c | FileCheck --check-prefix=CHECK1-C %s +// RUN: clang-refactor-test perform -action extract -selected=%s:7:3-7:15 -selected=%s:10:3-10:19 -selected=%s:13:3-13:5 -selected=%s:16:3-16:12 -selected=%s:19:3-19:19 -selected=%s:22:3-22:15 -selected=%s:25:3-25:19 -selected=%s:28:3-28:19 %s | FileCheck --check-prefix=CHECK1-CPP %s + +typedef struct { + int width, height; +} Rectangle; + +void takesStructPtr(Rectangle *sp) { } + +#ifdef __cplusplus + +void addressOfRef(int &x, int *&ip, Rectangle &r) { + takesPtr(&x); +// CHECK2: (int &x) {\ntakesPtr(&x);\n} + takesPtrPtr(&ip); +// CHECK2: (int *&ip) {\ntakesPtrPtr(&ip);\n} + takesStructPtr(&r); +// CHECK2: (Rectangle &r) {\ntakesStructPtr(&r);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:45:3-45:15 -selected=%s:47:3-47:19 -selected=%s:49:3-49:21 %s | FileCheck --check-prefix=CHECK2 %s + +#endif + +void addressOfArray(int x[]) { + takesPtrPtr(&x); +// CHECK3-C: (int **x) {\ntakesPtrPtr(x);\n} +// CHECK3-CPP: (int *&x) {\ntakesPtrPtr(&x);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:58:3-58:18 %s -x c | FileCheck --check-prefix=CHECK3-C %s +// RUN: clang-refactor-test perform -action extract -selected=%s:58:3-58:18 %s | FileCheck --check-prefix=CHECK3-CPP %s + +typedef struct { + Rectangle r; +} RectangleInStruct; + +void addressOfMember(Rectangle r, RectangleInStruct rs) { + takesPtr(&r.width); +// CHECK4-C: (Rectangle *r) {\ntakesPtr(&r->width);\n} +// CHECK4-CPP: (Rectangle &r) {\ntakesPtr(&r.width);\n} + takesPtr(&(rs).r.width); +// CHECK4-C: (RectangleInStruct *rs) {\ntakesPtr(&(rs)->r.width);\n} +// CHECK4-CPP: (RectangleInStruct &rs) {\ntakesPtr(&(rs).r.width);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:71:3-71:21 -selected=%s:74:3-74:26 %s -x c | FileCheck --check-prefix=CHECK4-C %s +// RUN: clang-refactor-test perform -action extract -selected=%s:71:3-71:21 -selected=%s:74:3-74:26 %s | FileCheck --check-prefix=CHECK4-CPP %s + +void takesConstPtr(const int *x) { } + +#ifdef __cplusplus + +void addressOfMember(const Rectangle &r) { + takesConstPtr(&r.width); +// CHECK5: (const Rectangle &r) {\ntakesConstPtr(&r.width);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:87:3-87:26 %s | FileCheck --check-prefix=CHECK5 %s + +class PrivateInstanceVariables { + int x; + Rectangle r; + + void method() { + takesPtr(&x); +// CHECK6: extracted(int &x) {\ntakesPtr(&x);\n} + takesStructPtr(&(r)); +// CHECK6: extracted(Rectangle &r) {\ntakesStructPtr(&(r));\n} + takesPtr(&((r).width)); +// CHECK6: extracted(Rectangle &r) {\ntakesPtr(&((r).width));\n} + } +}; + +// RUN: clang-refactor-test perform -action extract -selected=%s:98:5-98:17 -selected=%s:100:5-100:25 -selected=%s:102:5-102:27 %s | FileCheck --check-prefix=CHECK6 %s + +#endif + +void takesCVoidPtr(const void *x) { } +void takesCPtr(const int *x) { } +void takesCPtrPtr(int * const *x) { } +void takesBothPtrs(const int *x, int *y) {} +void addressForConstUseShouldPassAsConst(int x, int *ip) { + takesCPtr(&x); +// CHECK7-C: (const int *x) {\ntakesCPtr(x);\n} +// CHECK7-CPP: (const int &x) {\ntakesCPtr(&x);\n} + takesCVoidPtr((&(x))); +// CHECK7-C: (const int *x) {\ntakesCVoidPtr(((x)));\n} +// CHECK7-CPP: (const int &x) {\ntakesCVoidPtr((&(x)));\n} + takesCPtrPtr(&ip); +// CHECK7-C: (int *const *ip) {\ntakesCPtrPtr(ip);\n} +// CHECK7-CPP: (int *const &ip) {\ntakesCPtrPtr(&ip);\n} + takesCPtr(ip); +// CHECK7-C: (int *ip) {\ntakesCPtr(ip);\n} +// CHECK7-CPP: (int *ip) {\ntakesCPtr(ip);\n} + takesBothPtrs(&x, &x); +// CHECK7-C: (int *x) {\ntakesBothPtrs(x, x);\n} +// CHECK7-CPP: (int &x) {\ntakesBothPtrs(&x, &x);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:116:3-116:16 -selected=%s:119:3-119:22 -selected=%s:122:3-122:20 -selected=%s:125:3-125:16 -selected=%s:128:3-128:24 %s -x c | FileCheck --check-prefix=CHECK7-C %s +// RUN: clang-refactor-test perform -action extract -selected=%s:116:3-116:16 -selected=%s:119:3-119:22 -selected=%s:122:3-122:20 -selected=%s:125:3-125:16 -selected=%s:128:3-128:24 %s | FileCheck --check-prefix=CHECK7-CPP %s + +void addressOfConstUseAndMutation(int x) { + x = 0; + takesCPtr(&x); + x = 1; +} +// CHECK8: extracted(int &x) {\nx = 0;\n takesCPtr(&x);\n} +// CHECK8: extracted(int &x) {\ntakesCPtr(&x);\n x = 1;\n} + +// RUN: clang-refactor-test perform -action extract -selected=%s:137:3-138:16 -selected=%s:138:3-139:8 %s | FileCheck --check-prefix=CHECK8 %s + +void takesCStructPtr(const Rectangle *r) { } + +void constAddressOfMember(Rectangle r, RectangleInStruct rs) { + takesCStructPtr(&r); +// CHECK9-C: extracted(const Rectangle *r) {\ntakesCStructPtr(r);\n} +// CHECK9-CPP: extracted(const Rectangle &r) {\ntakesCStructPtr(&r);\n} + takesCPtr(&r.width); +// CHECK9-C: (const Rectangle *r) {\ntakesCPtr(&r->width);\n} +// CHECK9-CPP: (const Rectangle &r) {\ntakesCPtr(&r.width);\n} + takesCPtr((&(rs).r.height)); +// CHECK9-C: (const RectangleInStruct *rs) {\ntakesCPtr((&(rs)->r.height));\n} +// CHECK9-CPP: (const RectangleInStruct &rs) {\ntakesCPtr((&(rs).r.height));\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:149:3-149:22 -selected=%s:152:3-152:22 -selected=%s:155:3-155:30 %s -x c | FileCheck --check-prefix=CHECK9-C %s +// RUN: clang-refactor-test perform -action extract -selected=%s:149:3-149:22 -selected=%s:152:3-152:22 -selected=%s:155:3-155:30 %s | FileCheck --check-prefix=CHECK9-CPP %s + +#ifdef __cplusplus + +// RUN: clang-refactor-test perform -action extract -selected=%s:87:3-87:26 %s | FileCheck --check-prefix=CHECK5 %s + +class PrivateInstanceVariablesConstAddress { + int x; + Rectangle r; + + void method() { + takesCPtr(&x); +// CHECK10: extracted(const int &x) {\ntakesCPtr(&x);\n} + takesCStructPtr(&r); +// CHECK10: extracted(const Rectangle &r) {\ntakesCStructPtr(&r);\n} + } +}; + +// RUN: clang-refactor-test perform -action extract -selected=%s:172:5-172:18 -selected=%s:174:5-174:24 %s | FileCheck --check-prefix=CHECK10 %s + +#endif + +void rewriteToPtrWithDerefParensForArrow(Rectangle *r) { + int y = r->width; + r = 0; +// CHECK11: (Rectangle **r) {\nint y = (*r)->width;\n *r = 0;\n}\n\n" +} +// RUN: clang-refactor-test perform -action extract -selected=%s:184:3-185:8 %s -x c | FileCheck --check-prefix=CHECK11 %s + +void constAddressWithConditionalOperator(int x, int y) { + takesCPtr(&(x == 0 ? x : y)); +// CHECK12: (const int &x, const int &y) {\ntakesCPtr(&(x == 0 ? x : y));\n} +} +// RUN: clang-refactor-test perform -action extract -selected=%s:191:3-191:31 %s | FileCheck --check-prefix=CHECK12 %s diff --git a/clang/test/Refactor/Extract/extract-address-of-captured-variable.mm b/clang/test/Refactor/Extract/extract-address-of-captured-variable.mm new file mode 100644 index 0000000000000..9d6661cca8551 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-address-of-captured-variable.mm @@ -0,0 +1,44 @@ + +void takesVoidPtr(void *x) { } +void takesPtr(int *x) { } +void takesPtrPtr(int **x) { } + +#ifdef USECONST +#define CONST const +#else +#define CONST +#endif + +typedef struct { + int width, height; +} Rectangle; + +@interface I + +- (int)takesPtr:(CONST int *)x; ++ (int)takesPtr:(CONST int *)x; +- (int)takesVoidPtr:(CONST void *)x; +- (int)takesStructPtr:(CONST Rectangle *)r; + +@end + +void methodTakesPtr(I *i, int x, Rectangle r) { + [i takesPtr: &x]; +// CHECK1: extracted(I *i, int &x) {\nreturn [i takesPtr: &x];\n} +// CHECK2: extracted(I *i, const int &x) {\nreturn [i takesPtr: &x];\n} + [I takesPtr: (&x)]; +// CHECK1: extracted(int &x) {\nreturn [I takesPtr: (&x)];\n} +// CHECK2: extracted(const int &x) {\nreturn [I takesPtr: (&x)];\n} + [i takesVoidPtr: (&(x))]; +// CHECK1: extracted(I *i, int &x) {\nreturn [i takesVoidPtr: (&(x))];\n} +// CHECK2: extracted(I *i, const int &x) {\nreturn [i takesVoidPtr: (&(x))];\n} + [i takesStructPtr: &r]; +// CHECK1: extracted(I *i, Rectangle &r) {\nreturn [i takesStructPtr: &r];\n} +// CHECK2: extracted(I *i, const Rectangle &r) {\nreturn [i takesStructPtr: &r];\n} + [I takesPtr: &(r).width]; +// CHECK1: extracted(Rectangle &r) {\nreturn [I takesPtr: &(r).width];\n} +// CHECK2: extracted(const Rectangle &r) {\nreturn [I takesPtr: &(r).width];\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:26:3-26:19 -selected=%s:29:3-29:21 -selected=%s:32:3-32:27 -selected=%s:35:3-35:25 -selected=%s:38:3-38:27 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:26:3-26:19 -selected=%s:29:3-29:21 -selected=%s:32:3-32:27 -selected=%s:35:3-35:25 -selected=%s:38:3-38:27 %s -DUSECONST | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Extract/extract-before-comments.cpp b/clang/test/Refactor/Extract/extract-before-comments.cpp new file mode 100644 index 0000000000000..f20e0ab059ac4 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-before-comments.cpp @@ -0,0 +1,64 @@ + +// comment 1 +void extractBeforeComment1(int x) { + int y = x * x; +} +// CHECK1: "static int extracted(int x) {\nreturn x * x;\n}\n\n" [[@LINE-4]]:1 + +/* comment 2 */ + +void extractBeforeComment2(int x) { + int y = x * x; +} +// CHECK1: "static int extracted(int x) {\nreturn x * x;\n}\n\n" [[@LINE-5]]:1 + +/// comment 1 +/// +/// line 2 +void extractBeforeDocComment1(int x) { + int y = x * x; +} +// CHECK1: "static int extracted(int x) {\nreturn x * x;\n}\n\n" [[@LINE-6]]:1 + +/** + * @brief extractBeforeDocComment2 + * @param x + */ +void extractBeforeDocComment2(int x) { + int y = x * x; +} +// CHECK1: "static int extracted(int x) {\nreturn x * x;\n}\n\n" [[@LINE-7]]:1 + +// RUN: clang-refactor-test perform -action extract -selected=%s:4:11-4:16 -selected=%s:11:11-11:16 -selected=%s:19:11-19:16 -selected=%s:28:11-28:16 %s | FileCheck --check-prefix=CHECK1 %s + +/** + * @brief The AClass class + */ +class AClass { + + /// doc comment + int method(int x) { + return x * x; + } +// CHECK2: "static int extracted(int x) {\nreturn x * x;\n}\n\n" [[@LINE-9]]:1 +}; + +// RUN: clang-refactor-test perform -action extract -selected=%s:41:12-41:17 %s | FileCheck --check-prefix=CHECK2 %s + +namespace { + +} // end anonymous namespace + +void afterBraceAfterComment() { // CHECK3: "static void extracted() {\nint x = 0;\n}\n\n" [[@LINE]]:1 +// after-brace-begin: +1:1 + int x = 0; +// after-brace-end: +0:1 +} // another trailing +// This is valid CHECK3: "static void extracted() {\nint x = 0;\n}\n\n" [[@LINE]]:1 +void inbetweenerTwoComments() { +// inbetween-begin: +1:1 + int x = 0; +// inbetween-end: +0:1 +} + +// RUN: clang-refactor-test perform -action extract -selected=after-brace -selected=inbetween %s | FileCheck --check-prefix=CHECK3 %s diff --git a/clang/test/Refactor/Extract/extract-capture-instance-variable.cpp b/clang/test/Refactor/Extract/extract-capture-instance-variable.cpp new file mode 100644 index 0000000000000..b98bc0a6a8afa --- /dev/null +++ b/clang/test/Refactor/Extract/extract-capture-instance-variable.cpp @@ -0,0 +1,73 @@ + +typedef struct { + int width, height; +} Rectangle; + +class PrivateInstanceVariables { + int x; + Rectangle r; + + int method() { + int y = x; + return r.width + r.height * x + y; + } +// CHECK1: (int x) {\nint y = x;\nreturn y;\n} +// CHECK1-NEXT: extracted(x) +// CHECK1: extracted(const Rectangle &r, int x) {\nint y = x;\n return r.width + r.height * x + y;\n} +// CHECK1-NEXT: extracted(r, x) +// CHECK1: extracted(const Rectangle &r, int x, int y) {\nreturn r.width + r.height * x + y;\n} +// CHECK1-NEXT: extracted(r, x, y) +}; + +// RUN: clang-refactor-test perform -action extract -selected=%s:11:5-11:14 -selected=%s:11:5-12:38 -selected=%s:12:12-12:38 %s | FileCheck --check-prefix=CHECK1 %s + +class PrivateInstanceVariablesExplicitThis { + int x; + + + int method(int y) { + y = this->x; + { + int x = (this)->x; + } + y = x; + x = 0; + this->x = 0; + y = (y == 0 ? this : (PrivateInstanceVariablesExplicitThis *)0) -> x; + } +// CHECK2: (const PrivateInstanceVariablesExplicitThis &object, int &y) {\ny = object.x;\n {\n int x = (object).x;\n }\n y = object.x;\n} +// CHECK2-NEXT: extracted(*this, y) +// CHECK2: extracted(const PrivateInstanceVariablesExplicitThis &object) {\nint x = (object).x;\n} +// CHECK2-NEXT: extracted(*this) +// CHECK2: (PrivateInstanceVariablesExplicitThis &object, int &y) {\ny = object.x;\n {\n int x = (object).x;\n }\n y = object.x;\n object.x = 0;\n} +// CHECK2: (PrivateInstanceVariablesExplicitThis &object) {\nobject.x = 0;\n} +// CHECK2: (PrivateInstanceVariablesExplicitThis &object, int &y) {\ny = (y == 0 ? &object : (PrivateInstanceVariablesExplicitThis *)0) -> x;\n} + +// RUN: clang-refactor-test perform -action extract -selected=%s:29:5-33:10 -selected=%s:31:7-31:24 -selected=%s:29:5-34:10 -selected=%s:35:5-35:16 -selected=%s:36:5-36:73 %s | FileCheck --check-prefix=CHECK2 %s +}; + +class PublicInstanceVariables { int private_; +public: + int x; + Rectangle r; + + void method() { + int y; y = x; + x = 0; + int z = r.width + r.height * x + y; + this->x = (this)->r.height + (y == 0 ? this : (PublicInstanceVariables *)0) -> x; + x = private_; + } +// CHECK3: (const PublicInstanceVariables &object, int &y) {\ny = object.x;\n} +// CHECK3: (PublicInstanceVariables &object, int &y) {\ny = object.x;\n object.x = 0;\n} +// CHECK3: (PublicInstanceVariables &object) {\nobject.x = 0;\n} +// CHECK3: (const PublicInstanceVariables &object, int y) {\nint z = object.r.width + object.r.height * object.x + y;\n} +// CHECK3: (PublicInstanceVariables &object, int y) {\nobject.x = (object).r.height + (y == 0 ? &object : (PublicInstanceVariables *)0) -> x;\n} +// CHECK3: (PublicInstanceVariables &object, int private_) {\nobject.x = object.private_;\n} + void constMethod() const { + const_cast(this)->x = 2; + } +// CHECK3: (const PublicInstanceVariables &object) {\nconst_cast(&object)->x = 2;\n} +}; + +// RUN: clang-refactor-test perform -action extract -selected=%s:55:12-55:17 -selected=%s:55:12-56:10 -selected=%s:56:5-56:10 -selected=%s:57:5-57:39 -selected=%s:58:5-58:85 -selected=%s:59:5-59:17 -selected=%s:68:5-68:55 %s | FileCheck --check-prefix=CHECK3 %s diff --git a/clang/test/Refactor/Extract/extract-capture-self.m b/clang/test/Refactor/Extract/extract-capture-self.m new file mode 100644 index 0000000000000..4f8375a194d07 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-capture-self.m @@ -0,0 +1,56 @@ + +@interface AClass { + int ivar1; +} + +@property int prop; + +- (int)instanceMethod; ++ (int)classMethod; + +@end + +@implementation AClass { + int ivar2; +} + +- (int)instanceMethod { + ivar2 = 0; + ivar1 = 0; + self->ivar2 = 0; + self.prop = 0; + int x = self->ivar1; + int y = self.prop; + [self instanceMethod]; + [AClass classMethod]; + return 0; +} +// CHECK1: (AClass *object) {\nobject->ivar2 = 0;\n} +// CHECK1: (AClass *object) {\nobject->ivar2 = 0;\n} +// CHECK1: (AClass *object) {\nobject.prop = 0;\n}\n\n" +// CHECK1: (AClass *object) {\nint y = object.prop;\n} +// CHECK1: (AClass *object) {\nreturn [object instanceMethod];\n} +// CHECK1: (AClass *object) {\nobject->ivar2 = 0;\n object->ivar1 = 0;\n object->ivar2 = 0;\n object.prop = 0;\n int x = object->ivar1;\n int y = object.prop;\n [object instanceMethod];\n [AClass classMethod];\n}\n\n" +// CHECK1: () {\nreturn [AClass classMethod];\n}\n\n" + +// RUN: clang-refactor-test perform -action extract -selected=%s:18:3-18:12 -selected=%s:20:3-20:18 -selected=%s:21:3-21:16 -selected=%s:23:3-23:20 -selected=%s:24:3-24:24 -selected=%s:18:3-25:23 -selected=%s:25:3-25:23 %s | FileCheck --check-prefix=CHECK1 %s + ++ (int)classMethod { + int x = self.classMethod; + [self classMethod]; +} + +// CHECK2: () {\nint x = AClass.classMethod;\n} +// CHECK2: () {\nreturn [AClass classMethod];\n} + +// RUN: clang-refactor-test perform -action extract -selected=%s:39:3-39:27 -selected=%s:40:3-40:21 %s | FileCheck --check-prefix=CHECK2 %s + +- (void)rhsSelfCaptureAndRewrite:(AClass *)i { // CHECK3: "static void extracted(AClass *object, AClass *i) {\ni.prop= object.prop;\n}\n\n" +// rhs-prop-begin: +1:3 + i.prop= self.prop; +// rhs-prop-end: -1:21 +} + +// RUN: clang-refactor-test perform -action extract -selected=rhs-prop %s | FileCheck --check-prefix=CHECK3 %s + +@end diff --git a/clang/test/Refactor/Extract/extract-capture-static-var.cpp b/clang/test/Refactor/Extract/extract-capture-static-var.cpp new file mode 100644 index 0000000000000..097e176ba9bea --- /dev/null +++ b/clang/test/Refactor/Extract/extract-capture-static-var.cpp @@ -0,0 +1,12 @@ + + +void captureStaticVars() { + static int x; + int y = x; + x += 1; +// CHECK1: extracted(int x) {\nint y = x;\n}\n\n" +// CHECK1: extracted(int &x) {\nx += 1;\n} +// CHECK1: extracted() {\nstatic int x;\n int y = x;\n x += 1;\n}\n\n" +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:5:3-5:12 -selected=%s:6:3-6:9 -selected=%s:4:3-6:9 %s | FileCheck --check-prefix=CHECK1 %s diff --git a/clang/test/Refactor/Extract/extract-capture-super.m b/clang/test/Refactor/Extract/extract-capture-super.m new file mode 100644 index 0000000000000..0b18143ab3f80 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-capture-super.m @@ -0,0 +1,42 @@ +// RUN: clang-refactor-test perform -action extract -selected=super-msg -selected=super-prop -selected=super-self -selected=super-class-msg -selected=super-class-prop %s | FileCheck %s + +@interface BaseClass + +@property int prop; + +- (int)instanceMethod; ++ (void)classMethod; + +@property(class) int classProp; + +@end + +@interface SubClass: BaseClass + +@end + +@implementation SubClass + +- (void)method { + // super-msg-begin: +1:1 + [super instanceMethod]; // CHECK: extracted(BaseClass *superObject) {\n[superObject instanceMethod];\n} + // super-msg-end: +0:1 // CHECK: extracted(super.self) + // super-prop-begin: +1:11 + int x = super.prop; // CHECK: extracted(BaseClass *superObject) {\nreturn superObject.prop;\n} + // super-prop-end: -1:21 // CHECK: extracted(super.self) + // super-self-begin: +1:1 + int y = self.prop; // CHECK: extracted(SubClass *object, BaseClass *superObject) {\nint y = object.prop;\n int z = superObject.prop;\n} + int z = super.prop; // CHECK: extracted(self, super.self); + // super-self-end: +0:1 +} + ++ (void)classMethod { + // super-class-msg-begin: +1:1 + [super classMethod]; // CHECK: extracted() {\n[BaseClass classMethod];\n} + // super-class-msg-end: +0:1 // CHECK: extracted() + // super-class-prop-begin: +1:9 + (void)super.classProp; // CHECK: extracted() {\nreturn BaseClass.classProp;\n} + // super-class-prop-end: -1:24 // CHECK: extracted() +} + +@end diff --git a/clang/test/Refactor/Extract/extract-capture-this.cpp b/clang/test/Refactor/Extract/extract-capture-this.cpp new file mode 100644 index 0000000000000..cb15a5e3fdea0 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-capture-this.cpp @@ -0,0 +1,66 @@ + +class AClassWithMethods { + void method() { + } + void constMethod() const { + } + int operator << (int x) const { return x; } + int operator >> (int x) { return x; } + + void toBeExtracted() { + method(); + constMethod(); + *this << 2; + *(this) >> 2; + this->constMethod(); + (*(this)).constMethod(); + } +// CHECK1: (AClassWithMethods &object) {\nobject.method();\n} +// CHECK1: (const AClassWithMethods &object) {\nobject.constMethod();\n} +// CHECK1: (AClassWithMethods &object) {\nobject.method();\n object.constMethod();\n} +// CHECK1: (const AClassWithMethods &object) {\nreturn object << 2;\n} +// CHECK1: (AClassWithMethods &object) {\nreturn (object) >> 2;\n} +// CHECK1: (const AClassWithMethods &object) {\nobject.constMethod();\n} +// CHECK1: (const AClassWithMethods &object) {\n((object)).constMethod();\n} + + void toBeExtracted2(); +}; +// RUN: clang-refactor-test perform -action extract -selected=%s:11:5-11:13 -selected=%s:12:5-12:17 -selected=%s:11:5-12:17 -selected=%s:13:5-13:14 -selected=%s:14:5-14:16 -selected=%s:15:5-15:24 -selected=%s:16:5-16:28 %s | FileCheck --check-prefix=CHECK1 %s + +void takesRef(AClassWithMethods &object) {} +void takesConstRef(const AClassWithMethods &object) {} +void takesPtr(AClassWithMethods *object) {} +void takesConstPtr(const AClassWithMethods *object) {} + +void AClassWithMethods::toBeExtracted2() { + takesRef(*this); + takesConstRef((*(this))); + takesPtr(this); + takesConstPtr((this)); + takesConstPtr(false ? this : (AClassWithMethods*)0); +} +// CHECK2: (AClassWithMethods &object) {\ntakesRef(object);\n} +// CHECK2: (const AClassWithMethods &object) {\ntakesConstRef(((object)));\n} +// CHECK2: (AClassWithMethods &object) {\ntakesPtr(&object);\n} +// CHECK2: (const AClassWithMethods &object) {\ntakesConstPtr((&object));\n} +// CHECK2: (AClassWithMethods &object) {\ntakesConstPtr(false ? &object : (AClassWithMethods*)0);\n} + +// RUN: clang-refactor-test perform -action extract -selected=%s:36:3-36:18 -selected=%s:37:3-37:27 -selected=%s:38:3-38:17 -selected=%s:39:3-39:24 -selected=%s:40:3-40:54 %s | FileCheck --check-prefix=CHECK2 %s + +#ifdef USECONST +#define CONST const +#else +#define CONST +#endif + +class FallbackToMethodConstness { + int getter() const { return 0; } + int method(int x, FallbackToMethodConstness *other) CONST { + return (x == 0 ? this : other)->getter(); + } +// CHECK3: (FallbackToMethodConstness &object, FallbackToMethodConstness *other, int x) {\nreturn (x == 0 ? &object : other)->getter();\n} +// CHECK3-CONST: (const FallbackToMethodConstness &object, FallbackToMethodConstness *other, int x) {\nreturn (x == 0 ? &object : other)->getter();\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:59:5-59:45 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:59:5-59:45 %s -DUSECONST | FileCheck --check-prefix=CHECK3-CONST %s diff --git a/clang/test/Refactor/Extract/extract-capture-used-after-extraction.cpp b/clang/test/Refactor/Extract/extract-capture-used-after-extraction.cpp new file mode 100644 index 0000000000000..03fff5c67d94a --- /dev/null +++ b/clang/test/Refactor/Extract/extract-capture-used-after-extraction.cpp @@ -0,0 +1,214 @@ + +void takesInt(int x); + +typedef struct { int width; int height; } Rectangle; + +#ifdef USEINIT1 +#define INIT1 r.width = 0; { int r, y; } +#else +#define INIT1 { int r, y; } +#endif + +void extractCaptureUsedAfterSimple(int x) { + Rectangle r; + INIT1; + int y = x * x; +#ifdef USEAFTER1 + takesInt(y); +#endif +#ifdef USEAFTER2 + takesInt(r.height); +#endif +#ifdef USEAFTER3 + r.width = 0; +#endif +#ifdef USEAFTER4 + y += 1; +#endif +} +// CHECK1: "static void extracted(int x) {\nRectangle r;\n INIT1;\n int y = x * x;\n}\n\n" +// CHECK1-NEXT: "extracted(x);" +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s | FileCheck --check-prefix=CHECK1 %s + +// CHECK2: "static void extracted(Rectangle &r, int x, int &y) {\n\n INIT1;\n y = x * x;\n}\n\n" +// CHECK2-NEXT: "Rectangle r;\nint y;\nextracted(r, x, y);" +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEINIT1 -DUSEAFTER1 -DUSEAFTER2 | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEINIT1 -DUSEAFTER3 -DUSEAFTER4 | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEINIT1 -DUSEAFTER1 -DUSEAFTER2 -DUSEAFTER3 -DUSEAFTER4 | FileCheck --check-prefix=CHECK2 %s + +// CHECK3: "static void extracted(int x, int &y) {\n\n INIT1;\n y = x * x;\n}\n\n" +// CHECK3-NEXT: "Rectangle r;\nint y;\nextracted(x, y);" +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEAFTER1 -DUSEAFTER2 | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEAFTER3 -DUSEAFTER4 | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEAFTER1 -DUSEAFTER2 -DUSEAFTER3 -DUSEAFTER4 | FileCheck --check-prefix=CHECK3 %s + +// CHECK4: "static void extracted(int x, int &y) {\nRectangle r;\n INIT1;\n y = x * x;\n}\n\n" +// CHECK4-NEXT: "int y;\nextracted(x, y);" +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEINIT1 -DUSEAFTER1 | FileCheck --check-prefix=CHECK4 %s + +// CHECK5: "static void extracted(Rectangle &r, int x) {\n\n INIT1;\n int y = x * x;\n}\n\n" +// CHECK5-NEXT: "Rectangle r;\nextracted(r, x);" +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEINIT1 -DUSEAFTER3 | FileCheck --check-prefix=CHECK5 %s + +// CHECK6: "static void extracted(int x) {\n\n INIT1;\n int y = x * x;\n}\n\n" +// CHECK6-NEXT: "Rectangle r;\nextracted(x);" +// RUN: clang-refactor-test perform -action extract -selected=%s:13:3-15:16 %s -DUSEAFTER3 | FileCheck --check-prefix=CHECK6 %s + +void extractCaptureAfterUseMultipleDecls() { +#ifdef MULTIPLE_DECL1 + int x = 1, y = 2, z = 3; { int x, y, z; }; +#endif +#ifdef MULTIPLE_DECL2 + int x, y = 2, *z; { int y; } +#endif +#ifdef MULTIPLE_DECL3 + int x = 1, y, * z = 0, a, b = {0}; { int a; } +#endif +#ifdef USEX + x; +#endif +#ifdef USEY + y; +#endif +#ifdef USEZ + z; +#endif +#ifdef USEA + a; +#endif +#ifdef USEB + b; +#endif +} + +// CHECK7: "static void extracted() {\nint x = 1, y = 2, z = 3;\n}\n\n" +// CHECK7-NEXT; "extracted();" +// RUN: clang-refactor-test perform -action extract -selected=%s:59:3-59:26 %s -DMULTIPLE_DECL1 | FileCheck --check-prefix=CHECK7 %s +// CHECK8: "static void extracted(int &x, int &y, int &z) {\nx = 1; y = 2; z = 3;\n}\n\n" +// CHECK8-NEXT: "int x;\nint y;\nint z;\nextracted(x, y, z);" +// RUN: clang-refactor-test perform -action extract -selected=%s:59:3-59:26 %s -DMULTIPLE_DECL1 -DUSEX -DUSEY -DUSEZ | FileCheck --check-prefix=CHECK8 %s +// CHECK9: "static void extracted(int &x) {\nx = 1; int y = 2; int z = 3; { int x, y, z; }\n}\n\n" +// CHECK9-NEXT: "int x;\nextracted(x);" +// RUN: clang-refactor-test perform -action extract -selected=%s:59:3-59:44 %s -DMULTIPLE_DECL1 -DUSEX | FileCheck --check-prefix=CHECK9 %s +// CHECK10: "static void extracted(int &y) {\nint x = 1; y = 2; int z = 3; { int x, y, z; }\n}\n\n" +// CHECK10-NEXT: "int y;\nextracted(y);" +// RUN: clang-refactor-test perform -action extract -selected=%s:59:3-59:44 %s -DMULTIPLE_DECL1 -DUSEY | FileCheck --check-prefix=CHECK10 %s +// CHECK11: "static void extracted(int &z) {\nint x = 1; int y = 2; z = 3; { int x, y, z; }\n}\n\n" +// CHECK11-NEXT: "int z;\nextracted(z);" +// RUN: clang-refactor-test perform -action extract -selected=%s:59:3-59:44 %s -DMULTIPLE_DECL1 -DUSEZ | FileCheck --check-prefix=CHECK11 %s + +// CHECK12: "static void extracted() {\nint x, y = 2, *z;\n}\n\n" +// CHECK12-NEXT: "extracted();" +// RUN: clang-refactor-test perform -action extract -selected=%s:62:3-62:19 %s -DMULTIPLE_DECL2 | FileCheck --check-prefix=CHECK12 %s +// CHECK13: "static void extracted(int &y) {\ny = 2;\n}\n\n" +// CHECK13-NEXT: "int x;\nint y;\nint * z;\nextracted(y);" +// RUN: clang-refactor-test perform -action extract -selected=%s:62:3-62:19 %s -DMULTIPLE_DECL2 -DUSEX -DUSEY -DUSEZ | FileCheck --check-prefix=CHECK13 %s +// CHECK14: "static void extracted(int &y) {\nint x; y = 2; int * z; { int y; }\n}\n\n" +// CHECK14-NEXT: "int y;\nextracted(y);" +// RUN: clang-refactor-test perform -action extract -selected=%s:62:3-62:31 %s -DMULTIPLE_DECL2 -DUSEY | FileCheck --check-prefix=CHECK14 %s + +// CHECK15: "static void extracted() {\nint x = 1, y, * z = 0, a, b = {0};\n}\n\n" +// CHECK15-NEXT: "extracted();" +// RUN: clang-refactor-test perform -action extract -selected=%s:65:3-65:36 %s -DMULTIPLE_DECL3 | FileCheck --check-prefix=CHECK15 %s +// CHECK16: "static void extracted(int &x, int *&z) {\nx = 1; z = 0; int a; int b = {0};\n}\n\n" +// CHECK16-NEXT: "int x;\nint y;\nint * z;\nextracted(x, z);" +// RUN: clang-refactor-test perform -action extract -selected=%s:65:3-65:36 %s -DMULTIPLE_DECL3 -DUSEX -DUSEY -DUSEZ | FileCheck --check-prefix=CHECK16 %s +// CHECK17: "static void extracted(int &b, int &x, int *&z) {\nx = 1; z = 0; b = {0};\n}\n\n" +// CHECK17-NEXT: "int x;\nint y;\nint * z;\nint a;\nint b;\nextracted(b, x, z);" +// RUN: clang-refactor-test perform -action extract -selected=%s:65:3-65:36 %s -DMULTIPLE_DECL3 -DUSEX -DUSEY -DUSEZ -DUSEA -DUSEB | FileCheck --check-prefix=CHECK17 %s +// CHECK18: "static void extracted() {\nint x = 1; int y; int * z = 0; int b = {0}; { int a; }\n}\n\n" +// CHECK18-NEXT: "int a;\nextracted();" +// RUN: clang-refactor-test perform -action extract -selected=%s:65:3-65:48 %s -DMULTIPLE_DECL3 -DUSEA | FileCheck --check-prefix=CHECK18 %s + +#define ONE 1 + +void preserveInitExpressionText() { + int a = ONE; + int x = ONE, y = ONE; + a, y; +} + +// CHECK19: "static void extracted(int &a, int &y) {\na = ONE;\n int x = ONE; y = ONE;\n}\n\n +// CHECK19-NEXT: "int a;\nint y;\nextracted(a, y);" +// RUN: clang-refactor-test perform -action extract -selected=%s:126:3-127:23 %s | FileCheck --check-prefix=CHECK19 %s + +class Construct { +public: + Construct(); + Construct(int x, int y); +}; + +void handleConstruct() { + Construct a(1, 2); + Construct b(3, 4), c(5, 6); + a, c; +} +// CHECK20: "static void extracted(Construct &a, Construct &c) {\na = Construct(1, 2);\n Construct b(3, 4); c = Construct(5, 6);\n}\n\n" +// CHECK20-NEXT: "Construct a;\nConstruct c;\nextracted(a, c);" +// RUN: clang-refactor-test perform -action extract -selected=%s:142:3-143:29 %s | FileCheck --check-prefix=CHECK20 %s + +struct Construct2 { + int x, y; +}; + +void handleConstruct2() { + Construct2 a = {1, 2}; + Construct2 b = {3, 4}, c = {5, 6}; + a, c; +} +// CHECK21: "static void extracted(Construct2 &a, Construct2 &c) {\na = {1, 2};\n Construct2 b = {3, 4}; c = {5, 6};\n}\n\n" +// CHECK21-NEXT: "Construct2 a;\nConstruct2 c;\nextracted(a, c);" +// RUN: clang-refactor-test perform -action extract -selected=%s:155:3-156:29 %s -std=c++11 | FileCheck --check-prefix=CHECK21 %s + +class Construct3 { +public: + Construct3(); + Construct3(int x); +}; + +void handleConstruct3() { + Construct3 a = 1; + Construct3 b = 2, c = 3 + 3; + a, c; +} +// CHECK22: "static void extracted(Construct3 &a, Construct3 &c) {\na = 1;\n Construct3 b = 2; c = 3 + 3;\n}\n\n" +// CHECK22-NEXT: "Construct3 a;\nConstruct3 c;\nextracted(a, c);" +// RUN: clang-refactor-test perform -action extract -selected=%s:170:3-171:26 %s -std=c++11 | FileCheck --check-prefix=CHECK22 %s + +void handleConstruct2InitList() { + Construct2 a { 5, 6 }; + Construct2 b { 1, 2 }, c { 3, 4 }; + a, c; +} +// CHECK23: "static void extracted(Construct2 &a, Construct2 &c) {\na = { 5, 6 };\n Construct2 b = { 1, 2 }; c = { 3, 4 };\n}\n\n" +// CHECK23-NEXT: "Construct2 a;\nConstruct2 c;\nextracted(a, c);" +// RUN: clang-refactor-test perform -action extract -selected=%s:179:3-180:36 %s -std=c++11 | FileCheck --check-prefix=CHECK23 %s + +void onlyOneUsedAfterExtractionIsReturned() { + const int x = 0; + x; + Construct a(1, 2); + a; +} +// CHECK24: "static int extracted() {\nconst int x = 0;\nreturn x;\n}\n\n" +// CHECK24-NEXT: "const int x = extracted();" +// CHECK24: "static Construct extracted() {\nConstruct a(1, 2);\nreturn a;\n}\n\n" +// CHECK24-NEXT: "Construct a = extracted();" + +int avoidReturningWhenReturnUsed() { + int x = 0; + if (x != 0) { return 22; } + x; +} +// CHECK24: "static int extracted(int &x) {\nx = 0;\n if (x != 0) { return 22; }\n}\n\n" +// CHECK24-NEXT: "int x;\nextracted(x);" + +void returnOnlyWhenReturnReturnsNothing() { + int x = 0; + if (x != 0) { return; } + x; +} +// CHECK24: "static int extracted() {\nint x = 0;\n if (x != 0) { return x; }\nreturn x;\n}\n\n" +// CHECK24-NEXT: "int x = extracted();" + +// RUN: clang-refactor-test perform -action extract -selected=%s:188:3-188:18 -selected=%s:190:3-190:20 -selected=%s:199:3-200:29 -selected=%s:207:3-208:26 %s -std=c++11 | FileCheck --check-prefix=CHECK24 %s diff --git a/clang/test/Refactor/Extract/extract-capture-used-after-extraction.m b/clang/test/Refactor/Extract/extract-capture-used-after-extraction.m new file mode 100644 index 0000000000000..c115babbf8622 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-capture-used-after-extraction.m @@ -0,0 +1,25 @@ +void takesInt(int); +void passByPointer() { + int x = 1; + int y = 0, z = 2; + takesInt(x); + z = 2; + x, z; +} +// CHECK1: "static void extracted(int *x, int *z) {\n*x = 1;\n int y = 0; *z = 2;\n takesInt(*x);\n *z = 2;\n}\n\n" +// CHECK1-NEXT: "int x;\nint z;\nextracted(&x, &z)" +// RUN: clang-refactor-test perform -action extract -selected=%s:3:3-6:8 %s | FileCheck --check-prefix=CHECK1 %s + +typedef struct { int width; int height; } Rectangle; + +void handleStructInit() { + Rectangle a = { 5, 6 }; + Rectangle b = { 1, 2 }, c = { 3, 4 }; + takesInt(a.width); + c.height = 10; + a, c; +} +// The produced code is invalid but we can let the user deal with it: +// CHECK2: "static void extracted(Rectangle *a, Rectangle *c) {\n*a = { 5, 6 };\n Rectangle b = { 1, 2 }; *c = { 3, 4 };\n takesInt(a->width);\n c->height = 10;\n}\n\n" 15:1 -> 15:1 +// CHECK2: "Rectangle a;\nRectangle c;\nextracted(&a, &c)" +// RUN: clang-refactor-test perform -action extract -selected=%s:16:3-19:16 %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Extract/extract-expression.cpp b/clang/test/Refactor/Extract/extract-expression.cpp new file mode 100644 index 0000000000000..56b26df8487b6 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-expression.cpp @@ -0,0 +1,60 @@ + +struct Rectangle { int width, height; }; + +int sumArea(Rectangle *rs, int count) { + int sum = 0; + for (int i = 0; i < count; ++i) { + Rectangle r = rs[i]; + sum += r.width * r.height; + } + return sum; +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:8:12-8:30 %s | FileCheck --check-prefix=CHECK1 %s +// CHECK1: "static int extracted(const Rectangle &r) {\nreturn r.width * r.height;\n}\n\n" 4:1 -> 4:1 +// CHECK1-NEXT: "extracted(r)" 8:12 -> 8:30 +; +void extractFullExpressionIfPartiallySelected(const Rectangle &r) { + int area = r.width * r.height; +} +// CHECK2: "static int extracted(const Rectangle &r) {\nreturn r.width * r.height;\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK2-NEXT: "extracted(r)" [[@LINE-3]]:14 -> [[@LINE-3]]:32 + +// RUN: clang-refactor-test perform -action extract -selected=%s:18:20-18:25 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:18:15-18:32 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:18:14-18:30 %s | FileCheck --check-prefix=CHECK2 %s +; +int extractFullMultipleCandidates(const Rectangle &r1) { + int y = r1.width - r1.width * r1.height; +} +// CHECK3-1: "static int extracted(const Rectangle &r1) {\nreturn - r1.width * r1.height;\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK3-1-NEXT: "+ extracted(r1)" [[@LINE-3]]:20 -> [[@LINE-3]]:42 +// CHECK3-2: "static int extracted(const Rectangle &r1) {\nreturn r1.width - r1.width * r1.height;\n}\n\n" [[@LINE-5]]:1 -> [[@LINE-5]]:1 +// CHECK3-2-NEXT: "extracted(r1)" [[@LINE-5]]:11 -> [[@LINE-5]]:42 + +// RUN: clang-refactor-test perform -action extract -selected=%s:28:20-28:42 %s | FileCheck --check-prefix=CHECK3-1 %s +// RUN: clang-refactor-test perform -action extract -candidate 0 -selected=%s:28:20-28:42 %s | FileCheck --check-prefix=CHECK3-1 %s +// RUN: clang-refactor-test perform -action extract -candidate 1 -selected=%s:28:20-28:42 %s | FileCheck --check-prefix=CHECK3-2 %s + +// RUN: not clang-refactor-test perform -action extract -candidate 2 -selected=%s:28:20-28:42 %s 2>&1 | FileCheck --check-prefix=CHECK-CAND-FAIL %s +// CHECK-CAND-FAIL: failed to select the refactoring candidate +; +int extractFullMultipleCandidatesCaptureJustExtractedVariables( + const Rectangle &r1, const Rectangle &r2) { + return r1.width - r2.width * r2.height; +} +// CHECK4: "static int extracted(const Rectangle &r2) {\nreturn - r2.width * r2.height;\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK4-NEXT: "+ extracted(r2)" [[@LINE-3]]:19 -> [[@LINE-3]]:41 + +// RUN: clang-refactor-test perform -action extract -candidate 0 -selected=%s:44:19-44:41 %s | FileCheck --check-prefix=CHECK4 %s + +// Even when the expression result is unused statement, we still want to extract +// it as an expression. +; +void extractStatementExpression(const Rectangle &r) { + r.width * r.height; +} +// CHECK5: "static int extracted(const Rectangle &r) {\nreturn r.width * r.height;\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK5-NEXT: "extracted(r)" [[@LINE-3]]:3 -> [[@LINE-3]]:21 + +// RUN: clang-refactor-test perform -action extract -selected=%s:55:3-55:21 %s | FileCheck --check-prefix=CHECK5 %s diff --git a/clang/test/Refactor/Extract/extract-from-class.cpp b/clang/test/Refactor/Extract/extract-from-class.cpp new file mode 100644 index 0000000000000..29d9a0041050b --- /dev/null +++ b/clang/test/Refactor/Extract/extract-from-class.cpp @@ -0,0 +1,45 @@ + +#ifdef MULTIPLE +class OuterClass { +#endif + +class AClass { + + int method(int x) { + return x + x * 2; + } +// CHECK1: "static int extracted(int x) {\nreturn x + x * 2;\n}\n\n" [[@LINE-5]]:1 +// CHECK2: "static int extracted(int x) {\nreturn x + x * 2;\n}\n\n" [[@LINE-9]]:1 +; + AClass(int x) { + int y = x * 1; + } +// CHECK1: "static int extracted(int x) {\nreturn x * 1;\n}\n\n" [[@LINE-11]]:1 +// CHECK2: "static int extracted(int x) {\nreturn x * 1;\n}\n\n" [[@LINE-15]]:1 + ~AClass() { + int x = 0 + 4; + } +// CHECK1: "static int extracted() {\nreturn 0 + 4;\n}\n\n" [[@LINE-16]]:1 +// CHECK2: "static int extracted() {\nreturn 0 + 4;\n}\n\n" [[@LINE-20]]:1 +; + int operator +(int x) { + return x + x * 3; + } +// CHECK1: "static int extracted(int x) {\nreturn x + x * 3;\n}\n\n" [[@LINE-22]]:1 +// CHECK2: "static int extracted(int x) {\nreturn x + x * 3;\n}\n\n" [[@LINE-26]]:1 +; + void otherMethod(int x); +}; + +#ifdef MULTIPLE +} +#endif + +// RUN: clang-refactor-test perform -action extract -selected=%s:9:12-9:21 -selected=%s:15:13-15:18 -selected=%s:20:13-20:18 -selected=%s:26:12-26:21 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:9:12-9:21 -selected=%s:15:13-15:18 -selected=%s:20:13-20:18 -selected=%s:26:12-26:21 %s -DMULTIPLE | FileCheck --check-prefix=CHECK2 %s +; +void AClass::otherMethod(int x) { + int y = x * x + 10; +} +// CHECK3: "static int extracted(int x) {\nreturn x * x + 10;\n}\n\n" [[@LINE-3]]:1 +// RUN: clang-refactor-test perform -action extract -selected=%s:42:11-42:21 %s | FileCheck --check-prefix=CHECK3 %s diff --git a/clang/test/Refactor/Extract/extract-header-inline.h b/clang/test/Refactor/Extract/extract-header-inline.h new file mode 100644 index 0000000000000..a323a1164a027 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-header-inline.h @@ -0,0 +1,7 @@ +// RUN: clang-refactor-test perform -action extract -selected=extract %s | FileCheck %s +; +void extractInline(int x) { // CHECK: "inline int extracted(int x) {\nreturn x + 1;\n}\n\n" [[@LINE]]:1 +// extract-begin: +1:11 + int y = x + 1; +// extract-end: -1:16 +} diff --git a/clang/test/Refactor/Extract/extract-initiate.cpp b/clang/test/Refactor/Extract/extract-initiate.cpp new file mode 100644 index 0000000000000..3ddbc9fc63dd2 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-initiate.cpp @@ -0,0 +1,150 @@ +struct Rectangle { int width, height; }; + +int sumArea(Rectangle *rs, int count) { + int sum = 0; + for (int i = 0; i < count; ++i) { + Rectangle r = rs[i]; + sum += r.width * r.height; + } + return sum; +} + +// RUN: clang-refactor-test list-actions -at=%s:7:30 -selected=%s:7:12-7:30 %s | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Extract Function + +// Ensure the an entire expression can be extracted: + +// RUN: clang-refactor-test initiate -action extract -selected=%s:7:12-7:30 %s | FileCheck --check-prefix=CHECK1 %s +// CHECK1: Initiated the 'extract' action at 7:12 -> 7:30 + +// Ensure that an expression can be extracted even when it's not fully selected: + +// RUN: clang-refactor-test initiate -action extract -selected=%s:7:13-7:30 -selected=%s:7:18-7:30 -selected=%s:7:20-7:30 -selected=%s:7:19-7:30 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action extract -selected=%s:7:12-7:29 -selected=%s:7:12-7:23 -selected=%s:7:12-7:21 -selected=%s:7:12-7:22 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action extract -selected=%s:7:19-7:22 -selected=%s:7:20-7:21 -selected=%s:7:15-7:25 %s | FileCheck --check-prefix=CHECK1 %s + +// Ensure that the action isn't allowed be when no expression is selected: + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:1:1-1:5 -selected=%s:2:1-2:1 -selected=%s:3:1-3:38 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// RUN: not clang-refactor-test initiate -action extract -at=%s:1:1 -in=%s:3:1-38 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// CHECK-NO: Failed to initiate the refactoring action +// CHECK-NO-NOT: Initiated the 'extract' action + +int multipleCandidates(Rectangle &r1, Rectangle &r2) { + int x = r1.width + r1.height; // CHECK2-SINGLE: Initiated the 'extract' action at [[@LINE]]:11 -> [[@LINE]]:31 + int y = r1.width - r2.width * r2.height; +} + +// RUN: clang-refactor-test initiate -action extract -selected=%s:34:20-34:31 -selected=%s:34:19-34:23 %s | FileCheck --check-prefix=CHECK2 %s +// CHECK2: Initiated the 'extract' action with multiple candidates: +// CHECK2-NEXT: + r1.height +// CHECK2-NEXT: r1.width + r1.height +// RUN: clang-refactor-test initiate -action extract -selected=%s:34:20-34:21 %s | FileCheck --check-prefix=CHECK2-SINGLE %s + +// RUN: clang-refactor-test initiate -action extract -selected=%s:35:19-35:42 %s | FileCheck --check-prefix=CHECK3 %s +// CHECK3: Initiated the 'extract' action with multiple candidates: +// CHECK3-NEXT: - r2.width * r2.height +// CHECK3-NEXT: r1.width - r2.width * r2.height + +void trimWhitespaceAndSemiColons(const Rectangle &r) { + int x = r.width + r.width * r.height; ; + //CHECK4: Initiated the 'extract' action at [[@LINE-1]]:26 -> [[@LINE-1]]:44 + //CHECK5: Initiated the 'extract' action at [[@LINE-2]]:12 -> [[@LINE-2]]:44 +} + +// RUN: clang-refactor-test initiate -action extract -selected=%s:50:23-50:46 -selected=%s:50:23-50:45 %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action extract -selected=%s:50:10-51:3 %s | FileCheck --check-prefix=CHECK5 %s + +void disallowBlankStatements() { + // comment + ; + +} + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:59:1-59:3 -selected=%s:60:1-62:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void allowCommentSelection(int y) { + char c = "//fake comment" [y] ; +} + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:67:13-67:16 %s 2>&1 | FileCheck --check-prefix=CHECK-SIMPLE %s +// CHECK-SIMPLE: Failed to initiate the refactoring action (the selected expression is too simple) + +void extractStatements(const Rectangle &r) { + (void)r; + int area = r.width * r.height; + int perimeter = r.width + r.height; + int sum = area + perimeter; + // CHECK6: Initiated the 'extract' action at [[@LINE-3]]:3 -> [[@LINE-2]]:38 + // CHECK7: Initiated the 'extract' action at [[@LINE-5]]:3 -> [[@LINE-2]]:30 +} + +// RUN: clang-refactor-test initiate -action extract -selected=%s:75:1-76:38 -selected=%s:74:11-77:3 -selected=%s:75:14-76:26 %s | FileCheck --check-prefix=CHECK6 %s + +// RUN: clang-refactor-test initiate -action extract -selected=%s:74:1-77:30 -selected=%s:73:45-78:1 %s | FileCheck --check-prefix=CHECK7 %s + +void extractStatementsCompoundChild(const Rectangle &r) { + int x = 0; + { + int area = r.width * r.height; + } + int y = 0; + int z = 1; + // CHECK8: Initiated the 'extract' action at [[@LINE-5]]:3 -> [[@LINE-2]]:13 +} + +// RUN: clang-refactor-test initiate -action extract -selected=%s:89:5-91:12 %s | FileCheck --check-prefix=CHECK8 %s + +void extractStatementsTrimComments(const Rectangle &r) { + int x = 0; + + // comment + int area = r.width * r.height; + + // another comment + int y = 0; + + // trailing comment +} +// CHECK9: Initiated the 'extract' action at [[@LINE-7]]:3 -> [[@LINE-7]]:33 +// CHECK10: Initiated the 'extract' action at [[@LINE-5]]:3 -> [[@LINE-5]]:13 + +// RUN: clang-refactor-test initiate -action extract -selected=%s:100:1-102:32 -selected=%s:101:6-104:21 -selected=%s:100:1-105:3 %s | FileCheck --check-prefix=CHECK9 %s +// RUN: clang-refactor-test initiate -action extract -selected=%s:103:1-105:12 -selected=%s:104:6-107:22 -selected=%s:103:1-108:1 %s | FileCheck --check-prefix=CHECK10 %s + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:101:1-101:13 -selected=%s:106:1-107:22 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void disallowEmptyCompoundStatement() { + // comment +} + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:118:1-118:13 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void trimLeadingCompoundStatementComment() { + // comment + + int x = 0; +} + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:124:1-124:13 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void extractEntireCompoundStatement() { + { + int x = 0; + int y = 0; + } + // CHECK11: Initiated the 'extract' action at [[@LINE-4]]:3 -> [[@LINE-1]]:4 + // CHECK12: Initiated the 'extract' action at [[@LINE-4]]:5 -> [[@LINE-3]]:15 + // CHECK13: Initiated the 'extract' action at [[@LINE-5]]:5 -> [[@LINE-4]]:15 +} + +// RUN: clang-refactor-test initiate -action extract -selected=%s:132:3-135:4 %s | FileCheck --check-prefix=CHECK11 %s +// RUN: clang-refactor-test initiate -action extract -selected=%s:132:3-135:3 %s | FileCheck --check-prefix=CHECK12 %s +// RUN: clang-refactor-test initiate -action extract -selected=%s:132:4-135:4 %s | FileCheck --check-prefix=CHECK13 %s + +void disallowExtractionWhenSelectionRangeIsOutsideFunction() { + int x = 0; + int x = 1; +} + +// RUN: not clang-refactor-test initiate -action extract -selected=%s:143:1-147:12 -selected=%s:146:3-150:3 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/Extract/extract-initiate.m b/clang/test/Refactor/Extract/extract-initiate.m new file mode 100644 index 0000000000000..4af03c6dd62d5 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-initiate.m @@ -0,0 +1,14 @@ + +@interface I +@end + +@implementation I + +- (int)computeAreaSquaredGivenWidth:(int)width height:(int)height { + int area = width * height; + return area * area; +} +//CHECK1: Initiated the 'extract' action at [[@LINE-3]]:14 -> [[@LINE-3]]:28 +// RUN: clang-refactor-test initiate -action extract -selected=%s:8:14-8:28 %s | FileCheck --check-prefix=CHECK1 %s + +@end diff --git a/clang/test/Refactor/Extract/extract-macros.cpp b/clang/test/Refactor/Extract/extract-macros.cpp new file mode 100644 index 0000000000000..ecac1cee60541 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-macros.cpp @@ -0,0 +1,39 @@ +#define ONE 1 + +void extractExprMacros(int x) { + bool b = x == ONE; +// CHECK1: {\nreturn x == ONE;\n} + int y = ONE + x; +// CHECK1: {\nreturn ONE + x;\n} + int z = ONE - ONE * ONE; +// CHECK1: {\nreturn ONE - ONE * ONE;\n} +// CHECK1: {\nreturn ONE * ONE;\n} +// CHECK1: {\nreturn - ONE * ONE;\n} +} +// RUN: clang-refactor-test perform -action extract -selected=%s:4:12-4:20 -selected=%s:6:11-6:19 -selected=%s:8:12-8:26 -selected=%s:8:17-8:26 -selected=%s:8:15-8:26 %s | FileCheck --check-prefix=CHECK1 %s + +#define MACRO2(x, y) x, y + +int function(int x); + +// MACRO-ARG3: "static int extracted(int x, int &y, int z) {\nreturn y = MACRO2(x + 2, function(z));\n}\n\n" [[@LINE+4]]:1 +// MACRO-ARG2: "static int extracted(int z) {\nreturn function(z);\n}\n\n" [[@LINE+3]]:1 +// MACRO-ARG1: "static int extracted(int x) {\nreturn x + 2;\n}\n\n" [[@LINE+2]]:1 +; +void extractFromMacroArgument(int x, int y, int z) { + + // macro-arg-expr4-begin: +4:7 + // macro-arg-expr3-begin: +3:14 + // macro-arg-expr2-begin: +2:21 + // macro-arg-expr1-begin: +1:14 + y = MACRO2(x + 2, function(z)); // comment4 + // macro-arg-expr1-end: -1:19 // MACRO-ARG1: "extracted(x)" [[@LINE-1]]:14 -> [[@LINE-1]]:19 + // macro-arg-expr2-end: -2:32 // MACRO-ARG2: "extracted(z)" [[@LINE-2]]:21 -> [[@LINE-2]]:32 + // macro-arg-expr3-end: -3:32 // MACRO-ARG3: "extracted(x, y, z)" [[@LINE-3]]:3 -> [[@LINE-3]]:33 + // macro-arg-expr4-end: -4:19 +} + +// RUN: clang-refactor-test perform -action extract -selected=macro-arg-expr1 %s | FileCheck --check-prefix=MACRO-ARG1 %s +// RUN: clang-refactor-test perform -action extract -selected=macro-arg-expr2 %s | FileCheck --check-prefix=MACRO-ARG2 %s +// RUN: clang-refactor-test perform -action extract -selected=macro-arg-expr3 %s | FileCheck --check-prefix=MACRO-ARG3 %s +// RUN: clang-refactor-test perform -action extract -selected=macro-arg-expr4 %s | FileCheck --check-prefix=MACRO-ARG3 %s diff --git a/clang/test/Refactor/Extract/extract-method.cpp b/clang/test/Refactor/Extract/extract-method.cpp new file mode 100644 index 0000000000000..84beaa87d6b82 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-method.cpp @@ -0,0 +1,153 @@ + +void function(int x) { + int y = x * x; +} + +class InitiateMethodExtraction { +public: + + InitiateMethodExtraction() { + int x = constMethod(1); + } +// CHECK1: "void extracted() {\nint x = constMethod(1);\n}\n\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 +// CHECK1-NEXT: "extracted();" [[@LINE-3]]:5 -> [[@LINE-3]]:28 +// CHECK2: "static void extracted(const InitiateMethodExtraction &object) {\nint x = object.constMethod(1);\n}\n\n" +// CHECK2-NEXT: "extracted(*this);" +; + ~InitiateMethodExtraction() { + int x = constMethod(2); + } +// CHECK1: "void extracted() {\nint x = constMethod(2);\n}\n\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 +// CHECK1-NEXT: "extracted();" [[@LINE-3]]:5 -> [[@LINE-3]]:28 +// CHECK2: "static void extracted(const InitiateMethodExtraction &object) {\nint x = object.constMethod(2);\n}\n\n" +// CHECK2-NEXT: "extracted(*this);" +; + void method() { + int x = constMethod(3); + } +// CHECK1: "void extracted() {\nint x = constMethod(3);\n}\n\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 +// CHECK1-NEXT: "extracted();" [[@LINE-3]]:5 -> [[@LINE-3]]:28 +// CHECK2: "static void extracted(const InitiateMethodExtraction &object) {\nint x = object.constMethod(3);\n}\n\n" +// CHECK2-NEXT: "extracted(*this);" +; + int constMethod(int x) const { + return x + x * 2; + } +// CHECK1: "int extracted(int x) const {\nreturn x + x * 2;\n}\n\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 +// CHECK1-NEXT: "extracted(x);" [[@LINE-3]]:5 -> [[@LINE-3]]:22 +// CHECK2: "static int extracted(int x) {\nreturn x + x * 2;\n}\n\n" +// CHECK2-NEXT: "extracted(x);" +; + int operator << (int x) { + return constMethod(x); + } +// CHECK1: "int extracted(int x) {\nreturn constMethod(x);\n}\n\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 +// CHECK1-NEXT: "extracted(x)" [[@LINE-3]]:12 -> [[@LINE-3]]:26 +// CHECK2: "static int extracted(const InitiateMethodExtraction &object, int x) {\nreturn object.constMethod(x);\n}\n\n" +// CHECK2-NEXT: "extracted(*this, x)" +; + static void staticMethod(int x) { + int y = x * x; + } +// CHECK1: "static void extracted(int x) {\nint y = x * x;\n}\n\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 +// CHECK1-NEXT: "extracted(x);" [[@LINE-3]]:5 -> [[@LINE-3]]:19 +// CHECK2: "static void extracted(int x) {\nint y = x * x;\n}\n\n" +// CHECK2-NEXT: "extracted(x);" +; + void otherMethod(); +}; + +void InitiateMethodExtraction::otherMethod() { + int x = constMethod(4); +} +// CHECK1: "void extracted();\n\n" [[@LINE-6]]:3 -> [[@LINE-6]]:3 +// CHECK1-NEXT: "void InitiateMethodExtraction::extracted() {\nint x = constMethod(4);\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK1-NEXT: "extracted();" [[@LINE-4]]:3 -> [[@LINE-4]]:26 +// CHECK2: "static void extracted(const InitiateMethodExtraction &object) {\nint x = object.constMethod(4);\n}\n\n" +// CHECK2-NEXT: "extracted(*this);" + +// RUN: clang-refactor-test list-actions -at=%s:10:5 -selected=%s:10:5-10:27 %s | FileCheck --check-prefix=CHECK-METHOD %s + +// CHECK-METHOD: Extract Function{{$}} +// CHECK-METHOD-NEXT: Extract Method{{$}} + +// RUN: clang-refactor-test list-actions -at=%s:3:3 -selected=%s:3:3-3:16 %s | FileCheck --check-prefix=CHECK-FUNC %s + +// CHECK-FUNC: Extract Function{{$}} +// CHECK-FUNC-NOT: Extract Method + +// RUN: clang-refactor-test perform -action extract-method -selected=%s:10:5-10:27 -selected=%s:18:5-18:27 -selected=%s:26:5-26:27 -selected=%s:34:5-34:21 -selected=%s:42:12-42:26 -selected=%s:50:5-50:18 -selected=%s:61:3-61:25 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:10:5-10:27 -selected=%s:18:5-18:27 -selected=%s:26:5-26:27 -selected=%s:34:5-34:21 -selected=%s:42:12-42:26 -selected=%s:50:5-50:18 -selected=%s:61:3-61:25 %s | FileCheck --check-prefix=CHECK2 %s + + + + + + + +namespace ns { + struct Outer { + struct Inner { + Inner(int x); + + // comment + void method2(int x) const; // this stays! + int field; + }; + }; +} +ns::Outer::Inner::Inner(int x) { + int y = x + x; +} +// CHECK3: "void extracted(int x);\n\n" [[@LINE-11]]:7 -> [[@LINE-11]]:7 +// CHECK3: "void ns::Outer::Inner::extracted(int x) {\nint y = x + x;\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK3: "extracted(x);" [[@LINE-4]]:3 -> [[@LINE-4]]:17 + +namespace ns { + + // comment + void Outer::Inner::method2(int x) const { + if (x != 0) + int z = x * x; + else { + (void)x; + } + } +// CHECK3: "void extracted(int x) const;\n\n" [[@LINE-23]]:7 -> [[@LINE-23]]:7 +// CHECK3: "void Outer::Inner::extracted(int x) const {\nif (x != 0)\n int z = x * x;\n else {\n (void)x;\n }\n}\n\n" [[@LINE-9]]:3 -> [[@LINE-9]]:3 +// CHECK3: "extracted(x);" [[@LINE-8]]:5 -> [[@LINE-4]]:6 +; + void Outer::Inner::methodNotDeclared(int x) { + int z = x * x; + } +} +// CHECK3: "\n\nvoid extracted(int x);\n" [[@LINE-30]]:48 -> [[@LINE-30]]:48 +// CHECK3: "void Outer::Inner::extracted(int x) {\nint z = x * x;\n}\n\n" [[@LINE-5]]:3 -> [[@LINE-5]]:3 +// CHECK3: "extracted(x);" [[@LINE-5]]:5 -> [[@LINE-5]]:19 + +struct Empty { + int field; +}; + +void Empty::method() { + field = 22; +} +// CHECK3: "void extracted();\n\n" [[@LINE-5]]:1 -> [[@LINE-5]]:1 +// CHECK3: "void Empty::extracted() {\nfield = 22;\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK3: "extracted()" [[@LINE-4]]:3 -> [[@LINE-4]]:13 + +// RUN: clang-refactor-test perform -action extract-method -selected=%s:100:3-100:16 -selected=%s:110:5-114:4 -selected=%s:121:5-121:18 -selected=%s:133:3-133:13 %s | FileCheck --check-prefix=CHECK3 %s + +template +struct TemplateExtraction { + void method(); // CHECK4: "void extracted();\n\n" [[@LINE]]:3 -> [[@LINE]]:3 +}; + +template // CHECK4: "template \nvoid TemplateExtraction::extracted() {\nint y = x;\n}\n\n" [[@LINE]]:1 -> [[@LINE]]:1 +void TemplateExtraction::method() { +// template-method-begin: +1:1 + int y = x; +// template-method-end: +0:1 +} + +// RUN: clang-refactor-test perform -action extract-method -selected=template-method %s | FileCheck --check-prefix=CHECK4 %s diff --git a/clang/test/Refactor/Extract/extract-method.m b/clang/test/Refactor/Extract/extract-method.m new file mode 100644 index 0000000000000..6c136b0770dbf --- /dev/null +++ b/clang/test/Refactor/Extract/extract-method.m @@ -0,0 +1,57 @@ + +void function(int x) { + int y = x * x; +} + +@interface MethodExtraction + +- (int)method; +- (void)classMethod; + +@end + +@implementation MethodExtraction + +- (void)aMethod:(int)x withY:(int)y { + int a = x + y; +} +// CHECK1: "- (void)extracted:(int)x y:(int)y {\nint a = x + y;\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK1-NEXT: "[self extracted:x y:y];" [[@LINE-3]]:3 -> [[@LINE-3]]:17 +// CHECK2: "static void extracted(int x, int y) {\nint a = x + y;\n}\n\n" +// CHECK2-NEXT: "extracted(x, y);" +; ++ (void)classMethod { + int x = 1; + int y = function(x); +} +// CHECK1: "+ (void)extracted {\nint x = 1;\n int y = function(x);\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 +// CHECK1-NEXT: "[self extracted];" [[@LINE-4]]:3 -> [[@LINE-3]]:23 +// CHECK2: "static void extracted() {\nint x = 1;\n int y = function(x);\n}\n\n" +// CHECK2-NEXT: "extracted();" + +@end + +@implementation MethodExtraction (Category) + +- (void)catMethod { + int x = [self method]; +} + ++ (void)catClassMethod { + int x = function(42); +} + +@end + +// RUN: clang-refactor-test list-actions -at=%s:16:3 -selected=%s:16:3-16:16 %s | FileCheck --check-prefix=CHECK-METHOD %s + +// CHECK-METHOD: Extract Function{{$}} +// CHECK-METHOD-NEXT: Extract Method{{$}} + +// RUN: clang-refactor-test list-actions -at=%s:3:3 -selected=%s:3:3-3:16 %s | FileCheck --check-prefix=CHECK-FUNC %s + +// CHECK-FUNC: Extract Function{{$}} +// CHECK-FUNC-NOT: Extract Method + +// RUN: clang-refactor-test perform -action extract-method -selected=%s:16:3-16:16 -selected=%s:24:3-25:22 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:16:3-16:16 -selected=%s:24:3-25:22 %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Extract/extract-mutation-of-captured-variable.cpp b/clang/test/Refactor/Extract/extract-mutation-of-captured-variable.cpp new file mode 100644 index 0000000000000..1b9e2eeba78b8 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-mutation-of-captured-variable.cpp @@ -0,0 +1,130 @@ + +typedef struct { + int width, height; +} Rectangle; + +#ifdef ADD +#define MUT(x, y) x += y +#endif + +#ifdef MUL +#define MUT(x, y) x *= y +#endif + +#ifdef BIT +#define MUT(x, y) x |= y +#endif + +#ifdef SHIFT +#define MUT(x, y) x >>= y +#endif + +#ifdef INC1 +#define MUT(x, y) ++x +#endif + +#ifdef INC2 +#define MUT(x, y) x++ +#endif + +#ifdef DEC1 +#define MUT(x, y) --x +#endif + +#ifdef DEC2 +#define MUT(x, y) x-- +#endif + +#ifndef MUT +#define MUT(x, y) x = y +#endif + +#ifdef FIELD +class MutatePrivateInstanceVariables { + int x; + int y; + Rectangle r; + +#endif + +void mutateVariableOrField +#ifndef FIELD + (int x, int y, Rectangle r) +#else + () +#endif +{ + (MUT(x, 1)); +// CHECK1: (int &x) {\nreturn (MUT(x, 1));\n} + + (MUT((x), 1)); +// CHECK1: (int &x) {\nreturn (MUT((x), 1));\n} + + (MUT(r.width, 1)); +// CHECK1: (Rectangle &r) {\nreturn (MUT(r.width, 1));\n} + + (MUT((x, r.height), 1)); +// CHECK1: (Rectangle &r, int x) {\nreturn (MUT((x, r.height), 1));\n} + + (MUT((x == 0 ? x : y), 1)); +// CHECK1: (int &x, int &y) {\nreturn (MUT((x == 0 ? x : y), 1));\n} + + Rectangle a, b; + (x == 0 ? (r) : b) = a; +// CHECK2: (const Rectangle &a, Rectangle &b, Rectangle &r, int x) {\nreturn (x == 0 ? (r) : b) = a;\n} + +} + +#ifdef FIELD +}; +#endif + +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DFIELD | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test perform -action extract -selected=%s:73:3-73:25 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:73:3-73:25 %s -DFIELD | FileCheck --check-prefix=CHECK2 %s + +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DADD | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DMUL -DFIELD | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DBIT | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DSHIFT -DFIELD | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DINC1 | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DINC2 -DFIELD | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DDEC1 | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:57:3-57:14 -selected=%s:60:3-60:16 -selected=%s:63:3-63:20 -selected=%s:66:3-66:26 -selected=%s:69:3-69:29 %s -DDEC2 -DFIELD | FileCheck --check-prefix=CHECK1 %s + +void dontMutateVariable(int *array, int x) { + array[x] = 0; +// CHECK3: (int *array, int x) {\narray[x] = 0;\n} + *array = 0; +// CHECK3: (int *array) {\n*array = 0;\n} + array = 0; +// CHECK3: extracted(int *&array) {\narray = 0;\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:101:3-101:15 -selected=%s:103:3-103:13 -selected=%s:105:3-105:12 %s | FileCheck --check-prefix=CHECK3 %s + +#ifdef __cplusplus + +int &returnsRef(int x) { + static int result = 0; + return result; +} + +void dontMutateCallArguments(int x) { + returnsRef(x) = 0; +// CHECK4: extracted(int x) {\nreturnsRef(x) = 0;\n} +} + +void mutateRefVar(int &x) { + x = 0; +// CHECK4: extracted(int &x) {\nx = 0;\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:119:3-119:20 -selected=%s:124:3-124:8 %s | FileCheck --check-prefix=CHECK4 %s + +#endif diff --git a/clang/test/Refactor/Extract/extract-objc-property.m b/clang/test/Refactor/Extract/extract-objc-property.m new file mode 100644 index 0000000000000..94cb4ef70991a --- /dev/null +++ b/clang/test/Refactor/Extract/extract-objc-property.m @@ -0,0 +1,47 @@ +@interface HasProperty + +@property (strong) HasProperty *item; + +- (HasProperty *)implicitProp; + +- (void)setImplicitSetter:(HasProperty *)value; + +@end + +@implementation HasProperty + +- (void)test { +// property-name-begin: +2:8 +// property-begin: +1:3 + self.item; +// property-end: -1:12 +// property-name-end: -2:12 +// CHECK: "static HasProperty * extracted(HasProperty *object) {\nreturn object.item;\n}\n\n" + +// implicit-name-begin: +2:8 +// implicit-begin: +1:3 + self.implicitProp; +// implicit-end: -1:20 +// implicit-name-end: -2:20 +// CHECK: "static HasProperty * extracted(HasProperty *object) {\nreturn object.implicitProp;\n}\n\n" +} + +// RUN: clang-refactor-test perform -action extract -selected=property -selected=implicit %s -fobjc-arc | FileCheck %s +// RUN: clang-refactor-test perform -action extract -selected=property-name -selected=implicit-name %s -fobjc-arc | FileCheck %s + +- (void)prohibitSetterExtraction { +// setter-pref-begin: +2:8 +// setter-begin: +1:3 + self.item = 0; +// setter-end: -1:12 +// setter-pref-end: -2:12 +// implicit-setter-pref-begin: +2:8 +// implicit-setter-begin: +1:3 + self.implicitSetter = 0; +// implicit-setter-end: -1:22 +// implicit-setter-pref-end: -2:22 +} +// CHECK-SETTER: Failed to initiate the refactoring action (property setter can't be extracted)! +// RUN: not clang-refactor-test initiate -action extract -selected=setter -selected=setter-pref -selected=implicit-setter -selected=implicit-setter-pref %s -fobjc-arc 2>&1 | FileCheck --check-prefix=CHECK-SETTER %s + +@end diff --git a/clang/test/Refactor/Extract/extract-reference-of-captured-variable.cpp b/clang/test/Refactor/Extract/extract-reference-of-captured-variable.cpp new file mode 100644 index 0000000000000..1c7116965d6d3 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-reference-of-captured-variable.cpp @@ -0,0 +1,287 @@ + +void takesPtr(int *x) { } + +typedef struct { + int width, height; +} Rectangle; + +void takesStructPtr(Rectangle *sp) { } + +void variableTakesRef(int x, Rectangle r) { + int &y = x; + takesPtr(&y); +// CHECK1: (int &x) {\nint &y = x;\n takesPtr(&y);\n} + Rectangle p = r; + Rectangle &rp = p; + takesStructPtr(&rp); +// CHECK1: (const Rectangle &r) {\nRectangle p = r;\n Rectangle &rp = p;\n takesStructPtr(&rp);\n} +// CHECK1: (Rectangle &p) {\nRectangle &rp = p;\n takesStructPtr(&rp);\n} + int &member = ((r).width); + int z = member; +// CHECK1: (Rectangle &r) {\nint &member = ((r).width);\n int z = member;\n} + +// Even though y takes a reference to x, we still want to pass it by value here. + int a = x; +// CHECK1: (int x) {\nint a = x;\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:11:3-12:15 -selected=%s:14:3-16:22 -selected=%s:15:3-16:22 -selected=%s:19:3-20:17 -selected=%s:24:3-24:12 %s | FileCheck --check-prefix=CHECK1 %s + +class PrivateInstanceVariables { + int x; + Rectangle r; + + void method() { + int &y = x; +// CHECK2: extracted(int &x) {\nint &y = x;\n} + Rectangle &rr = r; +// CHECK2: extracted(Rectangle &r) {\nRectangle &rr = r;\n} + int &z = ((r).width); +// CHECK2: extracted(Rectangle &r) {\nint &z = ((r).width);\n} + } +}; + +// RUN: clang-refactor-test perform -action extract -selected=%s:35:5-35:15 -selected=%s:37:5-37:22 -selected=%s:39:5-39:25 %s | FileCheck --check-prefix=CHECK2 %s + +#ifdef USECONST +#define CONST const +#else +#define CONST +#endif + +void takesRef(CONST int &x) { } + +void takesStructRef(CONST Rectangle &r) { } + +void takesValue(int x) { } + +struct ConsTakesRef { + ConsTakesRef(CONST int &x) { } + + void takesRef(CONST int &x) const { } + void takesValue(int x) const { } +}; + +int operator << (CONST Rectangle &r, CONST int &x) { return 0; } + +void callTakesRef(int x, Rectangle r) { + takesRef(x); +// CHECK3: extracted(int &x) {\ntakesRef(x);\n} +// CHECK4: extracted(int x) {\ntakesRef(x);\n} + takesValue(x); +// CHECK3: extracted(int x) {\ntakesValue(x);\n} +// CHECK4: extracted(int x) {\ntakesValue(x);\n} + auto k = ConsTakesRef(x); auto y = ConsTakesRef(x); +// CHECK3: extracted(int &x) {\nauto k = ConsTakesRef(x);\n} +// CHECK4: extracted(int x) {\nauto k = ConsTakesRef(x);\n} + y.takesRef((x)); +// CHECK3: extracted(int &x, const ConsTakesRef &y) {\ny.takesRef((x));\n} +// CHECK4: extracted(int x, const ConsTakesRef &y) {\ny.takesRef((x));\n} + y.takesValue(x); +// CHECK3: extracted(int x, const ConsTakesRef &y) {\ny.takesValue(x);\n} +// CHECK4: extracted(int x, const ConsTakesRef &y) {\ny.takesValue(x);\n} + takesStructRef((r)); +// CHECK3: extracted(Rectangle &r) {\ntakesStructRef((r));\n} +// CHECK4: extracted(const Rectangle &r) {\ntakesStructRef((r));\n} + takesRef((r).height); +// CHECK3: extracted(Rectangle &r) {\ntakesRef((r).height);\n} +// CHECK4: extracted(const Rectangle &r) {\ntakesRef((r).height);\n} + y.takesRef(r.width); +// CHECK3: extracted(Rectangle &r, const ConsTakesRef &y) {\ny.takesRef(r.width);\n} +// CHECK4: extracted(const Rectangle &r, const ConsTakesRef &y) {\ny.takesRef(r.width);\n} + takesValue(r.width); +// CHECK3: extracted(const Rectangle &r) {\ntakesValue(r.width);\n} +// CHECK4: extracted(const Rectangle &r) {\ntakesValue(r.width);\n} + r << x; +// CHECK3: extracted(Rectangle &r, int &x) {\nreturn r << x;\n} +// CHECK4: extracted(const Rectangle &r, int x) {\nreturn r << x;\n} + + int &r1 = x; + takesRef(x); +// CHECK3: extracted(int &x) {\nint &r1 = x;\n takesRef(x);\n} +// CHECK4: extracted(int &x) {\nint &r1 = x;\n takesRef(x);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:68:3-68:14 -selected=%s:71:3-71:16 -selected=%s:74:3-74:27 -selected=%s:77:3-77:18 -selected=%s:80:3-80:18 -selected=%s:83:3-83:22 -selected=%s:86:3-86:23 -selected=%s:89:3-89:22 -selected=%s:92:3-92:22 -selected=%s:95:3-95:9 -selected=%s:99:3-100:14 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:68:3-68:14 -selected=%s:71:3-71:16 -selected=%s:74:3-74:27 -selected=%s:77:3-77:18 -selected=%s:80:3-80:18 -selected=%s:83:3-83:22 -selected=%s:86:3-86:23 -selected=%s:89:3-89:22 -selected=%s:92:3-92:22 -selected=%s:95:3-95:9 -selected=%s:99:3-100:14 %s -DUSECONST | FileCheck --check-prefix=CHECK4 %s + +void takesConstRef(const int &x) { } + +void callTakeRefAndConstRef(int x) { + takesRef(x); + takesConstRef(x); +// CHECK5: extracted(int &x) {\ntakesRef(x);\n takesConstRef(x);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:111:3-112:19 %s | FileCheck --check-prefix=CHECK5 %s + +class PrivateInstanceVariablesCallRefs { + int x; + Rectangle r; + + void callsTakeRef() { + takesRef(x); +// CHECK6: extracted(int &x) {\ntakesRef(x);\n} +// CHECK7: extracted(int x) {\ntakesRef(x);\n} + takesStructRef(r); +// CHECK6: extracted(Rectangle &r) {\ntakesStructRef(r);\n} +// CHECK7: extracted(const Rectangle &r) {\ntakesStructRef(r);\n} + takesRef(r.width); +// CHECK6: extracted(Rectangle &r) {\ntakesRef(r.width);\n} +// CHECK7: extracted(const Rectangle &r) {\ntakesRef(r.width);\n} + } +}; + +// RUN: clang-refactor-test perform -action extract -selected=%s:123:5-123:16 -selected=%s:126:5-126:22 -selected=%s:129:5-129:22 %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:123:5-123:16 -selected=%s:126:5-126:22 -selected=%s:129:5-129:22 %s -DUSECONST | FileCheck --check-prefix=CHECK7 %s + +void variableTakesConstRef(int x, Rectangle r) { + const int &y = x; +// CHECK8: extracted(int x) {\nconst int &y = x;\n} + const Rectangle &p = r; +// CHECK8: extracted(const Rectangle &r) {\nconst Rectangle &p = r;\n} + const int &z = r.width; +// CHECK8: extracted(const Rectangle &r) {\nconst int &z = r.width;\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:139:3-139:19 -selected=%s:141:3-141:25 -selected=%s:143:3-143:25 %s | FileCheck --check-prefix=CHECK8 %s + +class ClassWithMethod { +public: + int method() CONST { return 0; } + int operator + (int x) CONST { return x; } +}; + +void nonConstMethodCallImpliesNonConstReceiver(ClassWithMethod x) { + x.method(); +// CHECK10: extracted(ClassWithMethod &x) {\nreturn x.method();\n} +// CHECK11: extracted(const ClassWithMethod &x) {\nreturn x.method();\n} + x.operator +(2); +// CHECK10: extracted(ClassWithMethod &x) {\nreturn x.operator +(2);\n} +// CHECK11: extracted(const ClassWithMethod &x) {\nreturn x.operator +(2);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:156:3-156:14 -selected=%s:159:3-159:18 %s | FileCheck --check-prefix=CHECK10 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:156:3-156:14 -selected=%s:159:3-159:18 %s -DUSECONST | FileCheck --check-prefix=CHECK11 %s + +void ignoreMethodCallsOnPointer(ClassWithMethod *x) { + x->method(); +// CHECK12: extracted(ClassWithMethod *x) {\nreturn x->method();\n} + x->operator +(2); +// CHECK12: extracted(ClassWithMethod *x) {\nreturn x->operator +(2);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:168:3-168:13 -selected=%s:170:3-170:19 %s | FileCheck --check-prefix=CHECK12 %s + +void takesRValueRef(int &&x) { } +void takesRValueStructRef(Rectangle &&r) { } + +void callTakesRValueRef(int x) { + takesRValueRef(static_cast(x)); +// CHECK13: extracted(int &x) {\ntakesRValueRef(static_cast(x));\n} + Rectangle r; + takesRValueStructRef((static_cast(r))); +// CHECK13: extracted(Rectangle &r) {\ntakesRValueStructRef((static_cast(r)));\n} + int &&y = static_cast(r.height); +// CHECK13: extracted(Rectangle &r) {\nint &&y = static_cast(r.height);\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:180:3-180:20 -selected=%s:183:3-183:54 -selected=%s:185:3-185:41 %s | FileCheck --check-prefix=CHECK13 %s + +void referencesInConditionalOperator(int x, int y) { + takesRef(x == 0 ? x : y); +// CHECK14: (int &x, int &y) {\ntakesRef(x == 0 ? x : y);\n} +// CHECK15: (int x, int y) {\ntakesRef(x == 0 ? x : y);\n} + Rectangle a, b; + takesStructRef(y == 0 ? (a) : b); +// CHECK14: (Rectangle &a, Rectangle &b, int y) {\ntakesStructRef(y == 0 ? (a) : b);\n} +// CHECK15: (const Rectangle &a, const Rectangle &b, int y) {\ntakesStructRef(y == 0 ? (a) : b);\n} + takesRef(x == 0 ? (a).width : (y == 0 ? y : b.height)); +// CHECK14: (Rectangle &a, Rectangle &b, int x, int &y) {\ntakesRef(x == 0 ? (a).width : (y == 0 ? y : b.height));\n} +// CHECK15: (const Rectangle &a, const Rectangle &b, int x, int y) {\ntakesRef(x == 0 ? (a).width : (y == 0 ? y : b.height));\n} + takesRef((x == 0 ? a : (b)).width); +// CHECK14: (Rectangle &a, Rectangle &b, int x) {\ntakesRef((x == 0 ? a : (b)).width);\n} +// CHECK15: (const Rectangle &a, const Rectangle &b, int x) {\ntakesRef((x == 0 ? a : (b)).width);\n} + takesRef(x == 0 ? y : y); +// CHECK14: (int x, int &y) {\ntakesRef(x == 0 ? y : y);\n} +// CHECK15: (int x, int y) {\ntakesRef(x == 0 ? y : y);\n} + ClassWithMethod caller1, caller2; + (x == 0 ? caller1 : caller2).method(); +// CHECK14: (ClassWithMethod &caller1, ClassWithMethod &caller2, int x) {\nreturn (x == 0 ? caller1 : caller2).method();\n} +// CHECK15: (const ClassWithMethod &caller1, const ClassWithMethod &caller2, int x) {\nreturn (x == 0 ? caller1 : caller2).method();\n} +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:192:3-192:27 -selected=%s:196:3-196:35 -selected=%s:199:3-199:57 -selected=%s:202:3-202:37 -selected=%s:205:3-205:27 -selected=%s:209:3-209:40 %s | FileCheck --check-prefix=CHECK14 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:192:3-192:27 -selected=%s:196:3-196:35 -selected=%s:199:3-199:57 -selected=%s:202:3-202:37 -selected=%s:205:3-205:27 -selected=%s:209:3-209:40 %s -DUSECONST | FileCheck --check-prefix=CHECK15 %s + +class PrivateInstanceVariablesConditionalOperatorRefs { + int x; + Rectangle r; + + void callsTakeRef(int y) { + takesRef(y == 0 ? x : r.width); + } +}; +// CHECK16: (Rectangle &r, int &x, int y) {\ntakesRef(y == 0 ? x : r.width);\n} +// RUN: clang-refactor-test perform -action extract -selected=%s:222:5-222:35 %s | FileCheck --check-prefix=CHECK16 %s + +class ReferencesInCommaOperator { + int x; + + void callsTakeRef(int y, Rectangle r) { + takesRef((x, y)); +// CHECK17: (int x, int &y) {\ntakesRef((x, y));\n} + takesRef((y, x)); +// CHECK17: (int &x, int y) {\ntakesRef((y, x));\n} + takesStructRef((takesValue(x), r)); +// CHECK17: (Rectangle &r, int x) {\ntakesStructRef((takesValue(x), r));\n} + } +}; + +// RUN: clang-refactor-test perform -action extract -selected=%s:232:5-232:21 -selected=%s:234:5-234:21 -selected=%s:236:5-236:39 %s | FileCheck --check-prefix=CHECK17 %s + +struct StaticMember { + static int staticMember; +}; + +void memberMustBeNonStaticField(StaticMember s) { + takesRef(s.staticMember); +// CHECK18: (const StaticMember &s) {\ntakesRef(s.staticMember);\n} +} +// RUN: clang-refactor-test perform -action extract -selected=%s:248:3-248:27 %s | FileCheck --check-prefix=CHECK18 %s + +class ClassWithMethod2 { +public: + ClassWithMethod member; +}; + +class ClassWithMethod; + +void nonConstMethodCallImpliesNonConstReceiver2(ClassWithMethod2 x) { + x.member.method(); +// CHECK19: (ClassWithMethod2 &x) {\nreturn x.member.method();\n} +// CHECK20: (const ClassWithMethod2 &x) {\nreturn x.member.method();\n} +} +// RUN: clang-refactor-test perform -action extract -selected=%s:261:3-261:20 %s | FileCheck --check-prefix=CHECK19 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:261:3-261:20 %s -DUSECONST | FileCheck --check-prefix=CHECK20 %s + +class PrivateInstaceVariablesCallRefsBase { + int x; +}; + +class PrivateInstanceVariablesCallRefs2: public PrivateInstaceVariablesCallRefsBase { + int y; + Rectangle r; + + void callsTakeRef() { + takesRef(this->x); + takesRef((this)->y); + takesRef(static_cast(this)->x); + takesRef((0, ((const_cast(this)->r.width)))); + } +// CHECK21: (PrivateInstanceVariablesCallRefs2 &object) {\ntakesRef(object.x);\n} +// CHECK21: (PrivateInstanceVariablesCallRefs2 &object) {\ntakesRef((object).y);\n} +// CHECK21: (PrivateInstanceVariablesCallRefs2 &object) {\ntakesRef(static_cast(&object)->x);\n} +// CHECK21: (PrivateInstanceVariablesCallRefs2 &object) {\ntakesRef((0, ((const_cast(&object)->r.width))));\n} +}; +// RUN: clang-refactor-test perform -action extract -selected=%s:277:5-277:22 -selected=%s:278:5-278:24 -selected=%s:279:5-279:74 -selected=%s:280:5-280:86 %s | FileCheck --check-prefix=CHECK21 %s diff --git a/clang/test/Refactor/Extract/extract-reference-of-captured-variable.mm b/clang/test/Refactor/Extract/extract-reference-of-captured-variable.mm new file mode 100644 index 0000000000000..9d0f9c2ba3ea9 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-reference-of-captured-variable.mm @@ -0,0 +1,50 @@ + +#ifdef USECONST +#define CONST const +#else +#define CONST +#endif + +typedef struct { + int width, height; +} Rectangle; + +@interface I + +- (int)takesRef:(CONST int &)x; ++ (int)takesRef:(CONST int &)x; +- (int)takesVal:(int)x; +- (int)takesStructRef:(CONST Rectangle &)r; + +@end + +void methodTakesRef(I *i, int x, Rectangle r) { + [i takesRef: x]; +// CHECK1: extracted(I *i, int &x) {\nreturn [i takesRef: x];\n} +// CHECK2: extracted(I *i, int x) {\nreturn [i takesRef: x];\n} + [I takesRef: x]; +// CHECK1: extracted(int &x) {\nreturn [I takesRef: x];\n} +// CHECK2: extracted(int x) {\nreturn [I takesRef: x];\n} + [i takesVal: x]; +// CHECK1: extracted(I *i, int x) {\nreturn [i takesVal: x];\n} +// CHECK2: extracted(I *i, int x) {\nreturn [i takesVal: x];\n} + [i takesStructRef: r]; +// CHECK1: extracted(I *i, Rectangle &r) {\nreturn [i takesStructRef: r];\n} +// CHECK2: extracted(I *i, const Rectangle &r) {\nreturn [i takesStructRef: r];\n} + [I takesRef: (r).width]; +// CHECK1: extracted(Rectangle &r) {\nreturn [I takesRef: (r).width];\n} +// CHECK2: extracted(const Rectangle &r) {\nreturn [I takesRef: (r).width];\n} +} + +class PrivateInstanceVariablesMethodCallRefs { + int x; + + void methodTakesRef(I *j) { + [j takesRef: x]; +// CHECK1: extracted(I *j, int &x) {\nreturn [j takesRef: x];\n} +// CHECK2: extracted(I *j, int x) {\nreturn [j takesRef: x];\n} + } +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:22:3-22:18 -selected=%s:25:3-25:18 -selected=%s:28:3-28:18 -selected=%s:31:3-31:24 -selected=%s:34:3-34:26 -selected=%s:43:5-43:20 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract -selected=%s:22:3-22:18 -selected=%s:25:3-25:18 -selected=%s:28:3-28:18 -selected=%s:31:3-31:24 -selected=%s:34:3-34:26 -selected=%s:43:5-43:20 %s -DUSECONST | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Extract/extract-statement-macros.cpp b/clang/test/Refactor/Extract/extract-statement-macros.cpp new file mode 100644 index 0000000000000..b412a34b199b5 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-statement-macros.cpp @@ -0,0 +1,47 @@ +#define INT int +#define MACRO INT y = x * x + +void extractStatementsTrimComments(int x) { + INT y = 0; + + // comment + MACRO; + + int z = 0; +} +// CHECK1: Initiated the 'extract' action at [[@LINE-4]]:3 -> [[@LINE-2]]:13 +// CHECK2: Initiated the 'extract' action at [[@LINE-8]]:3 -> [[@LINE-5]]:9 + +// RUN: clang-refactor-test initiate -action extract -selected=%s:6:1-10:12 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action extract -selected=%s:5:3-9:1 %s | FileCheck --check-prefix=CHECK2 %s + +#define BLOCK __attribute__((__blocks__(byref))) +void macroAtDeclStmt() { + // attr-begin: +1:38 + BLOCK const char *Message = "HELLO"; + int X = 123; + // attr-end: -1:13 +} +// CHECK3: Initiated the 'extract' action at [[@LINE-4]]:3 -> [[@LINE-3]]:15 +// RUN: clang-refactor-test initiate -action extract -selected=attr %s -fblocks | FileCheck --check-prefix=CHECK3 %s + +#define MUT(x) (--(x)) + +void macroExtractionEndsInMacroArgument(int x, int y) { // CHECK4: "static void extracted(int &x, int &y) {\ny = MUT(x);\n}\n\n" [[@LINE]]:1 +// CHECK5: "static int extracted(int &x) {\nreturn MUT(x);\n}\n\n" [[@LINE-1]]:1 + + // macro-arg-expr-begin: +3:7 + // macro-arg-end1-begin: +2:1 + // macro-arg-end2-begin: +1:1 + y = MUT(x); // comment + // macro-arg-end1-end: -1:25 + // macro-arg-end2-end: -2:14 + // macro-arg-expr-end: -3:13 + + // CHECK4: "extracted(x, y)" [[@LINE-5]]:3 -> [[@LINE-5]]:13 + // CHECK5: "extracted(x)" [[@LINE-6]]:7 -> [[@LINE-6]]:13 +} + +// RUN: clang-refactor-test perform -action extract -selected=macro-arg-end1 %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test perform -action extract -selected=macro-arg-end2 %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test perform -action extract -selected=macro-arg-expr %s | FileCheck --check-prefix=CHECK5 %s diff --git a/clang/test/Refactor/Extract/extract-statements.cpp b/clang/test/Refactor/Extract/extract-statements.cpp new file mode 100644 index 0000000000000..4861d1100af1c --- /dev/null +++ b/clang/test/Refactor/Extract/extract-statements.cpp @@ -0,0 +1,178 @@ + +struct Rectangle { int width, height; }; + +void extractStatement(const Rectangle &r) { + int area = r.width * r.height; +// CHECK1: "static void extracted(const Rectangle &r) {\nint area = r.width * r.height;\n}\n\n" +// CHECK1-NEXT: "extracted(r);" [[@LINE-2]]:3 -> [[@LINE-2]]:33 + if (r.width) { + int x = r.height; + } +// CHECK1: "static void extracted(const Rectangle &r) {\nif (r.width) {\n int x = r.height;\n }\n}\n\n" +// CHECK1-NEXT: "extracted(r);" [[@LINE-4]]:3 -> [[@LINE-2]]:4 + if (r.width) { + int x = r.height; + } ; // This semicolon shouldn't be extracted. +// CHECK1: "static void extracted(const Rectangle &r) {\nif (r.width) {\n int x = r.height;\n }\n}\n\n" +// CHECK1-NEXT: "extracted(r);" [[@LINE-4]]:3 -> [[@LINE-2]]:4 + do { + } while (true) ; +// CHECK1: "static void extracted() {\ndo {\n } while (true) ;\n}\n\n" +// CHECK1-NEXT: "extracted();" [[@LINE-3]]:3 -> [[@LINE-2]]:19 + do { + } while (true) /*we still want to take this semicolon*/ ; +// CHECK1: "static void extracted() {\ndo {\n } while (true) /*we still want to take this semicolon*/ ;\n}\n\n" +// CHECK1-NEXT: "extracted();" [[@LINE-3]]:3 -> [[@LINE-2]]:60 +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:5:3-5:32 -selected=%s:8:3-10:4 -selected=%s:13:3-15:4 -selected=%s:18:3-19:17 -selected=%s:22:3-23:17 %s | FileCheck --check-prefix=CHECK1 %s +; +void extractCantFindSemicolon() { + do { + } while (true) + // Add a semicolon in both the extracted and original function as we don't + // want to extract the semicolon below: + ; +// CHECK2: "static void extracted() {\ndo {\n } while (true);\n}\n\n" +// CHECK2-NEXT: "extracted();" [[@LINE-6]]:3 -> [[@LINE-5]]:17 +} +// RUN: clang-refactor-test perform -action extract -selected=%s:31:3-32:17 %s | FileCheck --check-prefix=CHECK2 %s + +void extractedStmtNoNeedForSemicolon() { + { + int x = 0; + } +// CHECK3: "static void extracted() {\n{\n int x = 0;\n }\n}\n\n" + switch (2) { + case 1: + break; + case 2: + break; + } +// CHECK3: "static void extracted() {\nswitch (2) {\n case 1:\n break;\n case 2:\n break;\n }\n}\n\n" + while (true) { + int x = 0; + } +// CHECK3: "static void extracted() {\nwhile (true) {\n int x = 0;\n }\n}\n\n" + for (int i = 0; i < 10; ++i) { + } +// CHECK3: "static void extracted() {\nfor (int i = 0; i < 10; ++i) {\n }\n}\n\n" + struct XS { + int *begin() { return 0; } + int *end() { return 0; } + }; + XS xs; + for (int i : xs) { + } +// CHECK3: "static void extracted(const XS &xs) {\nfor (int i : xs) {\n }\n}\n\n" + try { int x = 0; } + catch (const int &i) { + int y = i; + } +// CHECK3: "static void extracted() {\ntry { int x = 0; }\n catch (const int &i) {\n int y = i;\n }\n}\n\n" +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:42:3-44:4 -selected=%s:46:3-51:4 -selected=%s:53:3-55:4 -selected=%s:57:3-58:4 -selected=%s:65:3-66:4 -selected=%s:68:3-71:4 %s -std=c++11 | FileCheck --check-prefix=CHECK3 %s +; +void extractStatementRange(int x) { + x = 2; + int y = 0; + extractStatementRange(x); + if (x == 2) { + int z = 0; + } + x = 2; + +// CHECK4: "static void extracted(int x) {\nextractStatementRange(x);\n if (x == 2) {\n int z = 0;\n }\n}\n\n" [[@LINE-9]]:1 +// CHECK4-NEXT: "extracted(x);" [[@LINE-7]]:3 -> [[@LINE-4]]:4 + +// CHECK4: "static void extracted(int x) {\nint y = 0;\n extractStatementRange(x);\n}\n\n" [[@LINE-12]]:1 +// CHECK4-NEXT: "extracted(x)" [[@LINE-11]]:3 -> [[@LINE-10]]:27 + +// CHECK4: "static void extracted(int &x) {\nx = 2;\n int y = 0;\n extractStatementRange(x);\n}\n\n" [[@LINE-15]]:1 +// CHECK4-NEXT: "extracted(x)" [[@LINE-15]]:3 -> [[@LINE-13]]:27 + +// CHECK4: "static void extracted(int &x) {\nx = 2;\n int y = 0;\n}\n\n" [[@LINE-18]]:1 +// CHECK4-NEXT: "extracted(x);" [[@LINE-18]]:3 -> [[@LINE-17]]:13 +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:80:3-83:4 -selected=%s:79:3-80:6 -selected=%s:78:3-80:6 -selected=%s:78:3-79:6 %s | FileCheck --check-prefix=CHECK4 %s + +void extractedVariableUsedAndDefinedInExtractedCode(int x) { + int y = x; + if (y == 1) { + int z = 0; + } +// CHECK5: "static void extracted(int x) {\nint y = x;\n if (y == 1) {\n int z = 0;\n }\n}\n\n" +// CHECK5-NEXT: "extracted(x);" +// CHECK5: "static void extracted(int y) {\nif (y == 1) {\n int z = 0;\n }\n}\n\n" +// CHECK5-NEXT: "extracted(y);" +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:102:2-105:4 -selected=%s:103:2-105:4 %s | FileCheck --check-prefix=CHECK5 %s + +void extractAssignmentAsStatementOrExpr(int x) { + x = 2; +// CHECK6: "static void extracted(int &x) {\nx = 2;\n}\n\n" + x = x = 3; +// CHECK6: "static int extracted(int &x) {\nreturn x = 3;\n}\n\n" + (void)(x = 4); +// CHECK6: "static int extracted(int &x) {\nreturn x = 4;\n}\n\n" + if (x = 5) { + } +// CHECK6: "static int extracted(int &x) {\nreturn x = 5;\n}\n\n" + if (true) + x = 6; +// CHECK6: "static void extracted(int &x) {\nx = 6;\n}\n\n" + bool b = 2; + if (b = false) { + } +// CHECK6: "static bool extracted(bool &b) {\nreturn b = false;\n}\n\n" +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:115:3-115:8 -selected=%s:117:7-117:12 -selected=%s:119:10-119:15 -selected=%s:121:7-121:12 -selected=%s:125:5-125:10 -selected=%s:128:7-128:16 %s | FileCheck --check-prefix=CHECK6 %s + +void extractCompoundAssignmentAsStatementOrExpr(int x) { + x += 2; +// CHECK7: "static void extracted(int &x) {\nx += 2;\n}\n\n" + x = x += 3; +// CHECK7: "static int extracted(int &x) {\nreturn x += 3;\n}\n\n" + if (x *= 4) { + } +// CHECK7: "static int extracted(int &x) {\nreturn x *= 4;\n}\n\n" +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:136:3-136:9 -selected=%s:138:7-138:13 -selected=%s:140:7-140:13 %s | FileCheck --check-prefix=CHECK7 %s + +int inferReturnTypeFromReturnStatement(int x) { + if (x == 0) { + return x; + } + if (x == 1) { + return x + 1; + } + return x + 2; +} +// CHECK8: "static int extracted(int x) {\nif (x == 1) {\n return x + 1;\n }\n return x + 2;\n}\n\n" +// CHECK8: "static int extracted(int x) {\nif (x == 1) {\n return x + 1;\n }\n}\n\n" + +// RUN: clang-refactor-test perform -action extract -selected=%s:151:3-154:15 -selected=%s:151:3-153:4 %s | FileCheck --check-prefix=CHECK8 %s + +void careForNonCompoundSemicolons() { +// if-open-begin:+1:1 + if (true) + careForNonCompoundSemicolons(); +// if-open-end: -1:35 +// CHECK9: "static void extracted() {\nif (true)\n careForNonCompoundSemicolons();\n}\n\n" +// CHECK9: "extracted();" [[@LINE-4]]:3 -> [[@LINE-3]]:36 + +// for-open-begin:+1:1 + for (int i = 0; i < 10; ++i) + while (i != 0) + ; +// for-open-end: +0:1 +// CHECK9:"static void extracted() {\nfor (int i = 0; i < 10; ++i)\n while (i != 0)\n ;\n}\n\n" [[@LINE-15]]:1 +// CHECK9: "extracted();" [[@LINE-5]]:3 -> [[@LINE-3]]:8 +} + +// RUN: clang-refactor-test perform -action extract -selected=if-open -selected=for-open %s | FileCheck --check-prefix=CHECK9 %s diff --git a/clang/test/Refactor/Extract/extract-statements.m b/clang/test/Refactor/Extract/extract-statements.m new file mode 100644 index 0000000000000..db86ab84023af --- /dev/null +++ b/clang/test/Refactor/Extract/extract-statements.m @@ -0,0 +1,57 @@ +@interface NSArray ++ (id)arrayWithObjects:(const id [])objects count:(unsigned long)cnt; +@end + +void extractedStmtNoNeedForSemicolon(NSArray *array) { + for (id i in array) { + int x = 0; + } +// CHECK1: "static void extracted(NSArray *array) {\nfor (id i in array) {\n int x = 0;\n }\n}\n\n" + id lock; + @synchronized(lock) { + int x = 0; + } +// CHECK1: "static void extracted(id lock) {\n@synchronized(lock) {\n int x = 0;\n }\n}\n\n" + @autoreleasepool { + int x = 0; + } +// CHECK1: "static void extracted() {\n@autoreleasepool {\n int x = 0;\n }\n}\n\n" + @try { + int x = 0; + } @finally { + } +// CHECK1: "static void extracted() {\n@try {\n int x = 0;\n } @finally {\n }\n}\n\n" +} + +// RUN: clang-refactor-test perform -action extract -selected=%s:6:3-8:4 -selected=%s:11:3-13:4 -selected=%s:15:3-17:4 -selected=%s:19:3-22:4 %s | FileCheck --check-prefix=CHECK1 %s + +@interface I + +@end + +@implementation I + +- (int)inferReturnTypeFromReturnStatement:(int)x { + if (x == 0) { + return x; + } + if (x == 1) { + return x + 1; + } + return x + 2; +} +// CHECK2: "static int extracted(int x) {\nif (x == 1) {\n return x + 1;\n }\n return x + 2;\n}\n\n" +// CHECK2: "static int extracted(int x) {\nif (x == 1) {\n return x + 1;\n }\n}\n\n" + +// RUN: clang-refactor-test perform -action extract -selected=%s:38:3-41:15 -selected=%s:38:3-40:4 %s | FileCheck --check-prefix=CHECK2 %s + +@end + +void partiallySelectedWithImpCastCrash(I *object) { +// partially-selected-begin: +1:3 + object; +// partially-selected-end: +1:11 +// comment +// CHECK3: "static void extracted(I *object) {\nobject;\n}\n\n" +// RUN: clang-refactor-test perform -action extract -selected=partially-selected %s | FileCheck --check-prefix=CHECK3 %s +} diff --git a/clang/test/Refactor/Extract/extract-whole-source-construct.cpp b/clang/test/Refactor/Extract/extract-whole-source-construct.cpp new file mode 100644 index 0000000000000..93df9e080fba2 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-whole-source-construct.cpp @@ -0,0 +1,120 @@ +void extractEntireIfWhenSelectedBody(int x) { + if (x == 1) + { + int z = x + 2; + } + else if (x == 3) + { + int y = x + 1; + } + // CHECK1: Initiated the 'extract' action at [[@LINE-8]]:3 -> [[@LINE-1]]:4 +} + +// RUN: clang-refactor-test initiate -action extract -selected=%s:3:3-5:4 -selected=%s:7:3-9:4 -selected=%s:4:5-8:19 -selected=%s:6:12-9:4 %s | FileCheck --check-prefix=CHECK1 %s + +void extractEntireSourceConstructWhenSelectedBody(int x) { + switch (x) + { + case 0: + break; + case 1: + break; + } +// CHECK2: Initiated the 'extract' action at [[@LINE-7]]:3 -> [[@LINE-1]]:4 +// RUN: clang-refactor-test initiate -action extract -selected=%s:17:4-22:4 %s | FileCheck --check-prefix=CHECK2 %s + + for (int i = 0; i < x; ++i) + { + break; + } +// CHECK3: Initiated the 'extract' action at [[@LINE-4]]:3 -> [[@LINE-1]]:4 +// RUN: clang-refactor-test initiate -action extract -selected=%s:27:4-29:4 %s | FileCheck --check-prefix=CHECK3 %s + + while (x < 0) + { + break; + } +// CHECK4: Initiated the 'extract' action at [[@LINE-4]]:3 -> [[@LINE-1]]:4 +// RUN: clang-refactor-test initiate -action extract -selected=%s:34:4-36:4 %s | FileCheck --check-prefix=CHECK4 %s + + do + { + break; + } + while (x != 0); +// CHECK5: Initiated the 'extract' action at [[@LINE-5]]:3 -> [[@LINE-1]]:17 +// RUN: clang-refactor-test initiate -action extract -selected=%s:41:4-43:4 %s | FileCheck --check-prefix=CHECK5 %s +} + +void extractJustTheCompoundStatement() { + { + { + int x = 0; + } + } +// CHECK6: Initiated the 'extract' action at [[@LINE-4]]:5 -> [[@LINE-2]]:6 +// RUN: clang-refactor-test initiate -action extract -selected=%s:51:5-53:6 %s | FileCheck --check-prefix=CHECK6 %s +} + +void extractSwitch(int x) { + switch (x) { + extractSwitch(x - 1); + + case 0: + extractSwitch(x + 1); + break; + + // comment + case 1: + extractSwitch(x + 2); + break; + + default: + extractSwitch(x + 2); + break; + + } +// CHECK7: Initiated the 'extract' action at [[@LINE-17]]:3 -> [[@LINE-1]]:4 +// RUN: clang-refactor-test initiate -action extract -selected=%s:61:3-64:27 -selected=%s:63:5-63:11 -selected=%s:68:5-69:27 -selected=%s:72:5-72:13 %s | FileCheck --check-prefix=CHECK7 %s +} + +class AClass { + void method(); + + void extractWholeCallWhenJustMethodSelected() { + method(); + } +}; +// CHECK8: Initiated the 'extract' action at [[@LINE-3]]:5 -> [[@LINE-3]]:13 +// RUN: clang-refactor-test initiate -action extract -selected=%s:85:5-85:6 -selected=%s:85:5-85:11 %s | FileCheck --check-prefix=CHECK8 %s + +void extractWholeCallWhenJustMethodSelected() { + AClass a; + a.method(); +} +// CHECK9: Initiated the 'extract' action at [[@LINE-2]]:3 -> [[@LINE-2]]:13 +// RUN: clang-refactor-test initiate -action extract -selected=%s:93:3-93:7 -selected=%s:93:5-93:11 %s | FileCheck --check-prefix=CHECK9 %s +; +void avoidExtractingTooMuch(bool boolean) { // CHECK10: void extracted() {\nint x = 2;\n // avoid-{{.*}}-end:+1:15\n int y = x;\n}\n\n" [[@LINE]]:1 + if (boolean) { + // avoid-too-much-begin:+1:1 // CHECK10: "extracted();" [[@LINE+1]]:5 -> [[@LINE+3]]:15 + int x = 2; + // avoid-too-much-end:+1:15 + int y = x; + } else { + int z = 3; + } + + // switch-casesel-begin: +4:3 // switch-casesel-end: +4:4 // CHECK10: void extracted(bool boolean) {\nswitch ((int)boolean) {\n case 0:\n avoidExtractingTooMuch(boolean);\n avoidExtractingTooMuch(boolean);\n break;\n }\n} + // switch-case0-begin: +4:5 // switch-case0-end: +4:36 // CHECK10: void extracted(bool boolean) {\navoidExtractingTooMuch(boolean);\n} + // switch-case1-begin: +3:5 // switch-case1-end: +4:36 // CHECK10: void extracted(bool boolean) {\navoidExtractingTooMuch(boolean);\n avoidExtractingTooMuch(boolean);\n}\n\n" + switch ((int)boolean) { + case 0: + avoidExtractingTooMuch(boolean); + avoidExtractingTooMuch(boolean); + break; + } + // CHECK10: "extracted(boolean)" [[@LINE-4]]:5 -> [[@LINE-3]]:36 +} + +// RUN: clang-refactor-test perform -action extract -selected=avoid-too-much -selected=switch-casesel -selected=switch-case0 -selected=switch-case1 %s | FileCheck --check-prefix=CHECK10 %s diff --git a/clang/test/Refactor/Extract/extracted-declaration-name.mm b/clang/test/Refactor/Extract/extracted-declaration-name.mm new file mode 100644 index 0000000000000..f9d72282ffb86 --- /dev/null +++ b/clang/test/Refactor/Extract/extracted-declaration-name.mm @@ -0,0 +1,50 @@ + +int compute(int n, int x, int y) { + int sum = 0; + for (int i = 0; i < n; ++i) { +// extract-func-begin: +1:12 + sum += (x - i) * (y + i); +// extract-func-end: -1:29 + } + return sum; +} +// RUN: clang-refactor-test perform -action extract -emit-associated -selected=extract-func %s | FileCheck --check-prefix=CHECK1 %s +// CHECK1: "static int extracted(int i, int x, int y) {\nreturn (x - i) * (y + i);\n}\n\n" [[@LINE-10]]:1 -> [[@LINE-10]]:1 [Symbol extracted-decl 0 1:12 -> 1:21] +// CHECK1-NEXT: "extracted(i, x, y)" [[@LINE-7]]:12 -> [[@LINE-7]]:29 [Symbol extracted-decl-ref 0 1:1 -> 1:10] + +struct Struct { + int func(int y) { return y; } + + int compute(int x, int y); +}; + +int Struct::compute(int x, int y) { +// extract-member-func-begin: +1:10 + return x * func(y + x) + y; +// extract-member-func-end: -1:25 +} +// RUN: clang-refactor-test perform -action extract-method -emit-associated -selected=extract-member-func %s | FileCheck --check-prefix=CHECK2 %s +// CHECK2: "int extracted(int x, int y);\n\n" [[@LINE-9]]:3 -> [[@LINE-9]]:3 [Symbol extracted-decl 0 1:5 -> 1:14] +// CHECK2-NEXT: "int Struct::extracted(int x, int y) {\nreturn x * func(y + x);\n}\n\n" [[@LINE-7]]:1 -> [[@LINE-7]]:1 [Symbol extracted-decl 0 1:13 -> 1:22] +// CHECK2-NEXT: "extracted(x, y)" [[@LINE-6]]:10 -> [[@LINE-6]]:25 [Symbol extracted-decl-ref 0 1:1 -> 1:10] + +@interface I + +@property int p; + +- (void)foo:(int)x with:(int)y; + +@end + +@implementation I + +- (void)foo:(int)x with:(int)y { +// extract-selector-begin: +1:1 + int m = compute(10, self.p + y, x); +// extract-selector-end: +0:1 +} + +@end +// RUN: clang-refactor-test perform -action extract-method -emit-associated -selected=extract-selector %s | FileCheck --check-prefix=CHECK3 %s +// CHECK3: "- (void)extracted:(int)x y:(int)y {\nint m = compute(10, self.p + y, x);\n}\n\n" [[@LINE-8]]:1 -> [[@LINE-8]]:1 [Symbol extracted-decl 0 1:9 -> 1:18 1:26 -> 1:27] +// CHECK3-NEXT: "[self extracted:x y:y];" [[@LINE-7]]:3 -> [[@LINE-7]]:38 [Symbol extracted-decl-ref 0 1:7 -> 1:16 1:19 -> 1:20] diff --git a/clang/test/Refactor/Extract/return-c-bool.c b/clang/test/Refactor/Extract/return-c-bool.c new file mode 100644 index 0000000000000..0cb5a64673ca5 --- /dev/null +++ b/clang/test/Refactor/Extract/return-c-bool.c @@ -0,0 +1,28 @@ +#ifndef __cplusplus +#define bool _Bool +#define true 1 +#define false 0 +#endif +typedef struct { + bool b; +} HasBool; + +bool boolType(bool b, HasBool *s) { + bool x = b && true; + bool y = boolType(b, s); + bool z = s->b; + bool a = !b; + return (b || true); +} +// RUN: clang-refactor-test perform -action extract -selected=%s:11:12-11:21 -selected=%s:12:12-12:26 --selected=%s:13:12-13:16 --selected=%s:14:12-14:14 --selected=%s:15:10-15:21 %s | FileCheck %s +// RUN: clang-refactor-test perform -action extract -selected=%s:11:12-11:21 -selected=%s:12:12-12:26 --selected=%s:13:12-13:16 --selected=%s:14:12-14:14 --selected=%s:15:10-15:21 %s -x c++ | FileCheck %s +// CHECK: "static bool extracted +; +int boolCompareOps(int x, int y) { + bool a = x == y; + bool b = x >= y; + bool c = ((x < y)); + return 0; +} +// RUN: clang-refactor-test perform -action extract -selected=%s:22:12-22:18 -selected=%s:23:12-23:18 --selected=%s:24:12-24:21 %s | FileCheck %s +// RUN: clang-refactor-test perform -action extract -selected=%s:22:12-22:18 -selected=%s:23:12-23:18 --selected=%s:24:12-24:21 %s -x c++ | FileCheck %s diff --git a/clang/test/Refactor/Extract/return-correct-stl-type.cpp b/clang/test/Refactor/Extract/return-correct-stl-type.cpp new file mode 100644 index 0000000000000..f7b9444997106 --- /dev/null +++ b/clang/test/Refactor/Extract/return-correct-stl-type.cpp @@ -0,0 +1,51 @@ +namespace std { + +struct Traits { + typedef char char_type; +}; + +template +struct BasicString { + typedef typename TraitsType::char_type value_type; + value_type value() const; + const value_type *data() const; +}; + +template +BasicString +operator+(const BasicString &lhs, + const BasicString &rhs); +template +BasicString +operator+(const BasicString &lhs, + const char *rhs); + +template +struct BasicString; +typedef BasicString String; + +} // end namespace std + +void returnCharTypeNotUselessValueType() { +// CHECK1: "static char extracted(const std::String &x) {\nreturn x.value();\n}\n\n" [[@LINE-1]]:1 +// CHECK1: "static const char * extracted(const std::String &x) {\nreturn x.data();\n}\n\n" [[@LINE-2]]:1 + std::String x; +// return-char-begin: +1:9 + (void)x.value(); +// return-char-end: +0:1 +// return-data-begin: +1:9 + (void)x.data(); +// return-data-end: +0:1 +} // RUN: clang-refactor-test perform -action extract -selected=return-char -selected=return-data %s | FileCheck --check-prefix=CHECK1 %s + +void operatorTypeInferral() { +// CHECK2: "static std::String extracted(const std::String &x) {\nreturn x + "hello";\n}\n\n" [[@LINE-1]]:1 +// CHECK2: "static std::String extracted(const std::String &x) {\nreturn x + x;\n}\n\n" [[@LINE-2]]:1 + std::String x; +// infer-string1-begin: +1:10 + (void)(x + "hello"); +// infer-string1-end: -1:21 +// infer-string2-begin: +1:10 + (void)(x + x); +// infer-string2-end: -1:15 +} // RUN: clang-refactor-test perform -action extract -selected=infer-string1 -selected=infer-string2 %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Extract/return-objc-bool.m b/clang/test/Refactor/Extract/return-objc-bool.m new file mode 100644 index 0000000000000..109ae5be4dcbc --- /dev/null +++ b/clang/test/Refactor/Extract/return-objc-bool.m @@ -0,0 +1,89 @@ +#ifdef SIGNED +typedef signed char BOOL; +#else + +#ifndef __cplusplus +#define bool _Bool +#endif + +typedef bool BOOL; + +#endif + +#define YES __objc_yes +#define NO __objc_no + +typedef struct { + BOOL b; +} HasBool; + +// Always prefer to use BOOL in the Objective-C methods. + +@interface I +@end + +@implementation I + +- (BOOL)boolType:(BOOL)b with:(HasBool*)s { + BOOL x = b && YES; + BOOL y = [self boolType: b with: s]; + BOOL z = s->b; + BOOL a = !b; + return (b == NO); +} +// RUN: clang-refactor-test perform -action extract -selected=%s:28:12-28:20 -selected=%s:29:12-29:38 -selected=%s:30:12-30:16 -selected=%s:31:12-31:14 -selected=%s:32:10-32:19 %s | FileCheck --check-prefix=CHECKBOOL %s +// RUN: clang-refactor-test perform -action extract -selected=%s:28:12-28:20 -selected=%s:29:12-29:38 -selected=%s:30:12-30:16 -selected=%s:31:12-31:14 -selected=%s:32:10-32:19 %s -D SIGNED | FileCheck --check-prefix=CHECKBOOL %s +// RUN: clang-refactor-test perform -action extract -selected=%s:28:12-28:20 -selected=%s:29:12-29:38 -selected=%s:30:12-30:16 -selected=%s:31:12-31:14 -selected=%s:32:10-32:19 %s -x objective-c++ | FileCheck --check-prefix=CHECKBOOL %s +// RUN: clang-refactor-test perform -action extract -selected=%s:28:12-28:20 -selected=%s:29:12-29:38 -selected=%s:30:12-30:16 -selected=%s:31:12-31:14 -selected=%s:32:10-32:19 %s -x objective-c++ -D SIGNED | FileCheck --check-prefix=CHECKBOOL %s + +// CHECKBOOL: "static BOOL extracted + +#ifdef __cplusplus + +// Prefer BOOL even in Objective-C++ methods. + +- (BOOL)chooseBOOLEvenInCPlusPlus:(bool)b and:(bool)c { + bool x = b && c; + bool n = !b; +} + +#endif +// RUN: clang-refactor-test perform -action extract -selected=%s:46:12-46:18 -selected=%s:47:12-47:14 %s -x objective-c++ | FileCheck --check-prefix=CHECKBOOL %s +// RUN: clang-refactor-test perform -action extract -selected=%s:46:12-46:18 -selected=%s:47:12-47:14 %s -x objective-c++ -D SIGNED | FileCheck --check-prefix=CHECKBOOL %s + +@end + +#ifdef __cplusplus + +// In Objective-C++ functions/methods we want to pick the type based on the expression. + +BOOL boolObjCFunction(BOOL b, BOOL c) { + BOOL x = b && c; + BOOL y = boolObjCFunction(b, c); + return b; +} +// RUN: clang-refactor-test perform -action extract -selected=%s:61:12-61:18 -selected=%s:62:12-62:34 %s -x objective-c++ | FileCheck --check-prefix=CHECKBOOL %s +// RUN: clang-refactor-test perform -action extract -selected=%s:61:12-61:18 -selected=%s:62:12-62:34 %s -x objective-c++ -D SIGNED | FileCheck --check-prefix=CHECKBOOL %s + +bool boolCPlusPlusFunction(bool b, bool c) { + bool x = b && c; + bool y = boolCPlusPlusFunction(b, c); + return b; +} +// CHECKNORMAL: "static bool extracted +// RUN: clang-refactor-test perform -action extract -selected=%s:69:12-69:18 -selected=%s:70:12-70:39 %s -x objective-c++ | FileCheck --check-prefix=CHECKNORMAL %s + +class AClass { + AClass(BOOL b, BOOL c, bool d, bool e) { + BOOL x = b && c; + bool y = d && e; + } + void method(BOOL b, BOOL c, bool d, bool e) { + BOOL x = b || c; + bool y = d || e; + } +}; +// RUN: clang-refactor-test perform -action extract -selected=%s:78:14-78:20 -selected=%s:82:14-84:20 %s -x objective-c++ | FileCheck --check-prefix=CHECKBOOL %s +// RUN: clang-refactor-test perform -action extract -selected=%s:79:14-79:20 -selected=%s:83:14-83:20 %s -x objective-c++ | FileCheck --check-prefix=CHECKNORMAL %s + +#endif diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-duplicates.cpp b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-duplicates.cpp new file mode 100644 index 0000000000000..ba97e23f8716f --- /dev/null +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-duplicates.cpp @@ -0,0 +1,63 @@ + +struct AClass { + int method(); + int method2(); +}; +struct AWrapperClass { + AClass &object(int x); +}; + +void duplicatesWithParens(AWrapperClass &wrapper) { + wrapper.object(0).method(); +// CHECK1: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-1]]:3 + ((wrapper).object((0))).method(); +// CHECK2: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-1]]:4 +} + +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:11:3-20 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:13:4-25 %s | FileCheck --check-prefix=CHECK2 %s + + +void noDuplicatesWithParens(AWrapperClass &wrapper) { + wrapper.object(- 1).method(); +#ifndef DUPLICATE + wrapper.object((- 1)).method(); +#else + (wrapper).object(- 1).method(); +#endif +// CHECK3: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-6]]:3 + + wrapper.object(1 + 2).method(); +#ifndef DUPLICATE + wrapper.object((1 + 2)).method(); +#else + ((wrapper)).object(1 + 2).method(); +#endif +// CHECK4: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-6]]:3 + + wrapper.object(true ? 0 : 1).method(); +#ifndef DUPLICATE + wrapper.object((true ? 0 : 1)).method(); +#else + ((wrapper)).object(true ? (0) : (1)).method(); +#endif +// CHECK5: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-6]]:3 +} + +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:22:3-22 %s -DDUPLICATE | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:30:3-24 %s -DDUPLICATE | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:38:3-31 %s -DDUPLICATE | FileCheck --check-prefix=CHECK5 %s + +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:22:1-32 -in=%s:30:1-34 -in=%s:38:1-41 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// CHECK-NO: Failed to initiate the refactoring action! + +void noDuplicatesWhenSemanticsChange(AWrapperClass &wrapper) { + wrapper.object(0).method(); + if (true) { + AWrapperClass wrapperBase; + AWrapperClass &wrapper = wrapperBase; + wrapper.object(0).method(); + } +} + +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:55:1-30 in=%s:59:1-32 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-duplicates.m b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-duplicates.m new file mode 100644 index 0000000000000..bafc192cbacb5 --- /dev/null +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-duplicates.m @@ -0,0 +1,32 @@ + +@interface Object + +- (int)instanceMethod; + +@end + +@interface Wrapper + +- (Object *)returnsObject:(int)arg; + +- (Object *)classMethodReturnsObject; ++ (Object *)classMethodReturnsObject; + +@end + +void differentWrapperVariables(Wrapper *wrapper) { + [[wrapper returnsObject: 42] instanceMethod]; + Wrapper *copyWrapper = wrapper; + if (wrapper) { + Wrapper *wrapper = copyWrapper; + [[wrapper returnsObject: 42] prop]; + } + [[Wrapper classMethodReturnsObject] instanceMethod]; + if (wrapper) { + __auto_type Wrapper = wrapper; + [[Wrapper classMethodReturnsObject] instanceMethod]; + } +} + +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:18:1-48 -in=%s:24:1-55 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// CHECK-NO: Failed to initiate the refactoring action! diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.cpp b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.cpp new file mode 100644 index 0000000000000..de8b1d80ac886 --- /dev/null +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.cpp @@ -0,0 +1,109 @@ + +struct AClass { + int method(); + int method2(); +}; +struct AWrapperClass { + AClass &object(); +}; + +void takesClass(AWrapperClass &wrapper) { + wrapper.object().method(); + wrapper.object().method(); + wrapper.object().method2(); +} +// CHECK1: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-4]]:3 +// CHECK2: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-4]]:3 +// CHECK3: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-4]]:3 + +// Suggest extracting 'wrapper.object()' +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:11:3-19 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:12:3-19 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:13:3-19 %s | FileCheck --check-prefix=CHECK3 %s + +// CHECK-NO: Failed to initiate the refactoring action! + +// Avoid suggesting extraction of 'wrapper.object().method2()' +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:13:20-30 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test list-actions -at=%s:11:11 %s | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Extract Repeated Expression + +AClass &returnsReference(int x); +AClass &returnsAndTakesFunctionPointer(AClass& (*)(int)); + +void checkReferenceCall() { + returnsReference(0).method(); + returnsReference(0).method2(); + returnsAndTakesFunctionPointer(returnsReference).method(); + returnsAndTakesFunctionPointer(returnsReference).method2(); +} +// CHECK4: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-5]]:3 +// CHECK5: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-4]]:3 +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:36:3-22 %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:38:3-51 %s | FileCheck --check-prefix=CHECK5 %s +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:36:23-32 -in=%s:37:23-32 -in=%s:38:52-61 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +struct WithFields { + int x, y; +}; +struct WithFieldsOperators { + WithFields *operator ->(); + WithFields &operator ()(); + + const WithFields &operator [](int x) const; + WithFields &operator [](int x); +}; + +void checkOperatorCalls(WithFieldsOperators &op, int id) { + op[id].x; + op[id].y; +// CHECK6: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:3 + op().x; + op().x; +// CHECK7: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:3 + op->x; + op->x; +} +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:59:3-9 %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:62:3-7 %s | FileCheck --check-prefix=CHECK7 %s +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:59:10-12 -in=%s:62:8-10 -in=%s:65:3-9 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +struct AWrapperClass2 { + AClass *object() const; +}; + +void checkPointerType(AWrapperClass *object, AWrapperClass2 *object2) { + object->object().method(); + if (object) { + object->object().method2(); + } +// CHECK8: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-4]]:3 + object2->object()->method(); + int m = object2->object()->method2(); +// CHECK9: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:3 +} +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:77:3-19 %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:82:3-20 %s | FileCheck --check-prefix=CHECK9 %s + +struct ConstVsNonConst { + int field; + void constMethod() const; + void method(); +}; + +struct ConstVsNonConstWrapper { + const ConstVsNonConst &object() const; + ConstVsNonConst &object(); +}; + +void checkFoo(ConstVsNonConstWrapper &object) { + object.object().constMethod(); + object.object().method(); +} +// CHECK10: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-3]]:3 +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -at=%s:101:3 %s | FileCheck --check-prefix=CHECK10 %s + +// Check that the action can be initiate using a selection: +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -selected=%s:11:3-11:19 -selected=%s:11:15-11:19 -selected=%s:11:3-11:17 -selected=%s:11:15-11:18 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -selected=%s:11:3-11:22 -selected=%s:11:1-13:30 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.m b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.m new file mode 100644 index 0000000000000..1cd28efa13c56 --- /dev/null +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.m @@ -0,0 +1,94 @@ + +@interface Base + +@end + +@interface Object: Base { + int ivar; +} + +- (int)instanceMethod; + +@property int prop; +@property void (^block)(); + +@end + +@interface Wrapper + +- (Object *)returnsObject:(int)arg; + ++ (Object *)classMethodReturnsObject; + +@property(class) Object *classObject; + +@property Object *object; + +@end + +void test(Wrapper *wrapper) { + [[wrapper returnsObject: 42] instanceMethod]; + [[wrapper returnsObject: 42] prop]; +// CHECK1: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:4 +// CHECK2: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:4 + + wrapper.object.prop; + [wrapper.object instanceMethod]; +// CHECK3: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:3 +// CHECK4: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:4 + + [[wrapper object] block]; + [[wrapper object] instanceMethod]; +// CHECK5: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:4 +// CHECK6: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:4 + + [[Wrapper classMethodReturnsObject] instanceMethod]; + [[Wrapper classMethodReturnsObject] prop]; +// CHECK7: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:4 +// CHECK8: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:4 + + Wrapper.classObject.prop; + if (1) + [Wrapper.classObject instanceMethod]; +// CHECK9: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-3]]:3 +// CHECK10: Initiated the 'extract-repeated-expr-into-var' action at [[@LINE-2]]:6 +} + +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:30:4-31 %s -fblocks | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:31:4-31 %s -fblocks | FileCheck --check-prefix=CHECK2 %s +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:30:1-3 -in=%s:30:32-48 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:35:3-17 %s -fblocks | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:36:4-18 %s -fblocks | FileCheck --check-prefix=CHECK4 %s +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:35:18-23 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:40:4-20 %s -fblocks | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:41:4-20 %s -fblocks | FileCheck --check-prefix=CHECK6 %s +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:40:21-28 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:45:4-38 %s -fblocks | FileCheck --check-prefix=CHECK7 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:46:4-38 %s -fblocks | FileCheck --check-prefix=CHECK8 %s +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:45:39-55 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:50:3-22 %s -fblocks | FileCheck --check-prefix=CHECK9 %s +// RUN: clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:52:6-25 %s -fblocks | FileCheck --check-prefix=CHECK10 %s +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:50:23-28 -in=%s:51:1-9 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// CHECK-NO: Failed to initiate the refactoring action! + +void testInvalidMethod(Wrapper *ref) { + if (2) + [[ref classObject] instanceMethod]; + [ref classObject].block(); +} +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -in=%s:81:6-23 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +@interface ImplicitPropertyWithoutGetter +- (void) setValue: (int) value; +@end +void implicitPropertyWithoutGetter(ImplicitPropertyWithoutGetter *x) { + x.value = 0; + x.value = 1; +} + +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -at=%s:90:3 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.cpp b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.cpp new file mode 100644 index 0000000000000..4d8c40099801c --- /dev/null +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.cpp @@ -0,0 +1,100 @@ + +struct AClass { + void constMethod() const; + void method(); +}; + +struct AWrapper { + const AClass &object(int x) const; + AClass &object(int x); +}; + +void takesClass(AWrapper &ref) { + int x = 0; + ref.object(x).constMethod(); + int y = 0; + ref.object(x).method(); +} +// CHECK1: "AClass &object = ref.object(x);\nobject" [[@LINE-4]]:3 -> [[@LINE-4]]:16 + +// CHECK1-NEXT: "object" [[@LINE-4]]:3 -> [[@LINE-4]]:16 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:14:3 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:16:3 %s | FileCheck --check-prefix=CHECK1 %s + +void variableNameSuggested(AWrapper &object) { +#ifdef IN_COMPOUND + { +#endif + object.object(21).constMethod(); + object.object(21).method(); +#ifdef IN_COMPOUND + } +#endif +} +// CHECK2: "AClass &object = object.object(21);\nobject" [[@LINE-6]]:3 -> [[@LINE-6]]:20 + +// CHECK2-NEXT: "object" [[@LINE-7]]:3 -> [[@LINE-7]]:20 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:29:3 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:29:3 %s -D IN_COMPOUND | FileCheck --check-prefix=CHECK2 %s + +void takesClass2(AWrapper &ref) { + int x = 0; + if (x) + ref.object(x).constMethod(); + ref.object(x).method(); +} +// CHECK3: "AClass &object = ref.object(x);\n" [[@LINE-4]]:3 -> [[@LINE-4]]:3 +// CHECK3-NEXT: "object" [[@LINE-4]]:5 -> [[@LINE-4]]:18 +// CHECK3-NEXT: "object" [[@LINE-4]]:3 -> [[@LINE-4]]:16 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:45:5 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:46:3 %s | FileCheck --check-prefix=CHECK3 %s + +void takesClass4(AWrapper &ref) { + int x = 0; + if (x) { + ref.object(x).constMethod(); + ref.object(x).method(); + } +} +// CHECK4: "AClass &object = ref.object(x);\nobject" [[@LINE-4]]:5 -> [[@LINE-4]]:18 + +// CHECK4-NEXT: "object" [[@LINE-5]]:5 -> [[@LINE-5]]:18 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:58:5 %s | FileCheck --check-prefix=CHECK4 %s + +void insertIntoCommonCompound1(AWrapper &ref) { +#ifdef EMBED + int x = 0; + while (true) { +#endif + int x = 0; + if (x) { + if (true) { + int y = x; + ref.object(x).constMethod(); + } + } else { + ref.object(x).method(); + } +// CHECK5: "AClass &object = ref.object(x);\n" [[@LINE-8]]:3 -> [[@LINE-8]]:3 +// CHECK5-NEXT: "object" [[@LINE-6]]:7 -> [[@LINE-6]]:20 +// CHECK5-NEXT: "object" [[@LINE-4]]:5 -> [[@LINE-4]]:18 +#ifdef EMBED + } +#endif +} +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:77:7 %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:80:5 %s -DEMBED | FileCheck --check-prefix=CHECK5 %s + +void checkFirstStmtInCompoundPlacement(AWrapper &ref) { + while (true) { + ref.object(20); + ref.object(20).method(); +// CHECK6: "AClass &object = ref.object(20);\nobject" [[@LINE-2]]:5 -> [[@LINE-2]]:19 + } +} + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:94:5 %s | FileCheck --check-prefix=CHECK6 %s diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.m b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.m new file mode 100644 index 0000000000000..723438d59345b --- /dev/null +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.m @@ -0,0 +1,80 @@ + +@interface Object { + int ivar; +} + +- (int)instanceMethod; + +@property int prop; +@property void (^block)(); + +@end + +@interface Wrapper + +- (Object *)returnsObject:(int)arg; + ++ (Object *)classMethodReturnsObject; + +@property(class) Object *classObject; + +@property Object *object; + +@end + +void takesClass(Wrapper *ref) { + int x = 0; + [[ref returnsObject: x] instanceMethod]; + int y = x; + [ref returnsObject: x].prop; +} +// CHECK1: "Object *duplicate = [ref returnsObject:x];\n" [[@LINE-4]]:3 -> [[@LINE-4]]:3 +// CHECK1-NEXT: "duplicate" [[@LINE-5]]:4 -> [[@LINE-5]]:26 +// CHECK1-NEXT: "duplicate" [[@LINE-4]]:3 -> [[@LINE-4]]:25 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:27:4 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:29:3 %s | FileCheck --check-prefix=CHECK1 %s + +void takesClass2(Wrapper *ref) { + if (2) + [[ref object] instanceMethod]; + [ref object].block(); +} +// CHECK2: "Object *object = [ref object];\n" [[@LINE-4]]:3 -> [[@LINE-4]]:3 +// CHECK2-NEXT: "object" [[@LINE-4]]:6 -> [[@LINE-4]]:18 +// CHECK2-NEXT: "object" [[@LINE-4]]:3 -> [[@LINE-4]]:15 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:40:6 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:41:3 %s | FileCheck --check-prefix=CHECK2 %s + +void takesClass3(Wrapper *ref) { + if (ref) { + [ref.object instanceMethod]; + ref.object.block(); + } +} +// CHECK3: "Object *object = ref.object;\n" [[@LINE-4]]:5 -> [[@LINE-4]]:5 +// CHECK3-NEXT: "object" [[@LINE-5]]:6 -> [[@LINE-5]]:16 +// CHECK3-NEXT: "object" [[@LINE-5]]:5 -> [[@LINE-5]]:15 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:52:6 %s | FileCheck --check-prefix=CHECK3 %s + +void worksOnClass() { + [Wrapper classMethodReturnsObject]->ivar = 0; + [[Wrapper classMethodReturnsObject] instanceMethod]; +} +// CHECK4: "Object *classMethodReturnsObject = [Wrapper classMethodReturnsObject];\nclassMethodReturnsObject" [[@LINE-3]]:3 -> [[@LINE-3]]:37 + +// CHECK4-NEXT: "classMethodReturnsObject" [[@LINE-4]]:4 -> [[@LINE-4]]:38 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:63:4 %s | FileCheck --check-prefix=CHECK4 %s + +void worksOnClassProperty() { + Wrapper.classObject->ivar = 0; + Wrapper.classObject.prop = 2; +} +// CHECK5: "Object *classObject = Wrapper.classObject;\nclassObject" [[@LINE-3]]:3 -> [[@LINE-3]]:22 + +// CHECK5-NEXT: "classObject" [[@LINE-4]]:3 -> [[@LINE-4]]:22 + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:73:3 %s | FileCheck --check-prefix=CHECK5 %s diff --git a/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-forward-decl.c b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-forward-decl.c new file mode 100644 index 0000000000000..dd63951819cc1 --- /dev/null +++ b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-forward-decl.c @@ -0,0 +1,26 @@ +#ifndef AFTER +enum ForwardEnumDecl; +#endif + +enum ForwardEnumDecl { + A, B +}; + +#ifdef AFTER +enum ForwardEnumDecl; +#endif + +void dontInitiateOnIncompleteEnum(enum ForwardEnumDecl e) { + switch (e) { + } +// CHECK: "case A:\n<#code#>\nbreak;\ncase B:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 + + switch (e) { + case A: + break; + } +// CHECK: "case B:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:14:3 -at=%s:18:3 %s | FileCheck %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:14:3 -at=%s:18:3 %s -D AFTER | FileCheck %s diff --git a/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-initiate.cpp b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-initiate.cpp new file mode 100644 index 0000000000000..3d8c1a777f84a --- /dev/null +++ b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-initiate.cpp @@ -0,0 +1,150 @@ +enum Color { + Black, + Blue, + White, + Gold +}; + +void initiate(Color c, int i) { + switch (c) { + case Black: + break; + } +// CHECK1: Initiated the 'fill-in-enum-switch-cases' action at [[@LINE-4]]:3 + + switch (c) { + } +// CHECK2: Initiated the 'fill-in-enum-switch-cases' action at [[@LINE-2]]:3 +} + +// RUN: clang-refactor-test list-actions -at=%s:9:3 %s | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Add Missing Switch Cases + +// Ensure the the action can be initiated around a switch: + +// RUN: clang-refactor-test initiate -action fill-in-enum-switch-cases -in=%s:9:3-15 -in=%s:10:1-14 -in=%s:11:1-11 -in=%s:12:1-3 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action fill-in-enum-switch-cases -in=%s:15:3-15 -in=%s:16:1-4 %s | FileCheck --check-prefix=CHECK2 %s + +// Ensure that the action can't be initiated in other places: + +// RUN: not clang-refactor-test initiate -action fill-in-enum-switch-cases -in=%s:8:1-32 -in=%s:9:1-2 -in=%s:13:1-77 -in=%s:15:1-2 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// CHECK-NO: Failed to initiate the refactoring action + +void dontInitiate(Color c, int i) { + switch (c) { + case Black: + break; + case Blue: + break; + case White: + break; + case Gold: + break; + } + + switch (i) { + case 0: + break; + } + + switch ((int)c) { + case 0: + break; + } +} + +// Ensure that the action can't be initiated on switches that have all cases or +// that don't work with an enum. + +// RUN: not clang-refactor-test initiate -action fill-in-enum-switch-cases -in=%s:34:3-15 %s 2>&1 | FileCheck --check-prefix=CHECK-ALL-COVERED %s +// CHECK-ALL-COVERED: Failed to initiate the refactoring action (All enum cases are already covered)! +// RUN: not clang-refactor-test initiate -action fill-in-enum-switch-cases -in=%s:45:3-15 -in=%s:50:3-20 %s 2>&1 | FileCheck --check-prefix=CHECK-NOT-ENUM %s +// CHECK-NOT-ENUM: Failed to initiate the refactoring action (The switch doesn't operate on an enum)! + +void initiateWithDefault(Color c, int i) { + switch (c) { + case Black: + break; + default: + break; + } +// CHECK3: Initiated the 'fill-in-enum-switch-cases' action at [[@LINE-6]]:3 + + switch (c) { + default: + break; + } +// CHECK4: Initiated the 'fill-in-enum-switch-cases' action at [[@LINE-4]]:3 +} + +// RUN: clang-refactor-test initiate -action fill-in-enum-switch-cases -at=%s:65:3 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action fill-in-enum-switch-cases -at=%s:73:3 %s | FileCheck --check-prefix=CHECK4 %s + +enum class Shape { + Rectangle, + Circle, + Octagon +}; + +typedef enum { + Anon1, + Anon2 +} AnonymousEnum; + +void initiateEnumClass(Shape shape, AnonymousEnum anon) { + switch (shape) { + } +// CHECK5: Initiated the 'fill-in-enum-switch-cases' action at [[@LINE-2]]:3 + switch (anon) { + } +// CHECK6: Initiated the 'fill-in-enum-switch-cases' action at [[@LINE-2]]:3 +} + +// RUN: clang-refactor-test initiate -action fill-in-enum-switch-cases -at=%s:95:3 %s -std=c++11 | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test initiate -action fill-in-enum-switch-cases -at=%s:98:3 %s | FileCheck --check-prefix=CHECK6 %s + +// Ensure that the operation can be initiated from a selection: + +// RUN: clang-refactor-test initiate -action fill-in-enum-switch-cases -selected=%s:9:3-12:4 -selected=%s:9:15-12:3 -selected=%s:9:15-12:3 -selected=%s:10:3-11:10 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action fill-in-enum-switch-cases -selected=%s:15:3-16:4 %s | FileCheck --check-prefix=CHECK2 %s + +void dontInitiateSelectedBody(Shape shape) { + switch (shape) { + case Shape::Rectangle: { + break; + } + case Shape::Circle: + break; + } +} + +// RUN: not clang-refactor-test initiate -action fill-in-enum-switch-cases -selected=%s:114:5-114:11 -selected=%s:117:5-117:10 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +enum IncompleteEnum : int; +enum class IncompleteClassEnum : short; +enum class IncompleteClassEnum2; +void dontInitiateOnIncompleteEnum(IncompleteEnum e1, IncompleteClassEnum e2, IncompleteClassEnum2 e3) { + switch (e1) { + } + switch (e1) { + case 0: + break; + } + switch (e2) { + } + switch (e2) { + case (IncompleteClassEnum)0: + break; + } + switch (e3) { + } +} + +// RUN: not clang-refactor-test initiate -action fill-in-enum-switch-cases -at=%s:127:3 -at=%s:129:3 -at=%s:133:3 -at=%s:135:3 -at=%s:139:3 %s -std=c++11 2>&1 | FileCheck --check-prefix=CHECK-NOT-COMPLETE %s +// CHECK-NOT-COMPLETE: Failed to initiate the refactoring action (The enum type is incomplete)! + +void initiateWhenSelectionIsPartial() { + int partiallySelected = 0; +} +int global = 0; +// RUN: not clang-refactor-test initiate -action fill-in-enum-switch-cases -selected=%s:147:1-150:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-neatly-ordered.cpp b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-neatly-ordered.cpp new file mode 100644 index 0000000000000..97118ec2e9e69 --- /dev/null +++ b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-neatly-ordered.cpp @@ -0,0 +1,157 @@ + +enum Color { + Black, + Blue, + White, + Gold +}; + +void placeBeforeDefault(Color c) { + switch (c) { + case Black: + break; + case Blue: + break; + default: + break; + } +// CHECK1: "case White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 + + switch (c) { + default: + break; + } +// CHECK1: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:10:3 -at=%s:20:3 %s | FileCheck --check-prefix=CHECK1 %s + +void dontPlaceBeforeDefault(Color c) { + switch (c) { + default: + break; + case Black: + break; + case Blue: + break; + } +// CHECK2: "case White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 + + switch (c) { + case Black: + break; + default: + break; + case Blue: + break; + } +// CHECK2: "case White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:30:3 -at=%s:40:3 %s | FileCheck --check-prefix=CHECK2 %s + +void insertAtProperPlaces(Color c) { + switch (c) { + case Black: + break; + case White: + break; +#ifdef USEDEFAULT + default: + break; +#endif + } +// CHECK3: "case Blue:\n<#code#>\nbreak;\n" [[@LINE-7]]:3 -> [[@LINE-7]]:3 +// CHECK3-NEXT: "case Gold:\n<#code#>\nbreak;\n" [[@LINE-2]]:3 -> [[@LINE-2]]:3 +// CHECK4: "case Blue:\n<#code#>\nbreak;\n" [[@LINE-9]]:3 -> [[@LINE-9]]:3 +// CHECK4-NEXT: "case Gold:\n<#code#>\nbreak;\n" [[@LINE-7]]:3 -> [[@LINE-7]]:3 + + switch (c) { + case White: + break; +#ifdef USEDEFAULT + default: + break; +#endif + } +// CHECK3: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\n" [[@LINE-7]]:3 -> [[@LINE-7]]:3 +// CHECK3-NEXT: "case Gold:\n<#code#>\nbreak;\n" [[@LINE-2]]:3 -> [[@LINE-2]]:3 +// CHECK4: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\n" [[@LINE-9]]:3 -> [[@LINE-9]]:3 +// CHECK4-NEXT: "case Gold:\n<#code#>\nbreak;\n" [[@LINE-7]]:3 -> [[@LINE-7]]:3 + + switch (c) { + case Gold: + break; +#ifdef USEDEFAULT + default: + break; +#endif + } +// CHECK3: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\n" [[@LINE-7]]:3 -> [[@LINE-7]]:3 +// CHECK4: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\n" [[@LINE-8]]:3 -> [[@LINE-8]]:3 + + switch (c) { + case Blue: + break; +#ifdef USEDEFAULT + default: + break; +#endif + } +// CHECK3: "case Black:\n<#code#>\nbreak;\n" [[@LINE-7]]:3 -> [[@LINE-7]]:3 +// CHECK3-NEXT: "case White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-2]]:3 -> [[@LINE-2]]:3 +// CHECK4: "case Black:\n<#code#>\nbreak;\n" [[@LINE-9]]:3 -> [[@LINE-9]]:3 +// CHECK4-NEXT: "case White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-7]]:3 -> [[@LINE-7]]:3 + + switch (c) { + case White: + break; + case Gold: + break; +#ifdef USEDEFAULT + default: + break; +#endif + } +// CHECK3: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\n" [[@LINE-9]]:3 -> [[@LINE-9]]:3 +// CHECK4: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\n" [[@LINE-10]]:3 -> [[@LINE-10]]:3 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:54:3 -at=%s:69:3 -at=%s:82:3 -at=%s:93:3 -at=%s:106:3 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:54:3 -at=%s:69:3 -at=%s:82:3 -at=%s:93:3 -at=%s:106:3 %s -D USEDEFAULT | FileCheck --check-prefix=CHECK4 %s + +void insertAtEndIfOrderingIsUncertain(Color c) { + switch (c) { + case Gold: + break; + case White: + break; + } +// CHECK5: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 + + switch (c) { + case Blue: + break; + case Black: + break; + } +// CHECK5: "case White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 + + switch (c) { + case White: + break; + case Black: + break; + } +// CHECK5: "case Blue:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 + + switch (c) { + case Gold: + break; + case Blue: + break; + } +// CHECK5: "case Black:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:124:3 -at=%s:132:3 -at=%s:140:3 -at=%s:148:3 %s | FileCheck --check-prefix=CHECK5 %s diff --git a/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-opaque-decl.cpp b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-opaque-decl.cpp new file mode 100644 index 0000000000000..c1a4040c43b4a --- /dev/null +++ b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-opaque-decl.cpp @@ -0,0 +1,33 @@ +enum IncompleteEnum : int; + +enum IncompleteEnum : int { + A, B +}; + +enum class IncompleteClassEnum : short; + +enum class IncompleteClassEnum : short { + B, C +}; + +enum class IncompleteClassEnum2; + +enum class IncompleteClassEnum2 { + D, E +}; + +void dontInitiateOnIncompleteEnum(IncompleteEnum e1, IncompleteClassEnum e2, IncompleteClassEnum2 e3) { + switch (e1) { + } +// CHECK: "case A:\n<#code#>\nbreak;\ncase B:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 + + switch (e2) { + } +// CHECK: "case IncompleteClassEnum::B:\n<#code#>\nbreak;\ncase IncompleteClassEnum::C:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 + + switch (e3) { + } +// CHECK: "case IncompleteClassEnum2::D:\n<#code#>\nbreak;\ncase IncompleteClassEnum2::E:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:20:3 -at=%s:24:3 -at=%s:28:3 %s -std=c++11 | FileCheck %s diff --git a/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-perform.cpp b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-perform.cpp new file mode 100644 index 0000000000000..76ab1c2e0a547 --- /dev/null +++ b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-perform.cpp @@ -0,0 +1,80 @@ +#ifdef NESTEDANON +namespace { +#endif +#ifdef NESTED1 +namespace foo { +struct Struct { +#endif + +#ifdef ENUMCLASS +enum class Color { +#else +enum Color { +#endif + Black, + Blue, + White, + Gold +}; + +#ifdef NESTED1 +#define PREFIX foo::Struct:: +#else +#define PREFIX +#endif + +#ifdef ENUMCLASS +#define CASE(x) PREFIX Color::x +#else +#define CASE(x) PREFIX x +#endif + +#ifdef NESTED1 +} +#ifndef NESTED1NS +} +#endif +#endif +#ifdef NESTEDANON +} +#endif + +void perform1(PREFIX Color c) { + switch (c) { + case CASE(Black): + break; + } +// CHECK1: "case Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 +// CHECK2: "case Color::Blue:\n<#code#>\nbreak;\ncase Color::White:\n<#code#>\nbreak;\ncase Color::Gold:\n<#code#>\nbreak;\n" [[@LINE-2]]:3 -> [[@LINE-2]]:3 +// CHECK3: "case foo::Struct::Blue:\n<#code#>\nbreak;\ncase foo::Struct::White:\n<#code#>\nbreak;\ncase foo::Struct::Gold:\n<#code#>\nbreak;\n" [[@LINE-3]]:3 -> [[@LINE-3]]:3 +// CHECK4: "case foo::Struct::Color::Blue:\n<#code#>\nbreak;\ncase foo::Struct::Color::White:\n<#code#>\nbreak;\ncase foo::Struct::Color::Gold:\n<#code#>\nbreak;\n" [[@LINE-4]]:3 -> [[@LINE-4]]:3 +// CHECK5: "case Struct::Blue:\n<#code#>\nbreak;\ncase Struct::White:\n<#code#>\nbreak;\ncase Struct::Gold:\n<#code#>\nbreak;\n" [[@LINE-5]]:3 -> [[@LINE-5]]:3 +// CHECK6: "case Struct::Color::Blue:\n<#code#>\nbreak;\ncase Struct::Color::White:\n<#code#>\nbreak;\ncase Struct::Color::Gold:\n<#code#>\nbreak;\n" [[@LINE-6]]:3 -> [[@LINE-6]]:3 + + switch (c) { + case CASE(Black): + break; + case (Color)1: // Blue + break; + } +// CHECK1: "case White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 +// CHECK2: "case Color::White:\n<#code#>\nbreak;\ncase Color::Gold:\n<#code#>\nbreak;\n" [[@LINE-2]]:3 -> [[@LINE-2]]:3 + + switch (c) { + } +// CHECK1: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 +// CHECK2: "case Color::Black:\n<#code#>\nbreak;\ncase Color::Blue:\n<#code#>\nbreak;\ncase Color::White:\n<#code#>\nbreak;\ncase Color::Gold:\n<#code#>\nbreak;\n" [[@LINE-2]]:3 -> [[@LINE-2]]:3 +} + +#ifdef NESTED1NS +} +#endif + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 -at=%s:54:3 -at=%s:63:3 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 -at=%s:54:3 -at=%s:63:3 %s -std=c++11 -D ENUMCLASS | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 %s -std=c++11 -D NESTED1 | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 %s -std=c++11 -D NESTED1 -D ENUMCLASS | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 %s -std=c++11 -D NESTED1 -D NESTED1NS | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 %s -std=c++11 -D NESTED1 -D NESTED1NS -D ENUMCLASS | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 -at=%s:54:3 -at=%s:63:3 %s -std=c++11 -D NESTEDANON | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 -at=%s:54:3 -at=%s:63:3 %s -std=c++11 -D NESTEDANON -D ENUMCLASS | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-wrap-in-compound.cpp b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-wrap-in-compound.cpp new file mode 100644 index 0000000000000..3e1f9fe2ebd23 --- /dev/null +++ b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-wrap-in-compound.cpp @@ -0,0 +1,63 @@ + +enum Color { + Black, + Blue, + White, + Gold +}; + +// Wrap the inserted case bodies in '{' '}' when the majority of others are +// wrapped as well. +void wrapInBraces(Color c) { + switch (c) { + case Black: { + int x = 0; + break; + } + } +// CHECK1: "case Blue: {\n<#code#>\nbreak;\n}\ncase White: {\n<#code#>\nbreak;\n}\ncase Gold: {\n<#code#>\nbreak;\n}\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 + + switch (c) { + case Black: { + int x = 0; + break; + } + case Blue: { + int y = 0; + break; + } + } +// CHECK1: "case White: {\n<#code#>\nbreak;\n}\ncase Gold: {\n<#code#>\nbreak;\n}\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:12:3 -at=%s:20:3 %s | FileCheck --check-prefix=CHECK1 %s + +void dontWrapInBraces(Color c) { + switch (c) { + case Black: { + int x = 0; + break; + } + case Blue: + break; + } +// CHECK2: "case White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 + + switch (c) { + case Black: { + int x = 0; + break; + } + case Blue: + break; + case White: + break; + } +// CHECK2: "case Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 + + switch (c) { + } +// CHECK2: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:36:3 -at=%s:46:3 -at=%s:58:3 %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/FillInEnumSwitchCases/initiate-on-enum-constant.c b/clang/test/Refactor/FillInEnumSwitchCases/initiate-on-enum-constant.c new file mode 100644 index 0000000000000..891b4bfe154d2 --- /dev/null +++ b/clang/test/Refactor/FillInEnumSwitchCases/initiate-on-enum-constant.c @@ -0,0 +1,12 @@ +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=enum-c %s | FileCheck %s + +enum Enum { + Enum_a, + Enum_b, +}; +void testEnumConstantInCWithIntType() { +// enum-c: +1:1 +switch (Enum_a) { +case Enum_a: break; +} // CHECK: "case Enum_b:\n<#code#>\nbreak;\n" [[@LINE]]:1 -> [[@LINE]]:1 +} diff --git a/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-initiate.cpp b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-initiate.cpp new file mode 100644 index 0000000000000..7990cc5c55b22 --- /dev/null +++ b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-initiate.cpp @@ -0,0 +1,68 @@ +struct AbstractClass { + virtual void method() = 0; + virtual void otherMethod() { } +}; + +struct Base { + virtual void nonAbstractClassMethod() { } +}; + +struct Target : Base, AbstractClass { + int field = 0; + + union SubRecord { + }; + + void outerMethod() const; + + void innerMethod() { + int x = 0; + } +}; +// CHECK1: Initiated the 'fill-in-missing-abstract-methods' action at [[@LINE-12]]:1 + +// RUN: clang-refactor-test initiate -action fill-in-missing-abstract-methods -in=%s:10:1-end -in=%s:11:1-end -in=%s:12:1-end -in=%s:13:1-2 -in=%s:15:1-end -in=%s:16:1-2 -in=%s:17:1-end -in=%s:18:1-2 -in=%s:21:1-2 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-abstract-methods -in=%s:13:3-end -in=%s:14:1-2 -in=%s:16:3-27 -in=%s:18:3-end -in=%s:19:1-end -in=%s:20:1-3 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// CHECK-NO: Failed to initiate the refactoring action + +// RUN: clang-refactor-test list-actions -at=%s:10:1 %s | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Add Missing Abstract Class Overrides + + +void Target::outerMethod() const { +} + +// RUN: not clang-refactor-test initiate -action fill-in-missing-abstract-methods -in=%s:33:1-end -in=%s:34:1-end %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +struct FinalTarget final : AbstractClass { +}; +// CHECK2: Initiated the 'fill-in-missing-abstract-methods' action at [[@LINE-2]]:1 + +// RUN: clang-refactor-test initiate -action fill-in-missing-abstract-methods -in=%s:38:1-end -in=%s:39:1-2 %s | FileCheck --check-prefix=CHECK2 %s + +union Union { }; +// RUN: not clang-refactor-test initiate -action fill-in-missing-abstract-methods -in=%s:44:1-end %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +class NoAbstractParents : Base { }; +// RUN: not clang-refactor-test initiate -action fill-in-missing-abstract-methods -in=%s:47:1-35 %s 2>&1 | FileCheck --check-prefix=CHECK-NO-ABSTRACT-BASE %s +// CHECK-NO-ABSTRACT-BASE: Failed to initiate the refactoring action (The class has no abstract bases) + +// RUN: not clang-refactor-test initiate -action fill-in-missing-abstract-methods -in=%s:1:1-end -in=%s:6:1-end %s 2>&1 | FileCheck --check-prefix=CHECK-NO-ABSTRACT-BASE %s + +// Check selection: + +// RUN: clang-refactor-test initiate -action fill-in-missing-abstract-methods -selected=%s:10:1-21:2 -selected=%s:12:1-16:10 -selected=%s:11:1-20:2 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-abstract-methods -selected=%s:13:3-14:4 -selected=%s:16:3-16:27 -selected=%s:18:3-20:4 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +struct HasAllMethods: AbstractClass { + virtual void method() override { } +}; +// RUN: not clang-refactor-test initiate -action fill-in-missing-abstract-methods -at=%s:58:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO-MISSING-METHODS %s +// CHECK-NO-MISSING-METHODS: Failed to initiate the refactoring action (The class has no missing abstract class methods) + +// Shouldn't crash: +// forward-decl: +1:1 +struct ForwardDecl; + +// RUN: not clang-refactor-test initiate -action fill-in-missing-abstract-methods -at=forward-decl %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-no-attributes.cpp b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-no-attributes.cpp new file mode 100644 index 0000000000000..e9f4404cd59bb --- /dev/null +++ b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-no-attributes.cpp @@ -0,0 +1,10 @@ + +struct AbstractClass { + __attribute__((annotate("test"))) + virtual void pureMethod() = 0; +}; + +struct Target : AbstractClass { +}; +// CHECK1: "void pureMethod() override;\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:7:1 %s | FileCheck --check-prefix=CHECK1 %s diff --git a/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-perform.cpp b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-perform.cpp new file mode 100644 index 0000000000000..cc6e8e59933f6 --- /dev/null +++ b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-perform.cpp @@ -0,0 +1,101 @@ +template +struct Generic { T x; }; + +struct AbstractClass { + virtual void pureMethod() = 0; + virtual void aPureMethod(int (*fptr)(), Generic y) = 0; + virtual int anotherPureMethod(const int &x) const = 0; + virtual int operator + (int) const = 0; + virtual void otherMethod() { } +}; + +struct Base { + virtual void nonAbstractClassMethod() { } +}; + +struct Target : Base, AbstractClass { +}; +// CHECK1: "void pureMethod() override;\n\nvoid aPureMethod(int (*fptr)(), Generic y) override;\n\nint anotherPureMethod(const int &x) const override;\n\nint operator+(int) const override;\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 + +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:16:1 %s | FileCheck --check-prefix=CHECK1 %s + +struct SubTarget : AbstractClass { + int anotherPureMethod(const int &) const { return 0; } +#ifdef HAS_OP + int operator + (int) const override { return 2; } +#endif +}; + +struct Target2 : SubTarget, Base { +}; +// CHECK2: "void pureMethod() override;\n\nvoid aPureMethod(int (*fptr)(), Generic y) override;\n\nint operator+(int) const override;\n\n" [[@LINE-1]]:1 +// CHECK3: "void pureMethod() override;\n\nvoid aPureMethod(int (*fptr)(), Generic y) override;\n\n" [[@LINE-2]]:1 + +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:29:1 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:29:1 %s -DHAS_OP | FileCheck --check-prefix=CHECK3 %s + +struct Abstract2 { + virtual void firstMethod(int x, int y) = 0; + virtual void secondMethod(int, int) { } + virtual void thirdMethod(int a) = 0; + virtual void fourthMethod() = 0; +}; + +struct FillInGoodLocations : Base, Abstract2 { + + void secondMethod(int, int) override; // comment + + void unrelatedMethod(); + +}; +// CHECK4: "\n\nvoid firstMethod(int x, int y) override;\n\nvoid thirdMethod(int a) override;\n\nvoid fourthMethod() override;\n" [[@LINE-5]]:51 -> [[@LINE-5]]:51 +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:44:1 %s | FileCheck --check-prefix=CHECK4 %s + +struct FillInGoodLocations2 : FillInGoodLocations, AbstractClass { + + void fourthMethod() override; + + // comment + void unrelatedMethod(); + + int operator + (int) const override; +}; +// CHECK5: "\n\nvoid firstMethod(int x, int y) override;\n\nvoid thirdMethod(int a) override;\n" [[@LINE-7]]:32 -> [[@LINE-7]]:32 +// CHECK5-NEXT: "\n\nvoid pureMethod() override;\n\nvoid aPureMethod(int (*fptr)(), Generic y) override;\n\nint anotherPureMethod(const int &x) const override;\n" [[@LINE-3]]:39 -> [[@LINE-3]]:39 +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:54:1 %s | FileCheck --check-prefix=CHECK5 %s + +struct FillInGoodLocations3 : Base, AbstractClass, Abstract2 { + + // comment + void unrelatedMethod(); + + void thirdMethod(int a) override; + + void firstMethod(int x, int y) override; + +}; +// CHECK6: "\n\nvoid fourthMethod() override;\n" [[@LINE-3]]:43 -> [[@LINE-3]]:43 +// CHECK6-NEXT: "void pureMethod() override;\n\nvoid aPureMethod(int (*fptr)(), Generic y) override;\n\nint anotherPureMethod(const int &x) const override;\n\nint operator+(int) const override;\n\n" [[@LINE-2]]:1 -> [[@LINE-2]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:67:1 %s | FileCheck --check-prefix=CHECK6 %s + +struct FIllInGoodLocationsWithMacros : Abstract2 { +#define METHOD(decl) void decl override; + + METHOD(thirdMethod(int a)) + METHOD(firstMethod(int x, int y)) void foo(); +}; +// CHECK7: "\n\nvoid fourthMethod() override;\n" [[@LINE-2]]:36 -> [[@LINE-2]]:36 +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:81:1 %s | FileCheck --check-prefix=CHECK7 %s + +template +class GenericType : Abstract2 { + +}; +// CHECK8: "void firstMethod(int x, int y) override;\n\nvoid thirdMethod(int a) override;\n\nvoid fourthMethod() override;\n\n" [[@LINE-1]]:1 + +struct GenericSubType : GenericType { + +}; +// CHECK8: "void firstMethod(int x, int y) override;\n\nvoid thirdMethod(int a) override;\n\nvoid fourthMethod() override;\n\n" [[@LINE-1]]:1 + +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:91:1 -at=%s:96:1 %s | FileCheck --check-prefix=CHECK8 %s diff --git a/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-with-bodies.cpp b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-with-bodies.cpp new file mode 100644 index 0000000000000..2318a56073122 --- /dev/null +++ b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-with-bodies.cpp @@ -0,0 +1,50 @@ + +struct AbstractClass { + virtual void pureMethod() = 0; +}; + +#ifdef HAS_BODY + #define BODY { } +#else + #define BODY ; +#endif + +struct Target1 : AbstractClass { + void method1() BODY +}; +// CHECK1: "void pureMethod() override;\n\n" [[@LINE-1]]:1 +// CHECK2: "void pureMethod() override { \n <#code#>\n}\n\n" [[@LINE-2]]:1 + +struct Target2 : AbstractClass { + void method1() BODY + void method2() BODY +}; +// CHECK1: "void pureMethod() override;\n\n" [[@LINE-1]]:1 +// CHECK2: "void pureMethod() override { \n <#code#>\n}\n\n" [[@LINE-2]]:1 + +struct Target2_1 : AbstractClass { + void method1() BODY + void method2(); +}; +// CHECK1: "void pureMethod() override;\n\n" [[@LINE-1]]:1 +// CHECK2: "void pureMethod() override;\n\n" [[@LINE-2]]:1 + +struct Target3 : AbstractClass { + void method1() BODY + void method2() BODY + void method3() BODY +}; +// CHECK1: "void pureMethod() override;\n\n" [[@LINE-1]]:1 +// CHECK2: "void pureMethod() override { \n <#code#>\n}\n\n" [[@LINE-2]]:1 + +struct Target4 : AbstractClass { + void method1() BODY + void method2() BODY + void method3() BODY + void method4() BODY +}; +// CHECK1: "void pureMethod() override;\n\n" [[@LINE-1]]:1 +// CHECK2: "void pureMethod() override { \n <#code#>\n}\n\n" [[@LINE-2]]:1 + +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:12:1 -at=%s:18:1 -at=%s:25:1 -at=%s:32:1 -at=%s:40:1 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:12:1 -at=%s:18:1 -at=%s:25:1 -at=%s:32:1 -at=%s:40:1 %s -DHAS_BODY | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-class-extension.m b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-class-extension.m new file mode 100644 index 0000000000000..ae785bcdf66cc --- /dev/null +++ b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-class-extension.m @@ -0,0 +1,51 @@ +@protocol Proto + +@required +-(void)method:(int)x; + +@end + +@protocol Proto2 + +@required +- (void)method2:(int)y; + +@end + +@interface Base +@end + +// Initiate the action from extension if the @implementation is in the same TU. +@interface WithExtension: Base +@end +@interface WithExtension() +@end +@interface WithExtension() +@end +@implementation WithExtension +// CHECK1: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:21:1-27 -in=%s:22:1-5 -in=%s:23:1-36 %s | FileCheck --check-prefix=CHECK1 %s + +@interface WithoutImplementation: Base +@end +@interface WithoutImplementation() +@end +@interface WithoutImplementation() +@end +// CHECK-NO-IMPL: Failed to initiate the refactoring action (Class extension without suitable @implementation)! +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:32:1 -at=%s:34:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO-IMPL %s + +// Initiate from the implementation even when the class has no protocols, but +// its extension does. + +@interface NoProtocols: Base +@end +@interface NoProtocols() +@end +@implementation NoProtocols +@end +// CHECK2: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-2]]:1 +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:44:1 -at=%s:46:1 %s | FileCheck --check-prefix=CHECK2 %s +// CHECK-NO-EXT-FROM-INTERFACE: Failed to initiate the refactoring action! +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:42:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO-EXT-FROM-INTERFACE %s diff --git a/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-required-only.m b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-required-only.m new file mode 100644 index 0000000000000..249e273356c27 --- /dev/null +++ b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-required-only.m @@ -0,0 +1,146 @@ +@protocol Proto + +#ifndef NO_REQUIRED +@required +-(void)ofCourseItisRequired:(int)x; +#endif + +#ifndef NO_NOTHING +- (void)nothingSpecified:(int)y; +#endif + +#ifndef NO_OPTIONAL +@optional; +- (void)justOptional; +#endif + +@end + +@protocol Proto2 + +#ifndef NO_NOTHING +// Effectively a @required. +- (void)nothingSpecified2:(int)y; +#endif + +#ifndef NO_REQUIRED +@required +-(void)ofCourseItisRequired2:(int)x; +#endif + +#ifndef NO_OPTIONAL +@optional; +- (void)justOptional2; +#endif + +@end + +@interface Base +@end + +// Initiate in the @interface when the interface has missing @required +// declarations. +@interface I1 : Base +// CHECK1: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +#ifdef DEF_REQUIRED +-(void)ofCourseItisRequired:(int)x; +#endif +#ifdef DEF_NOTHING +- (void)nothingSpecified:(int)y; +#endif +#ifdef DEF_OPTIONAL +- (void)justOptional; +#endif +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:43:1 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:43:1 %s -DNO_REQUIRED -DNO_OPTIONAL | FileCheck --check-prefix=CHECK1 %s +// CHECK-NO: Failed to initiate the refactoring action (All of the @required methods are there)! +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:43:1 %s -DNO_REQUIRED -DNO_NOTHING -DNO_OPTIONAL 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:43:1 %s -DNO_REQUIRED -DNO_NOTHING 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:43:1 %s -DDEF_REQUIRED | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:43:1 %s -DDEF_NOTHING | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:43:1 %s -DDEF_OPTIONAL | FileCheck --check-prefix=CHECK1 %s + +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:43:1 %s -DDEF_REQUIRED -DDEF_NOTHING 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +@interface I1(Category) +// CHECK2: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +#ifdef DEF_REQUIRED +-(void)ofCourseItisRequired2:(int)x; +#endif +#ifdef DEF_NOTHING +- (void)nothingSpecified2:(int)y; +#endif +#ifdef DEF_OPTIONAL +- (void)justOptional2; +#endif +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:67:1 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:67:1 %s -DNO_REQUIRED -DNO_OPTIONAL | FileCheck --check-prefix=CHECK2 %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:67:1 %s -DNO_REQUIRED -DNO_NOTHING -DNO_OPTIONAL 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:67:1 %s -DNO_REQUIRED -DNO_NOTHING 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:67:1 %s -DDEF_REQUIRED | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:67:1 %s -DDEF_NOTHING | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:67:1 %s -DDEF_OPTIONAL | FileCheck --check-prefix=CHECK2 %s + +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:67:1 %s -DDEF_REQUIRED -DDEF_NOTHING 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// Initiate in the @implementatino when the implementation has missing @required +// methods. +@implementation I1 +// CHECK3: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +#ifdef IMPL_REQUIRED +-(void)ofCourseItisRequired:(int)x { } +#endif +#ifdef IMPL_NOTHING +- (void)nothingSpecified:(int)y { } +#endif +#ifdef IMPL_OPTIONAL +- (void)justOptional { } +#endif +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DNO_REQUIRED -DNO_OPTIONAL | FileCheck --check-prefix=CHECK3 %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DNO_REQUIRED -DNO_NOTHING -DNO_OPTIONAL 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DNO_REQUIRED -DNO_NOTHING 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DIMPL_REQUIRED | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DIMPL_NOTHING | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DIMPL_OPTIONAL | FileCheck --check-prefix=CHECK3 %s + +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DIMPL_REQUIRED -DIMPL_NOTHING 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DDEF_REQUIRED -DDEF_NOTHING -DDEF_OPTIONAL | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DIMPL_REQUIRED -DDEF_REQUIRED -DDEF_NOTHING | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DIMPL_NOTHING -DDEF_REQUIRED -DDEF_NOTHING | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:92:1 %s -DIMPL_OPTIONAL -DDEF_REQUIRED -DDEF_NOTHING -DDEF_OPTIONAL | FileCheck --check-prefix=CHECK3 %s + +@implementation I1 (Category) +// CHECK4: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +#ifdef IMPL_REQUIRED +-(void)ofCourseItisRequired2:(int)x { } +#endif +#ifdef IMPL_NOTHING +- (void)nothingSpecified2:(int)y { } +#endif +#ifdef IMPL_OPTIONAL +- (void)justOptional2 { } +#endif +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DNO_REQUIRED -DNO_OPTIONAL | FileCheck --check-prefix=CHECK4 %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DNO_REQUIRED -DNO_NOTHING -DNO_OPTIONAL 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DNO_REQUIRED -DNO_NOTHING 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DIMPL_REQUIRED | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DIMPL_NOTHING | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DIMPL_OPTIONAL | FileCheck --check-prefix=CHECK4 %s + +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DIMPL_REQUIRED -DIMPL_NOTHING 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DDEF_REQUIRED -DDEF_NOTHING -DDEF_OPTIONAL | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DIMPL_REQUIRED -DDEF_REQUIRED -DDEF_NOTHING | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DIMPL_NOTHING -DDEF_REQUIRED -DDEF_NOTHING | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:120:1 %s -DIMPL_OPTIONAL -DDEF_REQUIRED -DDEF_NOTHING -DDEF_OPTIONAL | FileCheck --check-prefix=CHECK4 %s diff --git a/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-when-protocoled.m b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-when-protocoled.m new file mode 100644 index 0000000000000..0957f8bd61675 --- /dev/null +++ b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate-when-protocoled.m @@ -0,0 +1,97 @@ +@protocol Proto + +@required +-(void)method:(int)x; + +@required +- (void)method2:(int)y; + +@end + +@interface Base +@end + +// Initiate when @implementation's interface has a suitable protocol. +@interface I1 : Base +@end +// CHECK1: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-2]]:1 +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:15:1 %s | FileCheck --check-prefix=CHECK1 %s + +@implementation I1 + +@end +// CHECK2: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-3]]:1 +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:20:1 %s | FileCheck --check-prefix=CHECK2 %s + +@interface I2 : I1 + +@end +// CHECK3: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-3]]:1 +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:26:1 %s | FileCheck --check-prefix=CHECK3 %s + +@implementation I2 +@end +// CHECK4: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-2]]:1 +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:32:1 %s | FileCheck --check-prefix=CHECK4 %s + +// Shouldn't initiate when the @interface is a forward declaration. +@class ForwardDecl; +// CHECK-FORWARD: Failed to initiate the refactoring action! +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:38:1-19 %s 2>&1 | FileCheck --check-prefix=CHECK-FORWARD %s + +// Shouldn't initiate when the @interface has no protocols: + +@interface I3 : Base +@end +@implementation I3 +@end + +@implementation I4 +@end + +// CHECK-CLASS-NO-PROTO: Failed to initiate the refactoring action! +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:11:1-16 -in=%s:12:1-5 -at=%s:44:1 -at=%s:46:1 -at=%s:49:1 %s 2>&1 | FileCheck --check-prefix=CHECK-CLASS-NO-PROTO %s + +@protocol Proto2 + +@required +-(int)method3; + +@end + +// Initiate when the category has a suitable protocol: +@interface I3 (Category) +// CHECK5: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:63:1 %s | FileCheck --check-prefix=CHECK5 %s + +@implementation I3 (Category) +// CHECK6: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:63:1 %s | FileCheck --check-prefix=CHECK5 %s + +@interface I1 (Category) +// CHECK7: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:63:1 %s | FileCheck --check-prefix=CHECK5 %s + +@implementation I1 (Category) +// CHECK8: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-1]]:1 +@end +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:63:1 %s | FileCheck --check-prefix=CHECK5 %s + +// Shouldn't initiate when the category has no protocols (even when the class has them): +@interface I1 (Category2) +@end + +@implementation I1 (Category2) +@end + +@interface I3 (Category2) +@end + +@implementation I3 (Category2) +@end + +// CHECK-CAT-NO-PROTO: Failed to initiate the refactoring action! +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:84:1 -at=%s:87:1 -at=%s:90:1 -at=%s:93:1 %s 2>&1 | FileCheck --check-prefix=CHECK-CAT-NO-PROTO %s diff --git a/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate.m b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate.m new file mode 100644 index 0000000000000..066d737b74f92 --- /dev/null +++ b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-initiate.m @@ -0,0 +1,100 @@ +@protocol Proto + +@required +-(void)method:(int)x; + +@end + +@interface Base +@end + +@interface I : Base +@property int p1; +@property int p2; +@end + +// Initiate the action within the @implementation +@implementation I + +@dynamic p1; +@synthesize p2 = _p2; + +- (void)anotherMethod { + int x = 0; +} + +void function(int x) { + int y = x; +} + +@end + +// RUN: clang-refactor-test list-actions -at=%s:18:1 %s | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Add Missing Protocol Requirements + +// Ensure the the action can be initiated in the @implementation / @interface: + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:11:1-27 -in=%s:12:1-18 -in=%s:13:1-18 -in=%s:14:1-5 %s | FileCheck --check-prefix=CHECKI1 %s +// CHECKI1: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-27]]:1 +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:17:1-18 -at=%s:18:1 -in=%s:19:1-13 -in=%s:20:1-22 -at=%s:21:1 -at=%s:25:1 -at=%s:29:1 -in=%s:30:1-5 %s | FileCheck --check-prefix=CHECK1 %s +// CHECK1: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-23]]:1 + +// Ensure that the action can't be initiated in other places: + +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:1:1-10 -in=%s:4:1-21 -in=%s:6:1-5 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// CHECK-NO: Failed to initiate the refactoring action + +// Ensure that the action can't be initiated in methods/functions in @implementation: + +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:22:1-24 -in=%s:23:1-13 -in=%s:24:1-2 -in=%s:26:1-23 -in=%s:27:1-13 -in=%s:28:1-2 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +@protocol P2 + +-(void)method2:(int)y; + +@end + +@interface I (Category) + +@end + +@implementation I (Category) + +- (void)anotherMethod2:(int)x { + int x = 0; +} + +void aFunction(int x) { + int y = x; +} + +@end + +// Ensure the the action can be initiated in the category @implementation: + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:57:1-29 -at=%s:58:1 -in=%s:59:1-5 %s | FileCheck --check-prefix=CHECKI2 %s +// CHECKI2: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-19]]:1 + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:61:1-29 -at=%s:62:1 -at=%s:66:1 -at=%s:70:1 -in=%s:71:1-5 %s | FileCheck --check-prefix=CHECK2 %s +// CHECK2: Initiated the 'fill-in-missing-protocol-stubs' action at [[@LINE-18]]:1 + +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=%s:60:1 -at=%s:72:1 -at=%s:73:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -in=%s:63:1-32 -in=%s:64:1-13 -in=%s:65:1-2 -in=%s:67:1-24 -in=%s:68:1-13 -in=%s:69:1-2 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + + +// Check that initiation works with selection as well: + +// RUN: clang-refactor-test initiate -action fill-in-missing-protocol-stubs -selected=%s:17:1-30:5 -selected=%s:18:1-29:1 -selected=%s:20:1-20:10 -selected=%s:17:1-23:3 -selected=%s:27:3-30:5 -selected=%s:23:3-27:3 %s | FileCheck --check-prefix=CHECK1 %s + +// Not when just one entire method is selected though! +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -selected=%s:22:1-24:2 -selected=%s:26:1-28:2 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// And not when the container is just partially selected! +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -selected=%s:15:1-30:1 -selected=%s:17:1-40:1 -selected=%s:15:1-40:1 -selected=%s:1:1-90:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +@class ForwardClass; + +// forward-class: +1:1 +@implementation ForwardClass (ForwardClassCategory) +@end +// RUN: not clang-refactor-test initiate -action fill-in-missing-protocol-stubs -at=forward-class %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-no-attributes.m b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-no-attributes.m new file mode 100644 index 0000000000000..d2f37118be6f5 --- /dev/null +++ b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-no-attributes.m @@ -0,0 +1,28 @@ +@class AClass; + +@protocol Protocol + +- (void)methodAttribute __attribute__((availability(macos, introduced=10.10))); + +- (void)parameterTypeAttribute:(AClass * _Nullable)c; + +- (void)parameterTypeAttribute2:(AClass * _Nonnull)c; + +- (void)parameterAttribute:(int)p __attribute__((annotate("test"))); + +@end + +@interface Base +@end + +@interface I1 : Base + +@end +// CHECK1: "- (void)methodAttribute;\n\n- (void)parameterAttribute:(int)p;\n\n- (void)parameterTypeAttribute2:(AClass * _Nonnull)c;\n\n- (void)parameterTypeAttribute:(AClass * _Nullable)c;\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 + +@implementation I1 + +@end +// CHECK1: "- (void)methodAttribute { \n <#code#>\n}\n\n- (void)parameterAttribute:(int)p { \n <#code#>\n}\n\n- (void)parameterTypeAttribute2:(AClass * _Nonnull)c { \n <#code#>\n}\n\n- (void)parameterTypeAttribute:(AClass * _Nullable)c { \n <#code#>\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:18:1 -at=%s:23:1 %s | FileCheck --check-prefix=CHECK1 %s + diff --git a/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-perform.m b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-perform.m new file mode 100644 index 0000000000000..900df300fba6c --- /dev/null +++ b/clang/test/Refactor/FillInMissingProtocolStubs/fill-in-protocol-stubs-perform.m @@ -0,0 +1,196 @@ +@protocol Proto1 + +- (void)requiredInstanceMethod:(int)y; + ++ (void)aRequiredInstanceMethod:(int (*)(void))function with:(Proto *)p; + +@optional; +- (void)anOptionalMethod; + +@end + +@protocol Proto2 + +- (void)a; + ++ (void)b; + +@end + +@protocol Proto3 + +- (void)otherProtocolMethod:(int (^)(id))takesBlock; + +@end + +@interface Base +@end + +@interface I1 : Base + +@end +// CHECK1: "- (void)otherProtocolMethod:(int (^)(id))takesBlock;\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:29:1 %s | FileCheck --check-prefix=CHECK1 %s + +@implementation I1 + +@end +// CHECK2: "- (void)otherProtocolMethod:(int (^)(id))takesBlock { \n <#code#>\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:35:1 %s | FileCheck --check-prefix=CHECK2 %s + +@interface I2 : I1 + +@end +// CHECK3: "+ (void)aRequiredInstanceMethod:(int (*)(void))function with:(id)p;\n\n- (void)requiredInstanceMethod:(int)y;\n\n- (void)a;\n\n+ (void)b;\n\n- (void)otherProtocolMethod:(int (^)(id))takesBlock;\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:41:1 %s | FileCheck --check-prefix=CHECK3 %s + +@implementation I2 + +@end +// CHECK4: "+ (void)aRequiredInstanceMethod:(int (*)(void))function with:(id)p { \n <#code#>\n}\n\n- (void)requiredInstanceMethod:(int)y { \n <#code#>\n}\n\n- (void)a { \n <#code#>\n}\n\n+ (void)b { \n <#code#>\n}\n\n- (void)otherProtocolMethod:(int (^)(id))takesBlock { \n <#code#>\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:47:1 %s | FileCheck --check-prefix=CHECK4 %s + +@interface I1(Category) +@end +// CHECK5: "- (void)a;\n\n+ (void)b;\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 + +@implementation I1(Category) +@end +// CHECK5-NEXT: "- (void)a { \n <#code#>\n}\n\n+ (void)b { \n <#code#>\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:53:1 -at=%s:57:1 %s | FileCheck --check-prefix=CHECK5 %s + +@interface I3 : I1 + +- (void)requiredInstanceMethod:(int)y; + ++ (void)b; + +#ifdef HAS_OTHER +- (void) otherProtocolMethod:(int (^)(id))takesBlock; +#endif + +@end +// CHECK6: "\n\n+ (void)aRequiredInstanceMethod:(int (*)(void))function with:(id)p;\n" [[@LINE-9]]:39 -> [[@LINE-9]]:39 +// CHECK6-NEXT: "\n\n- (void)a;\n" [[@LINE-8]]:11 -> [[@LINE-8]]:11 +// CHECK6-NEXT: "- (void)otherProtocolMethod:(int (^)(id))takesBlock;\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK7: "\n\n+ (void)aRequiredInstanceMethod:(int (*)(void))function with:(id)p;\n" [[@LINE-12]]:39 -> [[@LINE-12]]:39 +// CHECK7-NEXT: "\n\n- (void)a;\n" [[@LINE-11]]:11 -> [[@LINE-11]]:11 + +@implementation I3 + +- (void)requiredInstanceMethod:(int)y { +} + ++ (void)b { +} + +#ifdef HAS_OTHER +- (void) otherProtocolMethod:(int (^)(id))takesBlock { } +#endif + +@end +// CHECK6: "\n\n+ (void)aRequiredInstanceMethod:(int (*)(void))function with:(id)p { \n <#code#>\n}\n" [[@LINE-10]]:2 -> [[@LINE-10]]:2 +// CHECK6-NEXT: "\n\n- (void)a { \n <#code#>\n}\n" [[@LINE-8]]:2 -> [[@LINE-8]]:2 +// CHECK6-NEXT: "- (void)otherProtocolMethod:(int (^)(id))takesBlock { \n <#code#>\n}\n\n" [[@LINE-3]]:1 -> [[@LINE-3]]:1 +// CHECK7: "\n\n+ (void)aRequiredInstanceMethod:(int (*)(void))function with:(id)p { \n <#code#>\n}\n" [[@LINE-13]]:2 -> [[@LINE-13]]:2 +// CHECK7-NEXT: "\n\n- (void)a { \n <#code#>\n}\n" [[@LINE-11]]:2 -> [[@LINE-11]]:2 + +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:62:1 -at=%s:79:1 %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:62:1 -at=%s:79:1 %s -DHAS_OTHER | FileCheck --check-prefix=CHECK7 %s + +@protocol ProtoWith3Methods + +- (void)a; +- (void)b; +- (void)c; + +@end + +@interface I4 : Base + +#ifndef USE_MACRO +- (void)b; + +- (void)a; // comment + +#else + +#define METHOD(name) -(void)name; + +METHOD(b) +METHOD(c) - (void)d; + +#endif + +@end +// CHECK8: "\n\n- (void)c;\n" [[@LINE-12]]:22 -> [[@LINE-12]]:22 +// CHECK9: "\n\n- (void)a;\n" [[@LINE-6]]:10 -> [[@LINE-6]]:10 + +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:109:1 %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:109:1 %s -D USE_MACRO | FileCheck --check-prefix=CHECK9 %s + +@protocol NSObject + +- (void)nsObjectMethod; + +@end + +@protocol SubProto + +- (void)sub1; +- (id)sub2; + +@end + +@protocol SubProto2 + +- (void)sub11; + +@end + +@protocol SuperProto + +@optional +- (void)mySub; + +@end + +@interface HasSubProtocolMethods: Base + +@end +// CHECK10: "- (void)sub1;\n\n- (id)sub2;\n\n- (void)sub11;\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:158:1 %s | FileCheck --check-prefix=CHECK10 %s + + + +@interface SuperClassWithSomeDecls : Base + +- (void)sub1; + +@end + +@interface SubClassOfSuperClassWithSomeDecls : SuperClassWithSomeDecls + +#ifdef HAS_SUB1_OVERRIDE +- (void)sub1; +#endif +#ifdef HAS_SUB11 +- (void)sub11; +#endif + +@end +// CHECK11: "- (id)sub2;\n\n- (void)sub11;\n\n" [[@LINE-1]]:1 +// CHECK12: "\n\n- (id)sub2;\n" [[@LINE-8]]:14 +// CHECK12-NEXT: "- (void)sub11;\n\n" [[@LINE-3]]:1 +// CHECK13: "- (id)sub2;\n\n" [[@LINE-4]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:172:1 %s | FileCheck --check-prefix=CHECK11 %s +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:172:1 %s -DHAS_SUB1_OVERRIDE | FileCheck --check-prefix=CHECK12 %s +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:172:1 %s -DHAS_SUB11 | FileCheck --check-prefix=CHECK13 %s + +@implementation SubClassOfSuperClassWithSomeDecls + +@end +// CHECK14: "- (id)sub2 { \n <#code#>\n}\n\n- (void)sub11 { \n <#code#>\n}\n\n" [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:190:1 %s | FileCheck --check-prefix=CHECK14 %s +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:190:1 %s -DHAS_SUB1_OVERRIDE | FileCheck --check-prefix=CHECK14 %s +// RUN: clang-refactor-test perform -action fill-in-missing-protocol-stubs -at=%s:190:1 %s -DHAS_SUB11 | FileCheck --check-prefix=CHECK14 %s diff --git a/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-initiate.cpp b/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-initiate.cpp new file mode 100644 index 0000000000000..e8d063168f394 --- /dev/null +++ b/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-initiate.cpp @@ -0,0 +1,566 @@ +void foo(); void foobar(); + +void simpleCompoundBodyIf(int x) { + foo(); + + if (x == 2) { + int y = x; + } else if (x == 3) { + foo(); + } else { + foobar(); + } + + foobar(); +} + +// RUN: clang-refactor-test list-actions -at=%s:6:3 %s | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Convert to Switch + +// Ensure the the action can be initiated around the ifs: + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:6:3-15 -in=%s:8:3-22 -in=%s:10:3-10 %s | FileCheck --check-prefix=CHECK1 %s +// CHECK1: Initiated the 'if-switch-conversion' action at 6:3 + +// Ensure that the action can't be initiated when not around the ifs: + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:1:1-10 -in=%s:3:1-18 -in=%s:4:1-9 -in=%s:6:1-2 -in=%s:14:1-12 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// CHECK-NO: Failed to initiate the refactoring action +// CHECK-NO-NOT: Initiated the 'if-switch-conversion' action + +// Ensure that the action can't be initiated inside the ifs: +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:7:1-15 -in=%s:9:1-11 -in=%s:11:1-14 -in=%s:12:1-4 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void nestedIf(int x) { + if (x == 2) { + if (x == 3) { + foo(); + } else { + foo(); + } + } else { + foobar(); + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:35:3-15 -in=%s:35:3-10 %s | FileCheck --check-prefix=CHECK2 %s +// CHECK2: Initiated the 'if-switch-conversion' action at 35:3 + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:36:5-17 -in=%s:38:5-12 %s | FileCheck --check-prefix=CHECK3 %s +// CHECK3: Initiated the 'if-switch-conversion' action at 36:5 + + + + + +void simpleFlatBodyIfs(int x) { + if (x == 2) + foo(); + else if (x == 3) + foo(); + + else if (x == 4) foobar(); + + else foo(); + + if (x == 2) foobar(); + else + //comment + foo(); +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:57:3-14 -in=%s:59:3-19 -in=%s:62:3-19 -in=%s:64:3-7 %s | FileCheck --check-prefix=CHECK4 %s +// CHECK4: Initiated the 'if-switch-conversion' action at 57:3 + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:57:1-2 -in=%s:58:1-11 -in=%s:59:1-2 -in=%s:60:1-11 -in=%s:61:1-1 -in=%s:62:1-2 -in=%s:62:20-29 -in=%s:63:1-1 -in=%s:64:1-2 -in=%s:64:8-14 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:66:3-15 -in=%s:67:3-7 -in=%s:68:1-14 %s | FileCheck --check-prefix=CHECK5 %s +// CHECK5: Initiated the 'if-switch-conversion' action at 66:3 + +void differentLineCompoundIf(int x) { + if (x == 2) + { + foo(); + } + + else if (x == 3) + + { + foo(); + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:81:3-14 -in=%s:86:3-19 -in=%s:87:1-1 %s | FileCheck --check-prefix=CHECK6 %s +// CHECK6: Initiated the 'if-switch-conversion' action at 81:3 + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:81:1-2 -in=%s:82:1-4 -in=%s:84:1-4 -in=%s:85:1-1 -in=%s:86:1-2 -in=%s:88:1-4 -in=%s:90:1-4 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void simpleEmptyIf(int x) { + if (x == 1) ; + else if (x == 2) ; +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:99:3-14 -in=%s:100:3-19 %s | FileCheck --check-prefix=CHECK7 %s +// CHECK7: Initiated the 'if-switch-conversion' action at 99:3 + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:99:15-16 -in=%s:100:20-21 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void sameLineIfElse(int x) { + if (x == 1) foo(); else foo(); + if (x == 2) { foo(); } else if (x == 3) { foo(); } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:109:3-14 -in=%s:109:22-26 %s | FileCheck --check-prefix=CHECK8 %s +// CHECK8: Initiated the 'if-switch-conversion' action at 109:3 + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:109:15-21 -in=%s:109:27-33 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// RUN: clang-refactor-test initiate -action if-switch-conversion -in=%s:110:3-15 -in=%s:110:24-43 %s | FileCheck --check-prefix=CHECK9 %s +// CHECK9: Initiated the 'if-switch-conversion' action at 110:3 + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:110:16-23 -in=%s:110:44-53 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void noIfsWithoutElses(int x) { + if (x == 1) { + foo(); + } + if (x == 2) ; +} + +// Ifs without any elses shouldn't be allowed: +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:124:1-16 -in=%s:127:1-16 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void noFancyIfs(const int *p) { + if (const int *x = p) { + } + else if (const int *y = p) { + } + + if (const int *x = p; *x == 2) { + } else if (const int *y = p; *y == 3) { + } +} + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:134:1-26 -in=%s:136:1-31 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// RUN: not clang-refactor-test initiate -action if-switch-conversion -in=%s:139:1-35 -in=%s:140:1-42 %s -std=c++1z 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +void prohibitBreaksCasesDefaults(int x) { + while (x != 0) { + break; + // Allowed: + if (x == 1) foo(); + else foo(); + // Not allowed: + if (x == 2) break; + else foo(); + if (x == 2) { foo(); } + else { break; } + if (x == 2) { foo(); } + else if (x == 1) { if (x == 2) { break; } } + } + switch (x) { + case 1: + // Allowed: + if (x == 1) foo(); + else foo(); + // Not allowed: + if (x == 2) foo(); + else if (x == 3) { + case 2: + foo(); + } + if (x == 3) foo(); + else { + default: + foo(); + } + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:151:5 %s | FileCheck --check-prefix=CHECK10 %s +// CHECK10: Initiated the 'if-switch-conversion' action at 151:5 + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:154:5 -at=%s:156:5 -at=%s:158:5 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-STATEMENTS %s +// CHECK-INVALID-STATEMENTS: Failed to initiate the refactoring action (if's body contains a 'break'/'default'/'case' statement) + +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:164:5 %s | FileCheck --check-prefix=CHECK11 %s +// CHECK11: Initiated the 'if-switch-conversion' action at 164:5 + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:167:5 -at=%s:172:5 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-STATEMENTS %s + +#ifdef DISALLOWED + #define DISALLOW(x) x +#else + #define DISALLOW(x) +#endif + +void allowBreaksInNestedLoops(int x) { + DISALLOW(while (true)) { + // Allowed: + if (x == 1) { + foo(); + } else if (x == 2) { + while (x != 0) { + break; + } + DISALLOW(break;) + } + + if (x == 1) { + foo(); + } else { + for (int y = 0; y < x; ++y) { + break; + } + DISALLOW(break;) + } + + if (x == 1) { + do { + break; + } while (x < 10); + DISALLOW(break;) + } else { + foo(); + } + + if (x == 1) { + do { + // nested loop. + while (true) { + } + break; + } while (x < 10); + DISALLOW(break;) + } else { + foo(); + } + + } + + // Still care about cases and defaults in loops: + switch (x) { + case 0: + if (x == 1) { + while (true) { + case 1: + } + } else { + foo(); + } + break; + } + + switch (x) { + case 0: + if (x == 1) { + while (true) { + default: + } + } else { + foo(); + } + break; + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:200:3 %s | FileCheck --check-prefix=CHECK-YES %s +// CHECK-YES: Initiated the 'if-switch-conversion' action + +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:209:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:218:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:227:3 %s | FileCheck --check-prefix=CHECK-YES %s + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:200:3 -at=%s:209:3 -at=%s:218:3 -at=%s:227:3 -at=%s:244:5 -at=%s:256:5 %s 2>&1 -D DISALLOWED | FileCheck --check-prefix=CHECK-INVALID-STATEMENTS %s + +void allowBreakDefaultCaseInNestedSwitches(int x) { + DISALLOW(switch (x)) { + // Allowed: + if (x == 1) { + foo(); + } else if (x == 2) { + switch (x) { + case 0: + foo(); + } + DISALLOW(case 0: ;) + } + + if (x == 1) { + foo(); + } else { + switch (x) { + default: + foo(); + } + DISALLOW(default: ;) + } + + if (x == 1) { + switch (x) { + break; + } + DISALLOW(break;) + } else { + foo(); + } + + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:279:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:289:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:299:3 %s | FileCheck --check-prefix=CHECK-YES %s + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:279:3 -at=%s:289:3 -at=%s:299:3 %s 2>&1 -D DISALLOWED | FileCheck --check-prefix=CHECK-INVALID-STATEMENTS %s + + + + + + + + + + + + + + + + +bool isTrue(); + +void allowOnlyEqualsOp(int x) { + if (x != 1) { + } else { + } + + if (x == 1) { + } else if (x > 2) { + } + + if (x == 3) { + } else if (x) { + } + + if (isTrue()) { + } else { + } +} + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:335:3 -at=%s:339:3 -at=%s:343:3 -at=%s:347:3 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-COND %s +// CHECK-INVALID-COND: Failed to initiate the refactoring action (unsupported conditional expression)! + +void allowEqualsOpInParens(int x) { + if ((x == 1)) { + } else { + } + + if (x == 1) { + } else if (((x == 2))) { + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:356:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: clang-refactor-test initiate -action if-switch-conversion -at=%s:360:3 %s | FileCheck --check-prefix=CHECK-YES %s + +enum Switchable { + A, B +}; + +struct Struct { +}; + +bool operator == (const Struct &, int); + +void allowSwitchableTypes(int x, bool b, long l, char c, Switchable e, + float f, double d, Struct s, int *ip) { + // Allowed: + if (b == true) { + } else { + } + + if (1 == 1) { + } else { + } + + if (l == 4) { + } else { + } + + if (e == A) { + } else { + } + + if (x == A) { + } else { + } + + if (c == 'x') { + } else { + } + + // Disallowed: + if (f == 0) { + } else { + } + + if (d == 0) { + } else { + } + + if (x == 0) { + } else if (x == 0.0) { + } + + if (s == 0) { + } else { + } + + if (ip == 0) { + } else { + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -location-agnostic -at=%s:380:3 -at=%s:384:3 -at=%s:388:3 -at=%s:392:3 -at=%s:396:3 -at=%s:400:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:405:3 -at=%s:409:3 -at=%s:413:3 -at=%s:417:3 -at=%s:421:3 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-COND %s + +template +void prohibitDependentOperators(T x) { + if (x == 0) { + } else { + } +} + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:431:3 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-COND %s + +int integerFunction(); + +void checkLHSSame(int x, int y) { + // Allowed: + if (integerFunction() == 1) { + } else if (integerFunction() == 2) { + } + + // Disallowed: + if (x == 1) { + } else if (y == 2) { + } + + if (x == 1) { + } else if (2 == 2) { + } + + if (integerFunction() == 1) { + } else if (x == 2) { + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -location-agnostic -at=%s:442:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:447:3 -at=%s:451:3 -at=%s:455:3 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-COND %s + +void checkRHSConstant(int x, int y, Switchable e) { + // Allowed: + if (x == (int)A) { + } else { + } + + if (e == (Switchable)1) { + } else { + } + + // Disallowed: + if (x == y) { + } else { + } + + if (x == 1) { + } else if (x == integerFunction()) { + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -location-agnostic -at=%s:465:3 -at=%s:469:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:474:3 -at=%s:478:3 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-COND %s + +void checkRHSUnique(int x, int y, Switchable e) { + // Disallowed: + if (x == 0) { + } else if (x == 0) { + } + + if (e == A) { + } else if (e == (Switchable)0) { + } +} + +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:474:3 -at=%s:478:3 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-COND %s + +void allowLHSParens(int x) { + if ((x) == 0) { + } else { + } +} + +void allowRHSParens(int x) { + if (x == (0)) { + } else { + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -location-agnostic -at=%s:500:3 -at=%s:506:3 %s | FileCheck --check-prefix=CHECK-YES %s + +void allowLogicalOr(int x, int y) { + // Allowed: + if (x == 0 || x == 1) { + } else { + } + + if (x == 0) { + } else if (x == 1 || x == 2) { + } + + if (x == (0) || (x == 1)) { + } else { + } + + if (x == 0) { + } else if ((x == 1 || x == 2)) { + } + + // Disallowed: + if (x == 0 && x == 1) { + } else { + } + + if (x == 0 | x == 1) { + } else { + } + + if (x == 0 || isTrue()) { + } else if (y == 2) { + } + + if (x == 0 || x == 1) { + } else if (y == 2) { + } + + if (x == 0) { + } else if (x == 1 || x == integerFunction()) { + } + + if (x == 1) { + } else if (x == 2 || x == 1) { + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -location-agnostic -at=%s:515:3 -at=%s:519:3 -at=%s:523:3 -at=%s:527:3 %s | FileCheck --check-prefix=CHECK-YES %s +// RUN: not clang-refactor-test initiate -action if-switch-conversion -at=%s:532:3 -at=%s:536:3 -at=%s:540:3 -at=%s:544:3 -at=%s:548:3 -at=%s:552:3 %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID-COND %s + +void parenImpCastsLHSEquivalence(int x) { + if ((x) == 1) { + } else if (x == 2) { + } +} + +// RUN: clang-refactor-test initiate -action if-switch-conversion -location-agnostic -at=%s:561:3 %s | FileCheck --check-prefix=CHECK-YES %s diff --git a/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-perform.cpp b/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-perform.cpp new file mode 100644 index 0000000000000..a16e165498696 --- /dev/null +++ b/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-perform.cpp @@ -0,0 +1,307 @@ +void foo(); + +void simpleCompoundBodyIf(int x) { + foo(); + + if (x == 2) { // CHECK1: "switch (" [[@LINE]]:3 -> [[@LINE]]:7 + (void)x; // CHECK1-NEXT: ") {\ncase " [[@LINE-1]]:8 -> [[@LINE-1]]:12 + // CHECK1-NEXT: ":" [[@LINE-2]]:13 -> [[@LINE-2]]:16 + } else { // CHECK1-NEXT: "break;\ndefault:" [[@LINE]]:3 -> [[@LINE]]:11 + foo(); + } + + if (((x) == 22 || x == 3) || x == 4) { // CHECK1: "switch (" [[@LINE]]:3 -> [[@LINE]]:9 + // CHECK1-NEXT: ") {\ncase " [[@LINE-1]]:10 -> [[@LINE-1]]:15 + // CHECK1-NEXT: ":\ncase " [[@LINE-2]]:17 -> [[@LINE-2]]:26 + // CHECK1-NEXT: ":\ncase " [[@LINE-3]]:27 -> [[@LINE-3]]:37 + // CHECK1-NEXT: ":" [[@LINE-4]]:38 -> [[@LINE-4]]:41 + } else { // CHECK1-NEXT: "break;\ndefault:" [[@LINE]]:3 -> [[@LINE]]:11 + } + + foo(); +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:6:3 -at=%s:13:3 %s | FileCheck --check-prefix=CHECK1 %s + +void colonInsertionCompoundBody(int x) { + if (x == 2) // CHECK2: ":" [[@LINE]]:13 -> [[@LINE+2]]:4 + + { + + } + else { + } +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:27:3 %s | FileCheck --check-prefix=CHECK2 %s + +void colonInsertionNonCompoundBody(int x) { + if (x == 2) foo(); // CHECK3: ":" [[@LINE]]:13 -> [[@LINE]]:14 + else { + } + + if (x == 2) // CHECK3: ":" [[@LINE]]:13 -> [[@LINE]]:14 + foo(); + else { + } + + if ((x == (2)) /*comment*/) // CHECK3: ":" [[@LINE]]:15 -> [[@LINE]]:30 + foo(); + else { + } + + if (x == 2 // CHECK3: ":" [[@LINE]]:13 -> [[@LINE+1]]:8 + ) + // comment + foo(); + else { + } +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:39:3 -at=%s:43:3 -at=%s:48:3 -at=%s:53:3 %s | FileCheck --check-prefix=CHECK3 %s + +void colonInsertionFailure(int x) { +#define EMPTY_MACRO + if (x == 1 EMPTY_MACRO ) foo(); + else { + } +} + +// RUN: not clang-refactor-test perform -action if-switch-conversion -at=%s:65:3 %s 2>&1 | FileCheck --check-prefix=CHECK-ERR1 %s +// CHECK-ERR1: failed to perform the refactoring operation (couldn't find the location of ')') + +void elseNonCompoundBody(int x) { +#ifdef WITH_ELSEIF + if (x == 1) foo(); else +#endif + if (x == 2) + foo(); + else // CHECK4: "break;\ndefault:" [[@LINE]]:3 -> [[@LINE]]:7 + foo(); + +#ifdef WITH_ELSEIF + if (x == 1) foo(); else +#endif + if (x == 2) + foo(); + else foo(); // CHECK4: "break;\ndefault:" [[@LINE]]:3 -> [[@LINE]]:7 + +#ifdef WITH_ELSEIF + if (x == 1) foo(); else +#endif + if (x == 2) foo(); /*comment*/ else foo(); // CHECK4: "\nbreak;\ndefault:" [[@LINE]]:34 -> [[@LINE]]:38 + +#ifdef WITH_ELSEIF + if (x == 1) foo(); else +#endif + if (x == 2) ; else ; // CHECK4: "\nbreak;\ndefault:" [[@LINE]]:17 -> [[@LINE]]:21 +} + +void elseCompoundBody(int x) { +#ifdef WITH_ELSEIF + if (x == 1) foo(); else +#endif + if (x == 2) { foo(); } else { foo(); } // CHECK4: "\nbreak;\ndefault:" [[@LINE]]:24 -> [[@LINE]]:32 + +#ifdef WITH_ELSEIF + if (x == 1) foo(); else +#endif + if (x == 2) { + // comment. + } + else // CHECK4: "break;\ndefault:" [[@LINE-1]]:3 -> [[@LINE+1]]:4 + { + foo(); + } +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:77:3 -at=%s:85:3 -at=%s:92:3 -at=%s:97:3 -at=%s:104:3 -at=%s:109:3 %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:77:3 -at=%s:85:3 -at=%s:92:3 -at=%s:97:3 -at=%s:104:3 -at=%s:109:3 %s -D WITH_ELSEIF | FileCheck --check-prefix=CHECK4 %s + +void elseIfCompoundBody(int x) { + if (x == 2) { + foo(); + } else if (x == 3) { // CHECK5: "break;\ncase " [[@LINE]]:3 -> [[@LINE]]:19 + foo(); // CHECK5-NEXT: ":" [[@LINE-1]]:20 -> [[@LINE-1]]:23 + } else if (x == 4 || x == 55) { // CHECK5-NEXT: "break;\ncase " [[@LINE]]:3 -> [[@LINE]]:19 + foo(); // CHECK5-NEXT: ":\ncase " [[@LINE-1]]:20 -> [[@LINE-1]]:29 + // CHECK5-NEXT: ":" [[@LINE-2]]:31 -> [[@LINE-2]]:34 + } + + if (x == 2) { foo(); } else if (x == 3) { foo(); } // CHECK5: "\nbreak;\ncase " [[@LINE]]:24 -> [[@LINE]]:40 + // CHECK5-NEXT: ":" [[@LINE-1]]:41 -> [[@LINE-1]]:44 + + if (x == 2) { + // comment. + } + else if (x == 3) // CHECK5: "break;\ncase " [[@LINE-1]]:3 -> [[@LINE]]:17 + { // CHECK5-NEXT: ":" [[@LINE-1]]:18 -> [[@LINE]]:4 + foo(); + } +} + +void elseIfNonCompoundBody(int x) { + if (x == 2) + foo(); + else if (x == 21) // CHECK5: "break;\ncase " [[@LINE]]:3 -> [[@LINE]]:17 + foo(); // CHECK5-NEXT: ":" [[@LINE-1]]:19 -> [[@LINE-1]]:20 + else if (x == 5) ;// CHECK5-NEXT: "break;\ncase " [[@LINE]]:3 -> [[@LINE]]:17 + // CHECK5-NEXT: ":" [[@LINE-1]]:18 -> [[@LINE-1]]:19 + + if (x == 2) foo(); /*comment*/ else if (x == 3) foo(); // CHECK5: "\nbreak;\ncase " [[@LINE]]:34 -> [[@LINE]]:48 + // CHECK5-NEXT: ":" [[@LINE-1]]:49 -> [[@LINE-1]]:50 + + if (x == 2) ; else if (x == 3) ; // CHECK5: "\nbreak;\ncase " [[@LINE]]:17 -> [[@LINE]]:31 +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:122:3 -at=%s:131:3 -at=%s:134:3 -at=%s:144:3 -at=%s:151:3 -at=%s:154:3 %s | FileCheck --check-prefix=CHECK5 %s + +void closingBraceInsertion(int x) { + if (x == 2) { + } else if (x == 3) { + } else { + foo(); + } // CHECK6: "break;\n" [[@LINE]] + + if (x == 3) { + } else if (x == 4) { + } // CHECK6: "break;\n" [[@LINE]] + + if (x == 2) + foo(); + else if (x == 3) + foo(); + else if (x == 4) // CHECK6: "\nbreak;\n}" [[@LINE+1]]:11 -> [[@LINE+1]]:11 + foo(); + + if (x == 2) + foo(); + else // CHECK6: "\nbreak;\n}" [[@LINE+1]]:11 -> [[@LINE+1]]:11 + foo(); + + if (x == 2) foo(); // CHECK6: "\nbreak;\n}" [[@LINE+1]]:35 -> [[@LINE+1]]:35 + else foo(); // preserve comments + + if (x == 2) foo(); + else if (x == 3) // CHECK6: "\nbreak;\n}" [[@LINE+1]]:12 -> [[@LINE+1]]:12 + foo() ; foo(); // no preserve + + if (x == 2) foo(); // CHECK6: "\nbreak;\n}" [[@LINE+1]]:11 -> [[@LINE+1]]:11 + else ; ; +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:160:3 -at=%s:166:3 -at=%s:170:3 -at=%s:177:3 -at=%s:182:3 -at=%s:185:3 -at=%s:189:3 %s | FileCheck --check-prefix=CHECK6 %s + +void needBreaks(int x) { + if (x == 2) { + return; + x = 3; + } else if (x == 3) { // CHECK7: "break;\ncase " [[@LINE]] + foo(); + return; + foo(); + } else { // CHECK7: "break;\ndefault:" [[@LINE]] + if (x == 1) { + return; + } + } // CHECK7: "break;\n" [[@LINE]]:3 + + if (x == 2) + if (x == 3) + return; + else ; else // CHECK7: "\nbreak;\ndefault:" [[@LINE]] + while (x < 2) + return; // CHECK7: "\nbreak;\n}" [[@LINE]]:52 +} + +void noNeedForBreaks(int x) { + if (x == 2) { + return; + } else if (x == 3) { // CHECK7: "case " [[@LINE]] + foo(); + return; + } else { // CHECK7: "default:" [[@LINE]] + if (x == 1) { + } + { + return; + } + } // CHECK7-NOT: "{{.*}}break{{.*}}" [[@LINE]] + + if (x == 2) return; else return; // CHECK7: "\ndefault:" [[@LINE]] + // CHECK7: "\n}" [[@LINE-1]] + + // Invalid returns should work as well. + if (x == 2) + return 1; + else // CHECK7: "default:" [[@LINE]] + return 2; // CHECK7: "\n}" [[@LINE]] +} + +int noNeedForBreaksInvalidRets(int x) { + if (x == 2) + return; // This omits the 'break'. + // But this doesn't (should it?). + else { // CHECK7: "default:" [[@LINE]] + return ""; + } // CHECK7: "break;\n" [[@LINE]] +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:196:3 -at=%s:209:3 -at=%s:218:3 -at=%s:231:3 -at=%s:235:3 -at=%s:242:3 %s | FileCheck --check-prefix=CHECK7 %s + +void needBraces(int x) { + if (x == 2) { // CHECK8: ": {" [[@LINE]]:13 -> [[@LINE]]:16 + int a = x; + } else if (x == 1) { // CHECK8-NEXT: "break;\n}\ncase " [[@LINE]]:3 + int a = 0, y = 1; // CHECK8-NEXT: ": {" [[@LINE-1]]:20 -> [[@LINE-1]]:23 + return; + } else if (x == 3 || x == 4) { // CHECK8-NEXT: "}\ncase " [[@LINE]]:3 + int m = 2; // CHECK8-NEXT: ":\ncase " [[@LINE-1]] + // CHECK8-NEXT: ": {" [[@LINE-2]]:30 -> [[@LINE-2]]:33 + } else if (x == 5) { // CHECK8-NEXT: "break;\n}\ncase " [[@LINE]]:3 + return; // CHECK8-NEXT: ":" [[@LINE-1]]:20 -> [[@LINE-1]]:23 + } else { // CHECK8-NEXT: "default: {" [[@LINE]]:3 -> [[@LINE]]:11 + foo(); + int k = x; + foo(); + } // CHECK8-NEXT: "break;\n}\n" [[@LINE]]:3 -> [[@LINE]]:3 + + if (x == 2) { // CHECK8: ": {" [[@LINE]] + int a = 2; + } else if (x == 3) { // CHECK8: "break;\n}\ncase " [[@LINE]] + int b = x; // CHECK8-NEXT: ": {" [[@LINE-1]] + } // CHECK8-NEXT: "break;\n}\n" [[@LINE]] + + if (x == 2) // CHECK8: ": {" [[@LINE]]:13 -> [[@LINE]]:14 + int a = x; + else if (x == 1 || x == 3) // CHECK8-NEXT: "break;\n}\ncase " [[@LINE]]:3 -> [[@LINE]]:17 + int b = 2; // CHECK8-NEXT: ":\ncase " [[@LINE-1]] + // CHECK8-NEXT: ": {" [[@LINE-2]]:28 -> [[@LINE-2]]:29 + else if (x == 4) // CHECK8-NEXT: "break;\n}\ncase " [[@LINE]]:3 -> [[@LINE]]:17 + foo(); // CHECK8-NEXT: ":" [[@LINE-1]] + else if (x == 5) // CHECK8-NEXT: "break;\ncase " [[@LINE]] + return; // CHECK8-NEXT: ":" [[@LINE-1]] + else // CHECK8-NEXT: "default: {" [[@LINE]]:3 -> [[@LINE]]:7 + int c = x; + // CHECK8-NEXT: "\nbreak;\n}\n}" [[@LINE-1]]:15 -> [[@LINE-1]]:15 + + if (x == 2) int a = 1; else int k = x; + // CHECK8: ": {" [[@LINE-1]]:13 -> [[@LINE-1]]:14 + // CHECK8-NEXT: "\nbreak;\n}\ndefault: {" [[@LINE-2]]:26 -> [[@LINE-2]]:30 + // CHECK8-NEXT: "\nbreak;\n}\n}" [[@LINE-3]]:41 -> [[@LINE-3]]:41 +} + +void noBracesNeeded(int x) { + if (x == 2) { // CHECK8: ":" [[@LINE]] + if (int *z = p) { + } + } else if (x == 3) { // CHECK8: "break;\ncase " [[@LINE]] + for (int z = 0; z < x ; ++z) ; // CHECK8: ":" [[@LINE-1]] + } else if (x == 4) { // CHECK8: "break;\ncase " [[@LINE]] + { // CHECK8: ":" [[@LINE-1]] + int a = 1; + } + } +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:253:3 -at=%s:269:3 -at=%s:275:3 -at=%s:288:3 -at=%s:295:3 %s | FileCheck --check-prefix=CHECK8 %s diff --git a/clang/test/Refactor/ImplementDeclaredMethods/Inputs/class.cpp b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/class.cpp new file mode 100644 index 0000000000000..f6389e2f1ddb0 --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/class.cpp @@ -0,0 +1,25 @@ + +struct Class { + int field; + + Class(); + + Class(int x) { } + + ~Class(); + + // commment + static void method(const int &value, int defaultParam = 20); + + virtual int voidMethod(int y) const; + void implementedMethod() const { + + } + + void outOfLineImpl(int x); + + void anotherImplementedMethod() { + + } +}; +// CHECK1: "{{.*}}class.cpp" "\n\nClass::Class() { \n <#code#>;\n}\n\nvoid Class::method(const int &value, int defaultParam) { \n <#code#>;\n}\n" [[@LINE-1]]:3 diff --git a/clang/test/Refactor/ImplementDeclaredMethods/Inputs/classInHeader.cpp b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/classInHeader.cpp new file mode 100644 index 0000000000000..6c16bdf0716a9 --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/classInHeader.cpp @@ -0,0 +1,69 @@ +#include "classInHeader.h" + +#ifndef NO_IMPL + +#define PREFIX + +#ifdef USE_NAMESPACE +#ifdef USE_NAMESPACE_PREFIX +#define PREFIX ns::ns2:: +#else +#ifdef USE_NAMESPACE_USING +using namespace ns::ns2; +#else +namespace ns { +namespace ns2 { +#define CLOSE_NAMESPACES +#endif +#endif +#endif + +void PREFIX ClassInHeader::implementedToo() { + +} + +void PREFIX ClassInHeader::implemented() { + +} +// CHECK1: "{{.*}}classInHeader.cpp" "\n\nvoid ClassInHeader::pleaseImplement() { \n <#code#>;\n}\n\nvoid ClassInHeader::pleaseImplementThisAsWell() { \n <#code#>;\n}\n" [[@LINE-1]]:2 +// CHECK1-NS-PREFIX: "{{.*}}classInHeader.cpp" "\n\nvoid ns::ns2::ClassInHeader::pleaseImplement() { \n <#code#>;\n}\n\nvoid ns::ns2::ClassInHeader::pleaseImplementThisAsWell() { \n <#code#>;\n}\n" [[@LINE-2]]:2 + +#ifdef CLOSE_NAMESPACES +} +} +#endif + +#endif + +namespace other { +#ifndef USE_NAMESPACE_USING +using namespace ns::ns2; +#else +} + +void usingCanBeHidden() { +#ifndef USE_NAMESPACE_USING +using namespace ns::ns2; +#else +} + +#ifdef USE_NAMESPACE_USING +using namespace ns::ns2; +#else +// We still want to insert 'using namespace ns::ns2' if the outer is already +// used. +using namespace ns; +#endif + +using namespace other; + +namespace ns { +namespace ns2 { +// Prefer to insert the methods at the end using 'using' instead of into a +// namespace. +} +} + +// CHECK1-NO-IMPL-USING-NS-IN-RECORD: "{{.*}}classInHeader.cpp" "\nusing namespace ns::ns2;\n\nvoid OuterRecord::ClassInHeader::pleaseImplement() { \n <#code#>;\n}\n\nvoid OuterRecord::ClassInHeader::pleaseImplementThisAsWell() { \n <#code#>;\n}\n" [[@LINE+3]]:1 +// CHECK1-NO-IMPL-USING-NS: "{{.*}}classInHeader.cpp" "\nusing namespace ns::ns2;\n\nvoid ClassInHeader::pleaseImplement() { \n <#code#>;\n}\n\nvoid ClassInHeader::pleaseImplementThisAsWell() { \n <#code#>;\n}\n" [[@LINE+2]]:1 +// CHECK1-NO-IMPL: "{{.*}}classInHeader.cpp" "\n\nvoid ClassInHeader::pleaseImplement() { \n <#code#>;\n}\n\nvoid ClassInHeader::pleaseImplementThisAsWell() { \n <#code#>;\n}\n" [[@LINE+1]]:1 -> [[@LINE+1]]:1 diff --git a/clang/test/Refactor/ImplementDeclaredMethods/Inputs/classInHeader.h b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/classInHeader.h new file mode 100644 index 0000000000000..89c01bd5b8cba --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/classInHeader.h @@ -0,0 +1,31 @@ + +#ifdef USE_NAMESPACE +namespace ns { +namespace ns2 { +#endif + +#ifdef USE_ENCLOSING_RECORD +struct OuterRecord { +#endif + +struct ClassInHeader { + void pleaseImplement(); + void implemented(); + void pleaseImplementThisAsWell(); + void implementedToo(); + void anotherMethod(); +}; + +#ifdef USE_ENCLOSING_RECORD +} +#endif + +void ClassInHeader::anotherMethod() { +} +// CHECK: "{{.*}}classInHeader.h" "\n\nvoid ClassInHeader::pleaseImplement() { \n <#code#>;\n}\n\nvoid ClassInHeader::pleaseImplementThisAsWell() { \n <#code#>;\n}\n" [[@LINE-1]]:2 -> [[@LINE-1]]:2 + +#ifdef USE_NAMESPACE +} +} +#endif + diff --git a/clang/test/Refactor/ImplementDeclaredMethods/Inputs/empty.cpp b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/empty.cpp new file mode 100644 index 0000000000000..8d1c8b69c3fce --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/empty.cpp @@ -0,0 +1 @@ + diff --git a/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcClass.m b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcClass.m new file mode 100644 index 0000000000000..1b31110b051ee --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcClass.m @@ -0,0 +1,13 @@ +#include "objcHeader.h" + +@implementation MyClass + +#ifdef MIX_IMPL ++ (void)classMethod { } + +- (void)method:(int)x with:(int)y { } +#endif + +@end +// CHECK1: "{{.*}}objcClass.m" "- (void)method { \n <#code#>;\n}\n\n+ (void)classMethod { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n- (void)method:(int)x with:(int)y { \n <#code#>;\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// CHECK2: "{{.*}}objcClass.m" "- (void)method { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n" [[@LINE-2]]:1 diff --git a/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcHeader.h b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcHeader.h new file mode 100644 index 0000000000000..8d79abedf4276 --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcHeader.h @@ -0,0 +1,13 @@ + +@interface MyClass + +- (void)method; + ++ (void)classMethod; + +- (void)implementedMethod; + +- (void)method:(int)x with:(int)y; + +@end + diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods-initiate.cpp b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods-initiate.cpp new file mode 100644 index 0000000000000..b94d92b676282 --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods-initiate.cpp @@ -0,0 +1,64 @@ + +struct Class { + int field; + + Class(); + + Class(int x) { } + + ~Class(); + + // commment + void method(); + + virtual voidMethod(int y) const; + void implementedMethod() const { + + } + + void outOfLineImpl(int x); + + void anotherImplementedMethod() { + + } +}; +// CHECK1: Initiated the 'implement-declared-methods' action at [[@LINE-20]]:3 +// CHECK2: Initiated the 'implement-declared-methods' action at [[@LINE-17]]:3 +// CHECK3: Initiated the 'implement-declared-methods' action at [[@LINE-15]]:3 +// CHECK4: Initiated the 'implement-declared-methods' action at [[@LINE-14]]:3 + +void function(); + +void function() { + +} + +void Class::outOfLineImpl(int x) { + +} + +// RUN: clang-refactor-test initiate -action implement-declared-methods -in=%s:5:3-10 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action implement-declared-methods -in=%s:9:3-11 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test initiate -action implement-declared-methods -in=%s:12:3-16 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test initiate -action implement-declared-methods -in=%s:14:3-34 %s | FileCheck --check-prefix=CHECK4 %s + +// RUN: not clang-refactor-test initiate -action implement-declared-methods -in=%s:2:1-end -in=%s:3:1-end -in=%s:4:1-end -in=%s:5:1-2 -in=%s:7:1-end -in=%s:9:1-2 -in=%s:11:1-end -in=%s:15:1-end -in=%s:16:1-end -in=%s:17:1-end -in=%s:19:1-end %s -in=%s:30:1-end 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// CHECK-NO: Failed to initiate the refactoring action + +// RUN: clang-refactor-test list-actions -at=%s:5:3 %s | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Generate Missing Function Definitions + +// Class, ~Class, method, voidMethod: +// CHECK5: Initiated the 'implement-declared-methods' action at [[@LINE-48]]:3 -> [[@LINE-39]]:34 +// RUN: clang-refactor-test initiate -action implement-declared-methods -selected=%s:2:1-24:3 -selected=%s:3:1-23:1 -selected=%s:4:1-23:3 -selected=%s:5:1-14:35 -selected=%s:5:9-14:4 %s | FileCheck --check-prefix=CHECK5 %s + +// ~Class, method +// CHECK6: Initiated the 'implement-declared-methods' action at [[@LINE-48]]:3 -> [[@LINE-45]]:16 +// RUN: clang-refactor-test initiate -action implement-declared-methods -selected=%s:9:1-12:16 -selected=%s:7:1-13:1 -selected=%s:7:17-12:4 %s | FileCheck --check-prefix=CHECK6 %s + +// voidMethod +// CHECK7: Initiated the 'implement-declared-methods' action at [[@LINE-47]]:3 +// RUN: clang-refactor-test initiate -action implement-declared-methods -selected=%s:14:3-14:10 -selected=%s:14:22-14:27 %s | FileCheck --check-prefix=CHECK7 %s + +// RUN: not clang-refactor-test initiate -action implement-declared-methods -selected=%s:2:1-30:10 -selected=%s:15:1-15:10 -selected=%s:16:1-16:3 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods-initiate.m b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods-initiate.m new file mode 100644 index 0000000000000..626926bc7361c --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods-initiate.m @@ -0,0 +1,106 @@ + +@protocol P + +- (void)method; + +@end + +@interface MyClass { + int ivar; +} + +@property int prop; + +- (void)method; + +// comment ++ (void)classMethod; + +- (void)implementedMethod; + +- (void)method:(int)x with:(int)y; + +@end + +@implementation MyClass + +- (void)implementedMethod { + +} + +@end + + +// CHECK1: Initiated the 'implement-declared-methods' action at [[@LINE-20]]:1 +// CHECK2: Initiated the 'implement-declared-methods' action at [[@LINE-18]]:1 +// CHECK3: Initiated the 'implement-declared-methods' action at [[@LINE-15]]:1 + +// RUN: clang-refactor-test initiate -action implement-declared-methods -in=%s:14:1-14 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test initiate -action implement-declared-methods -in=%s:17:1-20 %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test initiate -action implement-declared-methods -in=%s:21:1-34 %s | FileCheck --check-prefix=CHECK3 %s + +// RUN: not clang-refactor-test initiate -action implement-declared-methods -in=%s:4:1-end -in=%s:27:1-end -in=%s:8:1-end -in=%s:9:1-end -in=%s:12:1-end -in=%s:16:1-end -in=%s:19:1-end -in=%s:23:1-end %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// CHECK-NO: Failed to initiate the refactoring action + +// method, classMethod, method:with: : +// CHECK4: Initiated the 'implement-declared-methods' action at [[@LINE-33]]:1 -> [[@LINE-26]]:35 +// RUN: clang-refactor-test initiate -action implement-declared-methods -selected=%s:8:1-24:3 -selected=%s:9:1-23:3 -selected=%s:14:1-21:35 -selected=%s:14:14-21:2 %s | FileCheck --check-prefix=CHECK4 %s + +// classMethod, method:with: +// CHECK5: Initiated the 'implement-declared-methods' action at [[@LINE-34]]:1 -> [[@LINE-30]]:35 +// RUN: clang-refactor-test initiate -action implement-declared-methods -selected=%s:17:1-21:35 -selected=%s:16:1-22:1 -selected=%s:17:20-21:2 %s | FileCheck --check-prefix=CHECK5 %s + +// classMethod +// CHECK6: Initiated the 'implement-declared-methods' action at [[@LINE-38]]:1 +// RUN: clang-refactor-test initiate -action implement-declared-methods -selected=%s:17:1-17:10 -selected=%s:17:20-18:1 %s | FileCheck --check-prefix=CHECK6 %s + +// RUN: not clang-refactor-test initiate -action implement-declared-methods -selected=%s:2:1-30:10 -selected=%s:2:1-6:10 -selected=%s:6:1-25:2 -selected=%s:27:1-29:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// Methods declared in class extensions / categories should be supported: + +@interface I2 + +@end + +@interface I2 () + +- (void)method; ++ (void)classMethod; +- (void)implementedMethod; + +@end +// CHECK7: Initiated the 'implement-declared-methods' action at [[@LINE-5]]:1 +// RUN: clang-refactor-test initiate -action implement-declared-methods -at=%s:68:1 %s | FileCheck --check-prefix=CHECK7 %s +// RUN: not clang-refactor-test initiate -action implement-declared-methods -at=%s:70:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// CHECK8: Initiated the 'implement-declared-methods' action at [[@LINE-9]]:1 -> [[@LINE-8]]:21 +// RUN: clang-refactor-test initiate -action implement-declared-methods -selected=%s:68:1-71:1 %s | FileCheck --check-prefix=CHECK8 %s + +@implementation I2 + +- (void)implementedMethod { +} + +@end + +@interface I2 (Extension) + +- (void)methodExt; ++ (void)classMethodExt; +- (void)implementedMethodExt; + +@end +// CHECK9: Initiated the 'implement-declared-methods' action at [[@LINE-5]]:1 +// RUN: clang-refactor-test initiate -action implement-declared-methods -at=%s:89:1 %s | FileCheck --check-prefix=CHECK9 %s +// RUN: not clang-refactor-test initiate -action implement-declared-methods -at=%s:91:1 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// CHECK10: Initiated the 'implement-declared-methods' action at [[@LINE-9]]:1 -> [[@LINE-8]]:24 +// RUN: clang-refactor-test initiate -action implement-declared-methods -selected=%s:89:1-92:1 %s | FileCheck --check-prefix=CHECK10 %s + +@implementation I2 (Extension) + +- (void)implementedMethodExt { +} + +@end diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.cpp b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.cpp new file mode 100644 index 0000000000000..82ea02187aeb6 --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.cpp @@ -0,0 +1,164 @@ + +struct Class { + int field; + + Class(); + + Class(int x) { } + + ~Class(); + + // commment middle-methods-begin: +1:1 + static void method(const int &value, int defaultParam = 20); + + virtual int voidMethod(int y) const; + void implementedMethod() const { // middle-methods-end: -1:40 + + } + + void outOfLineImpl(int x); + + void anotherImplementedMethod() { + + } +}; +// CHECK1: "{{.*}}implement-declared-methods.cpp" "\n\nvoid Class::method(const int &value, int defaultParam) { \n <#code#>;\n}\n\nint Class::voidMethod(int y) const { \n <#code#>;\n}\n" [[@LINE+5]]:37 -> [[@LINE+5]]:37 +// CHECK2: "{{.*}}implement-declared-methods.cpp" "\n\nClass::Class() { \n <#code#>;\n}\n\nClass::~Class() { \n <#code#>;\n}\n\nvoid Class::method(const int &value, int defaultParam) { \n <#code#>;\n}\n\nint Class::voidMethod(int y) const { \n <#code#>;\n}\n" [[@LINE+4]]:37 +// CHECK3: "{{.*}}implement-declared-methods.cpp" "\n\nClass::~Class() { \n <#code#>;\n}\n\nvoid Class::method(const int &value, int defaultParam) { \n <#code#>;\n}\n" [[@LINE+3]]:37 +// CHECK4: "{{.*}}implement-declared-methods.cpp" "\n\nClass::Class() { \n <#code#>;\n}\n\nvoid Class::method(const int &value, int defaultParam) { \n <#code#>;\n}\n" [[@LINE+2]]:37 + +void Class::outOfLineImpl(int x) { } + +// query-all-impl: [ { name: ast.producer.query, filenameResult: "%s" } , { name: decl.query , predicateResults: [{name: decl.isDefined, intValues: [0, 0, 0, 0] }] }] +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=middle-methods -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=%s:5:1-20:1 -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=%s:8:1-12:10 -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK3 %s + +// Implement the constructor and method: +// query-mix-impl: [ { name: ast.producer.query, filenameResult: "%s" } , { name: decl.query , predicateResults: [{name: decl.isDefined, intValues: [0, 1, 0, 1] }] }] +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=%s:5:1-20:1 -continuation-file=%s -query-results=query-mix-impl %s | FileCheck --check-prefix=CHECK4 %s + +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=%s:5:1-20:1 -continuation-file=%S/Inputs/class.cpp -query-results=query-mix-impl %s | FileCheck --check-prefix=CHECK1 %S/Inputs/class.cpp + +// Empty continuation TU should produce an error: +// RUN: not clang-refactor-test perform -action implement-declared-methods -selected=%s:5:1-20:1 -continuation-file=%S/Inputs/empty.cpp -query-results=query-mix-impl %s 2>&1 | FileCheck --check-prefix=CHECK-EMPTY-ERR %s +// CHECK-EMPTY-ERR: failed to perform the refactoring continuation (the target class is not defined in the continuation AST unit)! + +#ifdef USE_NAMESPACE +namespace ns { +namespace ns2 { +#endif + +#ifdef USE_ENCLOSING_RECORD +struct OuterRecord { +#endif + +struct ClassInHeader { +// class-in-header-begin: +1:1 + void pleaseImplement(); + void implemented(); + void pleaseImplementThisAsWell(); + void implementedToo(); +// class-in-header-end: +1:1 +}; + +#ifdef USE_ENCLOSING_RECORD +struct } +#endif + +#ifdef USE_NAMESPACE +} +} +#endif + +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl %s | FileCheck --check-prefix=CHECK1 %S/Inputs/classInHeader.cpp +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl %s -DUSE_NAMESPACE | FileCheck --check-prefix=CHECK1 %S/Inputs/classInHeader.cpp +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl %s -DUSE_NAMESPACE -DUSE_NAMESPACE_USING | FileCheck --check-prefix=CHECK1 %S/Inputs/classInHeader.cpp +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl %s -DUSE_NAMESPACE -DUSE_NAMESPACE_PREFIX | FileCheck --check-prefix=CHECK1-NS-PREFIX %S/Inputs/classInHeader.cpp + +// Test when the implementation file has no out-of-line definitions. +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl %s -DNO_IMPL | FileCheck --check-prefix=CHECK1-NO-IMPL %S/Inputs/classInHeader.cpp +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl %s -DNO_IMPL -DUSE_NAMESPACE | FileCheck --check-prefix=CHECK1-NO-IMPL-USING-NS %S/Inputs/classInHeader.cpp +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl %s -DNO_IMPL -DUSE_NAMESPACE -DUSE_ENCLOSING_RECORD | FileCheck --check-prefix=CHECK1-NO-IMPL-USING-NS-IN-RECORD %S/Inputs/classInHeader.cpp +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl %s -DNO_IMPL -DUSE_NAMESPACE -DUSE_NAMESPACE_USING | FileCheck --check-prefix=CHECK1-NO-IMPL %S/Inputs/classInHeader.cpp + +// query-mix-impl-header: [ { name: ast.producer.query, filenameResult: "%S/classInHeader.h" } , { name: decl.query , predicateResults: [{name: decl.isDefined, intValues: [0, 1, 0, 1] }] }] +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl-header %s | FileCheck %S/Inputs/classInHeader.h + +// Ensure that the methods which are placed right after the record are placed +// after the outermost record: +namespace ns { + +struct AfterRecordOuterOuter { +struct AfterRecordOuter { + struct AfterRecordInner { +// after-record-inner-begin: +1:1 + void pleaseImplement(); +// after-record-inner-end: +0:1 + }; + + AfterRecordOuter(); +}; +// comment +}; +// CHECK-OUTERMOST: "{{.*}}implement-declared-methods.cpp" "\n\nvoid AfterRecordOuterOuter::AfterRecordOuter::AfterRecordInner::pleaseImplement() { \n <#code#>;\n}\n" [[@LINE-1]]:3 -> [[@LINE-1]]:3 + +} +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=after-record-inner -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK-OUTERMOST %s + +#ifdef INNER_TEMPLATE +template +struct OuterTemplateRecord { +#else + template +#endif + struct InnerTemplate { +// inner-template-begin: +0:1 + InnerTemplate(); + void function(); +// inner-template-end: +1:1 + }; +#ifdef INNER_TEMPLATE +}; +#endif + +// RUN: not clang-refactor-test perform -action implement-declared-methods -selected=inner-template %s 2>&1 | FileCheck --check-prefix=CHECK-TEMPLATE-NO %s +// RUN: not clang-refactor-test perform -action implement-declared-methods -selected=inner-template %s -DINNER_TEMPLATE 2>&1 | FileCheck --check-prefix=CHECK-TEMPLATE-NO %s + +// CHECK-TEMPLATE-NO: Failed to initiate the refactoring action (templates are unsupported)! + +template +class TemplateSpecialization { +}; + +template<> +class TemplateSpecialization<0, int> { +// template-specialization-begin: +0:1 + TemplateSpecialization(); + void function(); + void operator ()(int) const; + operator int() const; +// template-specialization-end: +0:1 +}; +// CHECK-SPECIALIZATION: "{{.*}}implement-declared-methods.cpp" "\n\nTemplateSpecialization<0, int>::TemplateSpecialization() { \n <#code#>;\n}\n\nvoid TemplateSpecialization<0, int>::function() { \n <#code#>;\n}\n\nvoid TemplateSpecialization<0, int>::operator()(int) const { \n <#code#>;\n}\n\nTemplateSpecialization<0, int>::operator int() const { \n <#code#>;\n}\n" [[@LINE-1]]:3 +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=template-specialization -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK-SPECIALIZATION %s + +template +class TemplateSpecialization { +// template-partial-specialization-begin: +0:1 + void function(); +// template-partial-specialization-end: +0:1 +}; + +// RUN: not clang-refactor-test perform -action implement-declared-methods -selected=template-partial-specialization %s -DINNER_TEMPLATE 2>&1 | FileCheck --check-prefix=CHECK-TEMPLATE-NO %s + +struct ProhibitTemplateFunctions { +// template-function-begin: +0:1 + void function(); + template + void functionTemplate(const T &); + void anotherFunction(); +// template-function-end: +0:1 +}; +// CHECK-FUNCTION-TEMPLATE: "{{.*}}implement-declared-methods.cpp" "\n\nvoid ProhibitTemplateFunctions::function() { \n <#code#>;\n}\n\nvoid ProhibitTemplateFunctions::anotherFunction() { \n <#code#>;\n}\n" [[@LINE-1]]:3 +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=template-function -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK-FUNCTION-TEMPLATE %s diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m new file mode 100644 index 0000000000000..3949e32650c48 --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m @@ -0,0 +1,55 @@ + +@interface MyClass + +// all-methods-begin: +1:1 +- (void)method; + ++ (void)classMethod; + +- (void)implementedMethod; + +- (void)method:(int)x with:(int)y; +// all-methods-end: +0:1 + +@end + +#ifndef NO_IMPL +@implementation MyClass + +- (void)someOtherMethod { } + +@end +// CHECK1: "{{.*}}implement-declared-methods.m" "- (void)method { \n <#code#>;\n}\n\n+ (void)classMethod { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n- (void)method:(int)x with:(int)y { \n <#code#>;\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 +// CHECK2: "{{.*}}implement-declared-methods.m" "- (void)method { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n" [[@LINE-2]]:1 +#endif +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-mix-impl %s | FileCheck --check-prefix=CHECK2 %s + +// query-all-impl: [ { name: ast.producer.query, filenameResult: "%s" } , { name: decl.query , predicateResults: [{name: decl.isDefined, intValues: [0, 0, 0, 0] }] }] +// query-mix-impl: [ { name: ast.producer.query, filenameResult: "%s" } , { name: decl.query , predicateResults: [{name: decl.isDefined, intValues: [0, 1, 0, 1] }] }] + +// Empty continuation TU or TU without @implementation should produce an error: +// RUN: not clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%S/Inputs/empty.cpp -query-results=query-all-impl %s 2>&1 | FileCheck --check-prefix=CHECK-EMPTY-ERR %s +// RUN: not clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-all-impl %s -DNO_IMPL 2>&1 | FileCheck --check-prefix=CHECK-EMPTY-ERR %s +// CHECK-EMPTY-ERR: failed to perform the refactoring continuation (the target @interface is not implemented in the continuation AST unit)! + +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%S/Inputs/objcClass.m -query-results=query-all-impl %s -DNO_IMPL | FileCheck --check-prefix=CHECK1 %S/Inputs/objcClass.m +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%S/Inputs/objcClass.m -query-results=query-mix-impl %s -DNO_IMPL -DMIX_IMPL | FileCheck --check-prefix=CHECK2 %S/Inputs/objcClass.m + +@interface MyClass (Category) + +// all-category-methods-begin: +1:1 +- (void)categoryMethod; ++ (MyClass *)classCategoryMethod; +// all-category-methods-end: +0:1 + +@end + +@implementation MyClass (Category) + +- (void)anotherMethod { +} + +@end +// CHECK3: "{{.*}}implement-declared-methods.m" "- (void)categoryMethod { \n <#code#>;\n}\n\n+ (MyClass *)classCategoryMethod { \n <#code#>;\n}\n\n" [[@LINE-1]]:1 +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-category-methods -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK3 %s diff --git a/clang/test/Refactor/ImplementDeclaredMethods/local-record.cpp b/clang/test/Refactor/ImplementDeclaredMethods/local-record.cpp new file mode 100644 index 0000000000000..6fbc3e46808d9 --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/local-record.cpp @@ -0,0 +1,30 @@ +void function() { +struct Class { + // one-method: +2:3 + // all-methods-begin: +1:1 + Class(); + + ~Class(); // comment + + int constOverride() const override; + + // comment + void method(const int &value, int defaultParam = 20) + ; + + void implementedMethod() const { + + } +}; +// all-methods-end: -1:1 +} +// CHECK1: " { \n <#code#>;\n}" [[@LINE-16]]:10 -> [[@LINE-16]]:11 +// RUN: clang-refactor-test perform -action implement-declared-methods -at=one-method %s | FileCheck --check-prefix=CHECK1 %s + +// CHECK2: " { \n <#code#>;\n}" [[@LINE-19]]:10 -> [[@LINE-19]]:11 +// CHECK2-NEXT: "" [[@LINE-18]]:11 -> [[@LINE-18]]:12 +// CHECK2-NEXT: " { \n <#code#>;\n}" [[@LINE-19]]:23 -> [[@LINE-19]]:23 +// CHECK2-NEXT: " { \n <#code#>;\n}" [[@LINE-18]]:37 -> [[@LINE-18]]:38 +// CHECK2-NEXT: " { \n <#code#>;\n}" [[@LINE-15]]:7 -> [[@LINE-15]]:8 +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods %s | FileCheck --check-prefix=CHECK2 %s + diff --git a/clang/test/Refactor/ImplementDeclaredMethods/prohibited-methods.cpp b/clang/test/Refactor/ImplementDeclaredMethods/prohibited-methods.cpp new file mode 100644 index 0000000000000..6f434d0d02210 --- /dev/null +++ b/clang/test/Refactor/ImplementDeclaredMethods/prohibited-methods.cpp @@ -0,0 +1,19 @@ +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods %s -std=c++11 | FileCheck %s +// default, deleted and pure methods should not be implemented! + +void function() { +struct Class { +// all-methods-begin: +1:1 + Class(); +// CHECK: " { \n <#code#>;\n}" [[@LINE-1]]:10 -> [[@LINE-1]]:11 + + Class(const Class &Other) = default; + Class(const Class &&Other) = delete; + + virtual void pureMethod(int x) = 0; + + virtual void method() const; +// CHECK-NEXT: " { \n <#code#>;\n}" [[@LINE-1]]:30 -> [[@LINE-1]]:31 +}; +// all-methods-end: -1:1 +} diff --git a/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-initiate.m b/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-initiate.m new file mode 100644 index 0000000000000..335608e68078b --- /dev/null +++ b/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-initiate.m @@ -0,0 +1,26 @@ +@class NSString; + +void initiate() { + NSString *string = @"hello"; + const char *cString = "world"; +} + +// RUN: clang-refactor-test list-actions -at=%s:4:25 %s | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Wrap in NSLocalizedString + +// Ensure the the action can be initiated in the string literal: + +// RUN: clang-refactor-test initiate -action localize-objc-string-literal -in=%s:4:22-30 %s | FileCheck --check-prefix=CHECK1 %s +// CHECK1: Initiated the 'localize-objc-string-literal' action at 4:22 + +// Ensure that the action can't be initiated in other places: + +// RUN: not clang-refactor-test initiate -action localize-objc-string-literal -in=%s:1:1-10 -in=%s:3:1-18 -in=%s:4:1-21 -in=%s:5:1-32 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s +// CHECK-NO: Failed to initiate the refactoring action + +// Ensure that the action can be initiated using a selection, and only when that +// selection doesn't go out of the string. + +// RUN: clang-refactor-test initiate -action localize-objc-string-literal -selected=%s:4:22-4:30 -selected=%s:4:25-4:30 -selected=%s:4:22-4:27 -selected=%s:4:25-4:27 -selected=%s:4:24-4:29 -selected=%s:4:23-4:30 %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: not clang-refactor-test initiate -action localize-objc-string-literal -selected=%s:4:20-4:30 -selected=%s:4:1-4:25 -selected=%s:4:25-5:5 -selected=%s:3:17-6:2 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-perform.m b/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-perform.m new file mode 100644 index 0000000000000..8c0de42faa39a --- /dev/null +++ b/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-perform.m @@ -0,0 +1,9 @@ +@class NSString; + +void perform() { + NSString *string = @"hello"; +} +// CHECK1: "NSLocalizedString(" [[@LINE-2]]:22 -> [[@LINE-2]]:22 +// CHECK1-NEXT: ", @"")" [[@LINE-3]]:30 -> [[@LINE-3]]:30 +// RUN: clang-refactor-test perform -action localize-objc-string-literal -at=%s:4:22 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test perform -action localize-objc-string-literal -selected=%s:4:23-4:30 %s | FileCheck --check-prefix=CHECK1 %s diff --git a/clang/test/Refactor/Rename/CanonicalizeInstantiatedDecls.cpp b/clang/test/Refactor/Rename/CanonicalizeInstantiatedDecls.cpp new file mode 100644 index 0000000000000..f520a2e004233 --- /dev/null +++ b/clang/test/Refactor/Rename/CanonicalizeInstantiatedDecls.cpp @@ -0,0 +1,86 @@ + +template +class BaseTemplate { +public: + T baseTemplateFunction(); + + T baseTemplateField; + + struct NestedBaseType { }; +}; + +template +class TemplateClass: public BaseTemplate { +public: + T function() { return T(); } + + static void staticFunction() { } + + T field; + + struct NestedType { + T nestedField; + + class SubNestedType { + public: + SubNestedType(int); + }; + using TypeAlias = T; + + typedef int Typedef; + + enum Enum { + EnumCase + }; + }; +}; + +void canonicalizeInstaniationReferences(TemplateClass &object) { + (void)object.function(); +// CHECK1: 'c:@ST>2#T#T@TemplateClass@F@function#' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):16 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK1 %s + (void)object.field; +// CHECK2: 'c:@ST>2#T#T@TemplateClass@FI@field' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):16 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK2 %s + (void)object.baseTemplateFunction(); +// CHECK3: 'c:@ST>1#T@BaseTemplate@F@baseTemplateFunction#' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):16 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK3 %s + (void)object.baseTemplateField; +// CHECK4: 'c:@ST>1#T@BaseTemplate@FI@baseTemplateField' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):16 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK4 %s + + TemplateClass::staticFunction(); +// CHECK5: 'c:@ST>2#T#T@TemplateClass@F@staticFunction#S' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):30 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK5 %s + + TemplateClass::NestedBaseType nestedBaseType; +// CHECK6: 'c:@ST>1#T@BaseTemplate@S@NestedBaseType' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):30 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK6 %s + TemplateClass::NestedType nestedSubType; +// CHECK7: 'c:@ST>2#T#T@TemplateClass@S@NestedType' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):30 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK7 %s + (void)nestedSubType.nestedField; +// CHECK8: 'c:@ST>2#T#T@TemplateClass@S@NestedType@FI@nestedField' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):23 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK8 %s + + typedef TemplateClass TT; + TT::NestedType::SubNestedType subNestedType(0); +// CHECK9: 'c:@ST>2#T#T@TemplateClass@S@NestedType' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):7 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK9 %s +// CHECK10: 'c:@ST>2#T#T@TemplateClass@S@NestedType@S@SubNestedType' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-4):19 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK10 %s + + TT::NestedType::TypeAlias nestedTypeAlias; +// CHECK11: 'c:@ST>2#T#T@TemplateClass@S@NestedType@TypeAlias' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):19 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK11 %s + TT::NestedType::Typedef nestedTypedef; +// CHECK12: 'c:{{.*}}CanonicalizeInstantiatedDecls.cpp@ST>2#T#T@TemplateClass@S@NestedType@T@Typedef' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):19 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK12 %s + + TT::NestedType::Enum nestedEnum; +// CHECK13: 'c:@ST>2#T#T@TemplateClass@S@NestedType@E@Enum' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):19 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK13 %s + (void)TT::NestedType::Enum::EnumCase; +// CHECK14: 'c:@ST>2#T#T@TemplateClass@S@NestedType@E@Enum@EnumCase' +// RUN: clang-refactor-test rename-initiate -at=%s:%(line-2):31 -new-name=x -dump-symbols %s | FileCheck --check-prefix=CHECK14 %s +} diff --git a/clang/test/Refactor/Rename/ClassAsTemplateArgument.cpp b/clang/test/Refactor/Rename/ClassAsTemplateArgument.cpp new file mode 100644 index 0000000000000..63107801a75e4 --- /dev/null +++ b/clang/test/Refactor/Rename/ClassAsTemplateArgument.cpp @@ -0,0 +1,20 @@ +class Foo /* Test 1 */ {}; // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 + +template +void func() {} + +template +class Baz {}; + +int main() { + func(); /* Test 2 */ // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:11 + Baz /* Test 3 */ obj; // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 + return 0; +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:1:7 -new-name=Bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:10:8 -new-name=Bar %s | FileCheck %s +// Test 3. +// RUN: clang-refactor-test rename-initiate -at=%s:11:7 -new-name=Bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/ClassSimpleRenaming.cpp b/clang/test/Refactor/Rename/ClassSimpleRenaming.cpp new file mode 100644 index 0000000000000..78d213c4bdf97 --- /dev/null +++ b/clang/test/Refactor/Rename/ClassSimpleRenaming.cpp @@ -0,0 +1,38 @@ +class Foo /* Test 1 */ { // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 +public: + void foo(int x); +}; + +void Foo::foo(int x) /* Test 2 */ {} // CHECK: rename [[@LINE]]:6 -> [[@LINE]]:9 + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:1:7 -new-name=Bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:6:6 -new-name=Bar %s | FileCheck %s + +struct ForwardDeclaration; // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:26 + +ForwardDeclaration *variable; // CHECK2: rename [[@LINE]]:1 -> [[@LINE]]:19 + +// RUN: clang-refactor-test rename-initiate -at=%s:13:8 -at=%s:15:1 -new-name=Bar %s | FileCheck --check-prefix=CHECK2 %s + +template +struct SpecializationWithoutDefinition { }; // CHECK3: rename [[@LINE]]:8 -> [[@LINE]]:39 + +template<> +struct SpecializationWithoutDefinition { }; // CHECK3: rename [[@LINE]]:8 -> [[@LINE]]:39 + +template<> +struct SpecializationWithoutDefinition; // CHECK3: rename [[@LINE]]:8 -> [[@LINE]]:39 + +// RUN: clang-refactor-test rename-initiate -at=%s:20:8 -at=%s:23:8 -new-name=Bar %s | FileCheck --check-prefix=CHECK3 %s + +template +struct Class { + void method(); +}; + +template // CHECK4: [[@LINE]]:19 -> [[@LINE]]:20 +void Class::method() { } // CHECK4: [[@LINE]]:12 -> [[@LINE]]:13 + +// RUN: clang-refactor-test rename-initiate -at=%s:35:19 -new-name=Bar %s | FileCheck --check-prefix=CHECK4 %s diff --git a/clang/test/Refactor/Rename/ComplexFunctionOverride.cpp b/clang/test/Refactor/Rename/ComplexFunctionOverride.cpp new file mode 100644 index 0000000000000..52f09e870d864 --- /dev/null +++ b/clang/test/Refactor/Rename/ComplexFunctionOverride.cpp @@ -0,0 +1,76 @@ +struct A { + virtual void foo() {} /* Test 1 */ // CHECK: rename [[@LINE]]:16 -> [[@LINE]]:19 +}; + +struct B : A { + void foo() override {} /* Test 2 */ // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:11 +}; + +struct C : B { + void foo() override {} /* Test 3 */ // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:11 +}; + +struct D : B { + void foo() override {} /* Test 4 */ // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:11 +}; + +struct E : D { + void foo() override {} /* Test 5 */ // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:11 +}; + +int main() { + A a; + a.foo(); // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 + B b; + b.foo(); // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 + C c; + c.foo(); // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 + D d; + d.foo(); // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 + E e; + e.foo(); // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 + return 0; +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:2:16 -new-name=bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:6:8 -new-name=bar %s | FileCheck %s +// Test 3. +// RUN: clang-refactor-test rename-initiate -at=%s:10:8 -new-name=bar %s | FileCheck %s +// Test 4. +// RUN: clang-refactor-test rename-initiate -at=%s:14:8 -new-name=bar %s | FileCheck %s +// Test 5. +// RUN: clang-refactor-test rename-initiate -at=%s:18:8 -new-name=bar %s | FileCheck %s + +// Check virtual inheritance + +struct A2 { + virtual void foo() {} // CHECK-VIRT: rename [[@LINE]]:16 -> [[@LINE]]:19 +}; +struct B2 : virtual A2 { + void foo() { } // CHECK-VIRT: rename [[@LINE]]:8 -> [[@LINE]]:11 +}; +struct C2 : virtual A2 { + void foo() override { } // CHECK-VIRT: rename [[@LINE]]:8 -> [[@LINE]]:11 +}; +struct D2 : B2, C2 { + void foo() override { // CHECK-VIRT: rename [[@LINE]]:8 -> [[@LINE]]:11 + A2::foo(); // CHECK-VIRT: rename [[@LINE]]:9 -> [[@LINE]]:12 + } +}; + +int bar() { + A2 a; + a.foo(); // CHECK-VIRT: rename [[@LINE]]:5 -> [[@LINE]]:8 + D2 d; + d.foo(); // CHECK-VIRT: rename [[@LINE]]:5 -> [[@LINE]]:8 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:49:16 -new-name=bar %s | FileCheck --check-prefix=CHECK-VIRT %s +// RUN: clang-refactor-test rename-initiate -at=%s:52:8 -new-name=bar %s | FileCheck --check-prefix=CHECK-VIRT %s +// RUN: clang-refactor-test rename-initiate -at=%s:55:8 -new-name=bar %s | FileCheck --check-prefix=CHECK-VIRT %s +// RUN: clang-refactor-test rename-initiate -at=%s:58:8 -new-name=bar %s | FileCheck --check-prefix=CHECK-VIRT %s +// RUN: clang-refactor-test rename-initiate -at=%s:59:9 -new-name=bar %s | FileCheck --check-prefix=CHECK-VIRT %s +// RUN: clang-refactor-test rename-initiate -at=%s:65:5 -new-name=bar %s | FileCheck --check-prefix=CHECK-VIRT %s +// RUN: clang-refactor-test rename-initiate -at=%s:67:5 -new-name=bar %s | FileCheck --check-prefix=CHECK-VIRT %s diff --git a/clang/test/Refactor/Rename/ComplicatedClassType.cpp b/clang/test/Refactor/Rename/ComplicatedClassType.cpp new file mode 100644 index 0000000000000..eb0fea88e081c --- /dev/null +++ b/clang/test/Refactor/Rename/ComplicatedClassType.cpp @@ -0,0 +1,62 @@ +// Forward declaration. +class Foo; /* Test 1 */ // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 + +class Baz { + virtual int getValue() const = 0; +}; + +class Foo : public Baz { /* Test 2 */// CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 +public: + Foo(int value = 0) : x(value) {} // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + + Foo &operator++(int) { // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + x++; + return *this; + } + + bool operator<(Foo const &rhs) { // CHECK: rename [[@LINE]]:18 -> [[@LINE]]:21 + return this->x < rhs.x; + } + + int getValue() const { + return 0; + } + +private: + int x; +}; + +int main() { + Foo *Pointer = 0; // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + Foo Variable = Foo(10); // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + // CHECK: rename [[@LINE-1]]:18 -> [[@LINE-1]]:21 + for (Foo it; it < Variable; it++) { // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:11 + } + const Foo *C = new Foo(); // CHECK: rename [[@LINE]]:9 -> [[@LINE]]:12 + // CHECK: rename [[@LINE-1]]:22 -> [[@LINE-1]]:25 + const_cast(C)->getValue(); // CHECK: rename [[@LINE]]:14 -> [[@LINE]]:17 + Foo foo; // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + const Baz &BazReference = foo; + const Baz *BazPointer = &foo; + dynamic_cast(BazReference).getValue(); /* Test 3 */ // CHECK: rename [[@LINE]]:22 -> [[@LINE]]:25 + dynamic_cast(BazPointer)->getValue(); /* Test 4 */ // CHECK: rename [[@LINE]]:22 -> [[@LINE]]:25 + reinterpret_cast(BazPointer)->getValue(); /* Test 5 */ // CHECK: rename [[@LINE]]:26 -> [[@LINE]]:29 + static_cast(BazReference).getValue(); /* Test 6 */ // CHECK: rename [[@LINE]]:21 -> [[@LINE]]:24 + static_cast(BazPointer)->getValue(); /* Test 7 */ // CHECK: rename [[@LINE]]:21 -> [[@LINE]]:24 + return 0; +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:2:7 -new-name=Bar %s -frtti | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:8:7 -new-name=Bar %s -frtti | FileCheck %s +// Test 3. +// RUN: clang-refactor-test rename-initiate -at=%s:41:22 -new-name=Bar %s -frtti | FileCheck %s +// Test 4. +// RUN: clang-refactor-test rename-initiate -at=%s:42:22 -new-name=Bar %s -frtti | FileCheck %s +// Test 5. +// RUN: clang-refactor-test rename-initiate -at=%s:43:26 -new-name=Bar %s -frtti | FileCheck %s +// Test 6. +// RUN: clang-refactor-test rename-initiate -at=%s:44:21 -new-name=Bar %s -frtti | FileCheck %s +// Test 7. +// RUN: clang-refactor-test rename-initiate -at=%s:45:21 -new-name=Bar %s -frtti | FileCheck %s diff --git a/clang/test/Refactor/Rename/Ctor.cpp b/clang/test/Refactor/Rename/Ctor.cpp new file mode 100644 index 0000000000000..73ac0206c3255 --- /dev/null +++ b/clang/test/Refactor/Rename/Ctor.cpp @@ -0,0 +1,29 @@ +class Foo { // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 +public: + Foo(); // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + Foo(int x, int y); // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + ~Foo(); // CHECK: rename [[@LINE]]:4 -> [[@LINE]]:7 +}; + +Foo::Foo() {} // CHECK: rename [[@LINE]]:1 -> [[@LINE]]:4 +// CHECK: rename [[@LINE-1]]:6 -> [[@LINE-1]]:9 + +Foo::Foo(int x, int y) { } // CHECK: rename [[@LINE]]:1 -> [[@LINE]]:4 +// CHECK: rename [[@LINE-1]]:6 -> [[@LINE-1]]:9 + +Foo::~Foo() {} // CHECK: rename [[@LINE]]:1 -> [[@LINE]]:4 +// CHECK: rename [[@LINE-1]]:7 -> [[@LINE-1]]:10 + +Foo f(const Foo &Other) { // CHECK: rename [[@LINE]]:1 -> [[@LINE]]:4 + // CHECK: rename [[@LINE-1]]:13 -> [[@LINE-1]]:16 + return Foo(Other); // CHECK: rename [[@LINE]]:10 -> [[@LINE]]:13 +} + +// Declarations. +// RUN: clang-refactor-test rename-initiate -at=%s:3:3 -at=%s:4:3 -at=%s:5:4 -new-name=Bar %s | FileCheck %s + +// Definitions. +// RUN: clang-refactor-test rename-initiate -at=%s:8:6 -at=%s:11:6 -at=%s:14:7 -new-name=Bar %s | FileCheck %s + +// Implicit copy constructor. +// RUN: clang-refactor-test rename-initiate -at=%s:19:10 -new-name=Bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/CtorInitializer.cpp b/clang/test/Refactor/Rename/CtorInitializer.cpp new file mode 100644 index 0000000000000..fe5b6098ea55c --- /dev/null +++ b/clang/test/Refactor/Rename/CtorInitializer.cpp @@ -0,0 +1,14 @@ +class Baz {}; + +class Qux { + Baz Foo; /* Test 1 */ // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 +public: + Qux(); +}; + +Qux::Qux() : Foo() /* Test 2 */ {} // CHECK: rename [[@LINE]]:14 -> [[@LINE]]:17 + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:4:7 -new-name=Bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:9:14 -new-name=Bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/DeclRefExpr.cpp b/clang/test/Refactor/Rename/DeclRefExpr.cpp new file mode 100644 index 0000000000000..3808343538716 --- /dev/null +++ b/clang/test/Refactor/Rename/DeclRefExpr.cpp @@ -0,0 +1,21 @@ +class C { +public: + static int Foo; /* Test 1 */ // CHECK: rename [[@LINE]]:14 -> [[@LINE]]:17 +}; + +int foo(int x) { return 0; } +#define MACRO(a) foo(a) + +int main() { + C::Foo = 1; /* Test 2 */ // CHECK: rename [[@LINE]]:6 -> [[@LINE]]:9 + MACRO(C::Foo); // CHECK: rename [[@LINE]]:12 -> [[@LINE]]:15 + int y = C::Foo; /* Test 3 */ // CHECK: rename [[@LINE]]:14 -> [[@LINE]]:17 + return 0; +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:3:14 -new-name=Bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:10:6 -new-name=Bar %s | FileCheck %s +// Test 3. +// RUN: clang-refactor-test rename-initiate -at=%s:12:14 -new-name=Bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/DependentExpressions.cpp b/clang/test/Refactor/Rename/DependentExpressions.cpp new file mode 100644 index 0000000000000..0c7612cba9b80 --- /dev/null +++ b/clang/test/Refactor/Rename/DependentExpressions.cpp @@ -0,0 +1,72 @@ +int invalid; + +class Base { + void baseFunction(); // CHECK3: [[@LINE]]:8 -> [[@LINE]]:20 + + int baseField; + + static void staticBaseFunction(); // CHECK7: [[@LINE]]:15 -> [[@LINE]]:33 +}; + +template +class BaseTemplate { +public: + T baseTemplateFunction(); + + T baseTemplateField; // CHECK4: [[@LINE]]:5 -> [[@LINE]]:22 + + static T baseTemplateVariable; // CHECK8: [[@LINE]]:12 -> [[@LINE]]:32 +}; + +template +class TemplateClass: public Base , public BaseTemplate { +public: + ~TemplateClass(); + + T function() { } // CHECK1: [[@LINE]]:5 -> [[@LINE]]:13 + + static void staticFunction() { } // CHECK5: [[@LINE]]:15 -> [[@LINE]]:29 + + T field; // CHECK2: [[@LINE]]:5 -> [[@LINE]]:10 + + static T variable; // CHECK6: [[@LINE]]:12 -> [[@LINE]]:20 + + struct Struct { }; // CHECK9: [[@LINE]]:10 -> [[@LINE]]:16 + + enum Enum { EnumValue }; // CHECK10: [[@LINE]]:8 -> [[@LINE]]:12 + + using TypeAlias = S; // CHECK11: [[@LINE]]:9 -> [[@LINE]]:18 + typedef T Typedef; + + void overload1(const T &); + void overload1(const S &); +}; + +template +void renameSimpleDependentDeclarations(const TemplateClass &object) { + + object.function(); // CHECK1: [[@LINE]]:10 -> [[@LINE]]:18 +// RUN: clang-refactor-test rename-initiate -at=%s:26:5 -at=%s:48:10 -new-name=x %s | FileCheck --check-prefix=CHECK1 %s + object.field; // CHECK2: [[@LINE]]:10 -> [[@LINE]]:15 +// RUN: clang-refactor-test rename-initiate -at=%s:30:5 -at=%s:50:10 -new-name=x %s | FileCheck --check-prefix=CHECK2 %s + object.baseFunction(); // CHECK3: [[@LINE]]:10 -> [[@LINE]]:22 +// RUN: clang-refactor-test rename-initiate -at=%s:4:8 -at=%s:52:10 -new-name=x %s | FileCheck --check-prefix=CHECK3 %s + object.baseTemplateField; // CHECK4: [[@LINE]]:10 -> [[@LINE]]:27 +// RUN: clang-refactor-test rename-initiate -at=%s:16:5 -at=%s:54:10 -new-name=x %s | FileCheck --check-prefix=CHECK4 %s + + TemplateClass::staticFunction(); // CHECK5: [[@LINE]]:24 -> [[@LINE]]:38 +// RUN: clang-refactor-test rename-initiate -at=%s:28:15 -at=%s:57:24 -new-name=x %s | FileCheck --check-prefix=CHECK5 %s + TemplateClass::variable; // CHECK6: [[@LINE]]:24 -> [[@LINE]]:32 +// RUN: clang-refactor-test rename-initiate -at=%s:32:12 -at=%s:59:24 -new-name=x %s | FileCheck --check-prefix=CHECK6 %s + TemplateClass::staticBaseFunction(); // CHECK7: [[@LINE]]:24 -> [[@LINE]]:42 +// RUN: clang-refactor-test rename-initiate -at=%s:8:15 -at=%s:61:24 -new-name=x %s | FileCheck --check-prefix=CHECK7 %s + TemplateClass::baseTemplateVariable; // CHECK8: [[@LINE]]:24 -> [[@LINE]]:44 +// RUN: clang-refactor-test rename-initiate -at=%s:18:12 -at=%s:63:24 -new-name=x %s | FileCheck --check-prefix=CHECK8 %s + + typename TemplateClass::Struct Val; // CHECK9: [[@LINE]]:33 -> [[@LINE]]:39 +// RUN: clang-refactor-test rename-initiate -at=%s:34:10 -at=%s:66:33 -new-name=x %s | FileCheck --check-prefix=CHECK9 %s + typename TemplateClass::Enum EnumVal; // CHECK10: [[@LINE]]:33 -> [[@LINE]]:37 +// RUN: clang-refactor-test rename-initiate -at=%s:36:8 -at=%s:68:33 -new-name=x %s | FileCheck --check-prefix=CHECK10 %s + typename TemplateClass::TypeAlias Val2; // CHECK11: [[@LINE]]:33 -> [[@LINE]]:42 +// RUN: clang-refactor-test rename-initiate -at=%s:38:9 -at=%s:70:33 -new-name=x %s | FileCheck --check-prefix=CHECK11 %s +} diff --git a/clang/test/Refactor/Rename/Field.cpp b/clang/test/Refactor/Rename/Field.cpp new file mode 100644 index 0000000000000..94b144add1650 --- /dev/null +++ b/clang/test/Refactor/Rename/Field.cpp @@ -0,0 +1,12 @@ +class Baz { + int Foo; /* Test 1 */ // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 +public: + Baz(); +}; + +Baz::Baz() : Foo(0) /* Test 2 */ {} // CHECK: rename [[@LINE]]:14 -> [[@LINE]]:17 + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:2:7 -new-name=Bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:7:14 -new-name=Bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/FunctionMacro.cpp b/clang/test/Refactor/Rename/FunctionMacro.cpp new file mode 100644 index 0000000000000..946c4d11fa993 --- /dev/null +++ b/clang/test/Refactor/Rename/FunctionMacro.cpp @@ -0,0 +1,17 @@ +#define moo foo + +int foo() /* Test 1 */ { // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 + return 42; +} + +void boo(int value) {} + +void qoo() { + foo(); // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + boo(foo()); // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 + moo(); // CHECK: macro [[@LINE]]:3 -> [[@LINE]]:3 + boo(moo()); // CHECK: macro [[@LINE]]:7 -> [[@LINE]]:7 +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:3:5 -new-name=macro_function %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/FunctionOverride.cpp b/clang/test/Refactor/Rename/FunctionOverride.cpp new file mode 100644 index 0000000000000..9c6fc3431f2ee --- /dev/null +++ b/clang/test/Refactor/Rename/FunctionOverride.cpp @@ -0,0 +1,10 @@ +class A { virtual void foo(); /* Test 1 */ }; // CHECK: rename [[@LINE]]:24 -> [[@LINE]]:27 +class B : public A { void foo(); /* Test 2 */ }; // CHECK: rename [[@LINE]]:27 -> [[@LINE]]:30 +class C : public B { void foo(); /* Test 3 */ }; // CHECK: rename [[@LINE]]:27 -> [[@LINE]]:30 + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:1:24 -new-name=bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:2:27 -new-name=bar %s | FileCheck %s +// Test 3. +// RUN: clang-refactor-test rename-initiate -at=%s:3:27 -new-name=bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/IndexedObjCMessageSend.mm b/clang/test/Refactor/Rename/IndexedObjCMessageSend.mm new file mode 100644 index 0000000000000..1fd607098c239 --- /dev/null +++ b/clang/test/Refactor/Rename/IndexedObjCMessageSend.mm @@ -0,0 +1,882 @@ ++(BOOL) onEntity { + call() = [_undef_ivar name: @"string literal" method: @"string literal"]; + // CHECK1: [[@LINE-1]]:25 -> [[@LINE-1]]:29, [[@LINE-1]]:49 -> [[@LINE-1]]:55 +} +// RUN: clang-refactor-test rename-indexed-file -name=name:method -new-name=object:world -indexed-file=%s -indexed-at=2:25 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK1 %s + +-(const Object &) a_200 { + some_type_t world = @"string literal"; + [self withSomething: [] () { + some_type_t foo = [super struct: @"string literal" + world: [] () { + } name: [] { + + + } a_200: 12 a_200: globalArray[i]]; + // CHECK2: [[@LINE-6]]:29 -> [[@LINE-6]]:35, [[@LINE-5]]:2 -> [[@LINE-5]]:7, [[@LINE-4]]:5 -> [[@LINE-4]]:9, [[@LINE-1]]:4 -> [[@LINE-1]]:9, [[@LINE-1]]:14 -> [[@LINE-1]]:19 + + } onEntity: ']' perform: "]"]; + // CHECK3: [[@LINE-10]]:9 -> [[@LINE-10]]:22, [[@LINE-1]]:4 -> [[@LINE-1]]:12, [[@LINE-1]]:18 -> [[@LINE-1]]:25 +} +// RUN: clang-refactor-test rename-indexed-file -name=struct:world:name:a_200:a_200 -new-name=withSomething:foo:part:struct:method -indexed-file=%s -indexed-at=10:29 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:onEntity:perform -new-name=method:foo:name -indexed-file=%s -indexed-at=9:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK3 %s + +-(int) struct { + // comment +} + +-(int) bar { + return ']'; +} + +-(int) part { + call() = [self test: 12 method: globalArray[i] test: globalArray[i] world: ']']; + // CHECK4: [[@LINE-1]]:18 -> [[@LINE-1]]:22, [[@LINE-1]]:27 -> [[@LINE-1]]:33, [[@LINE-1]]:50 -> [[@LINE-1]]:54, [[@LINE-1]]:71 -> [[@LINE-1]]:76 + [_undef_ivar withSomething: @{ @1, @3 } onEntity: [] () { ([self foo: "]" piece: foo: (']')]); + // CHECK5: [[@LINE-1]]:71 -> [[@LINE-1]]:74, [[@LINE-1]]:80 -> [[@LINE-1]]:85, [[@LINE-1]]:88 -> [[@LINE-1]]:91 + }]; + // CHECK6: [[@LINE-3]]:16 -> [[@LINE-3]]:29, [[@LINE-3]]:43 -> [[@LINE-3]]:51 + int bar = [self name: ']' z_Z_42: [] () { [globalObject send: [self perform: globalArray[i] * "string" a_200: 12 class: 12 perform: ']' object: [] () { + }] other: 42]; + // CHECK7: [[@LINE-2]]:74 -> [[@LINE-2]]:81, [[@LINE-2]]:109 -> [[@LINE-2]]:114, [[@LINE-2]]:119 -> [[@LINE-2]]:124, [[@LINE-2]]:129 -> [[@LINE-2]]:136, [[@LINE-2]]:142 -> [[@LINE-2]]:148 + } bar: ^ { + [globalObject message] = ([self onEntity: globalArray[i] class: 12 foo: "string"]); + // CHECK8: [[@LINE-1]]:36 -> [[@LINE-1]]:44, [[@LINE-1]]:61 -> [[@LINE-1]]:66, [[@LINE-1]]:71 -> [[@LINE-1]]:74 + + } bar: ^ () { + some_type_t foo = [self perform: 12 bar: (^ () { + }) bar: ^ { + } perform: @"string literal" test: "]" + 12]; + // CHECK9: [[@LINE-3]]:28 -> [[@LINE-3]]:35, [[@LINE-3]]:40 -> [[@LINE-3]]:43, [[@LINE-2]]:6 -> [[@LINE-2]]:9, [[@LINE-1]]:5 -> [[@LINE-1]]:12, [[@LINE-1]]:32 -> [[@LINE-1]]:36 + + }]; + // CHECK10: [[@LINE-14]]:19 -> [[@LINE-14]]:23, [[@LINE-14]]:29 -> [[@LINE-14]]:35, [[@LINE-11]]:5 -> [[@LINE-11]]:8, [[@LINE-7]]:4 -> [[@LINE-7]]:7 + [self piece: @"string literal" method: globalArray[i] method: "string" class: globalArray[i]]; + // CHECK11: [[@LINE-1]]:9 -> [[@LINE-1]]:14, [[@LINE-1]]:34 -> [[@LINE-1]]:40, [[@LINE-1]]:57 -> [[@LINE-1]]:63, [[@LINE-1]]:74 -> [[@LINE-1]]:79 +} +// RUN: clang-refactor-test rename-indexed-file -name=test:method:test:world -new-name=a_200:struct:perform:piece -indexed-file=%s -indexed-at=33:18 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:piece:foo -new-name=world:onEntity:name -indexed-file=%s -indexed-at=35:71 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:onEntity -new-name=class:object -indexed-file=%s -indexed-at=35:16 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:a_200:class:perform:object -new-name=method:perform:class:foo:name -indexed-file=%s -indexed-at=39:74 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK7 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:class:foo -new-name=piece:onEntity:bar -indexed-file=%s -indexed-at=43:36 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:bar:bar:perform:test -new-name=foo:world:class:struct:z_Z_42 -indexed-file=%s -indexed-at=47:28 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK9 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:z_Z_42:bar:bar -new-name=world:piece:perform:test -indexed-file=%s -indexed-at=39:19 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK10 %s +// RUN: clang-refactor-test rename-indexed-file -name=piece:method:method:class -new-name=a_200:withSomething:onEntity:onEntity -indexed-file=%s -indexed-at=54:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK11 %s + ++(int) struct { + call() = [self name: ^ () { const Object & piece = 12; + } perform: @{ @1, @3 } a_200: ^ { + some_type_t foo = [self name: ']' name: ^ () { + } method: ']']; + // CHECK12: [[@LINE-2]]:28 -> [[@LINE-2]]:32, [[@LINE-2]]:38 -> [[@LINE-2]]:42, [[@LINE-1]]:5 -> [[@LINE-1]]:11 + + }]; + // CHECK13: [[@LINE-7]]:18 -> [[@LINE-7]]:22, [[@LINE-6]]:5 -> [[@LINE-6]]:12, [[@LINE-6]]:26 -> [[@LINE-6]]:31 +} +// RUN: clang-refactor-test rename-indexed-file -name=name:name:method -new-name=part:bar:usingThing -indexed-file=%s -indexed-at=69:28 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK12 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:perform:a_200 -new-name=a_200:piece:class -indexed-file=%s -indexed-at=67:18 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK13 %s + ++(BOOL) world { + int bar = ([_undef_ivar world: "string" struct: @"string literal" world: globalArray[i] + perform: ^ { // comment + } < "string" name: [] () { + int bar = [self a_200: "string" * 12 method: globalArray[i] usingThing: @"string literal" part: ']']; + // CHECK14: [[@LINE-1]]:20 -> [[@LINE-1]]:25, [[@LINE-1]]:41 -> [[@LINE-1]]:47, [[@LINE-1]]:64 -> [[@LINE-1]]:74, [[@LINE-1]]:94 -> [[@LINE-1]]:98 + + }]); + // CHECK15: [[@LINE-7]]:27 -> [[@LINE-7]]:32, [[@LINE-7]]:43 -> [[@LINE-7]]:49, [[@LINE-7]]:69 -> [[@LINE-7]]:74, [[@LINE-6]]:2 -> [[@LINE-6]]:9, [[@LINE-5]]:16 -> [[@LINE-5]]:20 + [self perform: [self object: globalArray[i] onEntity: /*]*/ [] () { + ; ;[self.undef_property test: [] () { + + + } onEntity: @"string literal" +]; + // CHECK16: [[@LINE-5]]:28 -> [[@LINE-5]]:32, [[@LINE-2]]:4 -> [[@LINE-2]]:12 + + } method: "]"] world: "]" withSomething: @"string literal"]; + // CHECK17: [[@LINE-9]]:24 -> [[@LINE-9]]:30, [[@LINE-9]]:47 -> [[@LINE-9]]:55, [[@LINE-1]]:4 -> [[@LINE-1]]:10 + // CHECK18: [[@LINE-10]]:9 -> [[@LINE-10]]:16, [[@LINE-2]]:17 -> [[@LINE-2]]:22, [[@LINE-2]]:28 -> [[@LINE-2]]:41 + [self usingThing: ^ { + ; + + } world: "string" test: "]"]; + // CHECK19: [[@LINE-4]]:9 -> [[@LINE-4]]:19, [[@LINE-1]]:4 -> [[@LINE-1]]:9, [[@LINE-1]]:20 -> [[@LINE-1]]:24 + call() = [self bar: [] { globalArray[12] = [self onEntity: ^ () { ] } foo: "string" piece: @{ @1, @3 } bar: ']']; + // CHECK20: [[@LINE-1]]:56 -> [[@LINE-1]]:64, [[@LINE-1]]:77 -> [[@LINE-1]]:80, [[@LINE-1]]:91 -> [[@LINE-1]]:96, [[@LINE-1]]:110 -> [[@LINE-1]]:113 + } +//comment + struct: [] { return ^ () { + }; + } piece: "string" onEntity: ^ () { ] }]; + // CHECK21: [[@LINE-7]]:18 -> [[@LINE-7]]:21, [[@LINE-3]]:2 -> [[@LINE-3]]:8, [[@LINE-1]]:5 -> [[@LINE-1]]:10, [[@LINE-1]]:21 -> [[@LINE-1]]:29 + some_type_t foo = [_undef_ivar world: 12 + bar: [] () { + [globalObject send: [super class: 12 world: [self perform: 12 perform: @{ @1, @3 } + object: "string"] class: "string" z_Z_42: @{ @1, @3 }] other: 42]; + // CHECK22: [[@LINE-2]]:54 -> [[@LINE-2]]:61, [[@LINE-2]]:66 -> [[@LINE-2]]:73, [[@LINE-1]]:2 -> [[@LINE-1]]:8 + // CHECK23: [[@LINE-3]]:31 -> [[@LINE-3]]:36, [[@LINE-3]]:41 -> [[@LINE-3]]:46, [[@LINE-2]]:20 -> [[@LINE-2]]:25, [[@LINE-2]]:36 -> [[@LINE-2]]:42 + + }]; + // CHECK24: [[@LINE-8]]:34 -> [[@LINE-8]]:39, [[@LINE-7]]:2 -> [[@LINE-7]]:5 +} +// RUN: clang-refactor-test rename-indexed-file -name=a_200:method:usingThing:part -new-name=foo:bar:bar:class -indexed-file=%s -indexed-at=83:20 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK14 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:struct:world:perform:name -new-name=perform:bar:object:foo:object -indexed-file=%s -indexed-at=80:27 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK15 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:onEntity -new-name=name:class -indexed-file=%s -indexed-at=89:28 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK16 %s +// RUN: clang-refactor-test rename-indexed-file -name=object:onEntity:method -new-name=usingThing:a_200:onEntity -indexed-file=%s -indexed-at=88:24 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK17 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:world:withSomething -new-name=onEntity:method:part -indexed-file=%s -indexed-at=88:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK18 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:world:test -new-name=name:object:onEntity -indexed-file=%s -indexed-at=99:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK19 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:foo:piece:bar -new-name=test:foo:test:name -indexed-file=%s -indexed-at=104:56 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK20 %s +// RUN: clang-refactor-test rename-indexed-file -name=bar:struct:piece:onEntity -new-name=usingThing:method:part:piece -indexed-file=%s -indexed-at=104:18 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK21 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:perform:object -new-name=usingThing:withSomething:withSomething -indexed-file=%s -indexed-at=114:54 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK22 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:world:class:z_Z_42 -new-name=bar:piece:class:z_Z_42 -indexed-file=%s -indexed-at=114:31 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK23 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:bar -new-name=foo:bar -indexed-file=%s -indexed-at=112:34 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK24 %s + +-(void) test { + call() = [self name: globalArray[i] onEntity: "]" bar: ^ { return [self withSomething: [] () { + + + } name: ']' part: 12]; + // CHECK25: [[@LINE-4]]:79 -> [[@LINE-4]]:92, [[@LINE-1]]:4 -> [[@LINE-1]]:8, [[@LINE-1]]:14 -> [[@LINE-1]]:18 + } usingThing: "]" object: ]; + // CHECK26: [[@LINE-6]]:18 -> [[@LINE-6]]:22, [[@LINE-6]]:39 -> [[@LINE-6]]:47, [[@LINE-6]]:53 -> [[@LINE-6]]:56, [[@LINE-1]]:5 -> [[@LINE-1]]:15, [[@LINE-1]]:21 -> [[@LINE-1]]:27 + [globalObject message] = [self a_200: globalArray[i] struct: @{ @1, @3 } +]; + // CHECK27: [[@LINE-2]]:34 -> [[@LINE-2]]:39, [[@LINE-2]]:56 -> [[@LINE-2]]:62 +} +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:name:part -new-name=z_Z_42:object:test -indexed-file=%s -indexed-at=135:79 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK25 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:onEntity:bar:usingThing:object -new-name=foo:method:perform:onEntity:perform -indexed-file=%s -indexed-at=135:18 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK26 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:struct -new-name=bar:perform -indexed-file=%s -indexed-at=142:34 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK27 %s + +-(int) class { + return /*]*/ [] () { + call() = [self struct: (@"string literal") method: ^ () { + } foo: @"string literal" onEntity: [] { + + + } < globalArray[i] method: ']']; + // CHECK28: [[@LINE-5]]:19 -> [[@LINE-5]]:25, [[@LINE-5]]:47 -> [[@LINE-5]]:53, [[@LINE-4]]:5 -> [[@LINE-4]]:8, [[@LINE-4]]:28 -> [[@LINE-4]]:36, [[@LINE-1]]:21 -> [[@LINE-1]]:27 + + }; + [self name: [] () { if ([] { + + + }) { + BOOL class = @{ @1, @3 }; + + } + } perform: 12 onEntity: @"string literal"]; + // CHECK29: [[@LINE-8]]:9 -> [[@LINE-8]]:13, [[@LINE-1]]:5 -> [[@LINE-1]]:12, [[@LINE-1]]:17 -> [[@LINE-1]]:25 + return globalArray[i] * [self withSomething: [self part: 12 name: ^ { [globalObject send: [super withSomething: [self test: [] () { + + + } world: [_undef_ivar piece: 12 class: @"string literal" test: "string" bar: /*]*/ globalArray[i]] +] withSomething: 12 name: ']' test: "]" usingThing: @"string literal"] other: 42]; + // CHECK30: [[@LINE-2]]:24 -> [[@LINE-2]]:29, [[@LINE-2]]:34 -> [[@LINE-2]]:39, [[@LINE-2]]:59 -> [[@LINE-2]]:63, [[@LINE-2]]:74 -> [[@LINE-2]]:77 + // CHECK31: [[@LINE-6]]:125 -> [[@LINE-6]]:129, [[@LINE-3]]:4 -> [[@LINE-3]]:9 + // CHECK32: [[@LINE-7]]:104 -> [[@LINE-7]]:117, [[@LINE-3]]:3 -> [[@LINE-3]]:16, [[@LINE-3]]:21 -> [[@LINE-3]]:25, [[@LINE-3]]:31 -> [[@LINE-3]]:35, [[@LINE-3]]:41 -> [[@LINE-3]]:51 + } part: @{ @1, @3 }] withSomething: ']' foo: globalArray[i]]; + // CHECK33: [[@LINE-9]]:54 -> [[@LINE-9]]:58, [[@LINE-9]]:63 -> [[@LINE-9]]:67, [[@LINE-1]]:5 -> [[@LINE-1]]:9 + // CHECK34: [[@LINE-10]]:33 -> [[@LINE-10]]:46, [[@LINE-2]]:24 -> [[@LINE-2]]:37, [[@LINE-2]]:43 -> [[@LINE-2]]:46 +} +// RUN: clang-refactor-test rename-indexed-file -name=struct:method:foo:onEntity:method -new-name=object:piece:struct:foo:name -indexed-file=%s -indexed-at=152:19 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK28 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:perform:onEntity -new-name=z_Z_42:bar:z_Z_42 -indexed-file=%s -indexed-at=160:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK29 %s +// RUN: clang-refactor-test rename-indexed-file -name=piece:class:test:bar -new-name=world:bar:object:perform -indexed-file=%s -indexed-at=172:24 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK30 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:world -new-name=a_200:bar -indexed-file=%s -indexed-at=169:125 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK31 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:withSomething:name:test:usingThing -new-name=bar:class:class:perform:perform -indexed-file=%s -indexed-at=169:104 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK32 %s +// RUN: clang-refactor-test rename-indexed-file -name=part:name:part -new-name=class:perform:name -indexed-file=%s -indexed-at=169:54 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK33 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:withSomething:foo -new-name=test:object:withSomething -indexed-file=%s -indexed-at=169:33 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK34 %s + ++(Object *) usingThing { + return "string"; +} + ++(void) class { + if (@{ @1, @3 }) { + globalArray[12] = [self foo: [] { [globalObject message] = [self object: @{ @1, @3 } usingThing: globalArray[i] perform: "]"]; + // CHECK35: [[@LINE-1]]:76 -> [[@LINE-1]]:82, [[@LINE-1]]:96 -> [[@LINE-1]]:106, [[@LINE-1]]:123 -> [[@LINE-1]]:130 + } class: usingThing: "]" perform: [self.undef_property name: ^ () { ] } piece: 12 name: ^ () { + globalArray[12] = [_undef_ivar foo: ']' foo: [] { + } + bar: ^ { + } + [] { + + + } +]; + // CHECK36: [[@LINE-8]]:35 -> [[@LINE-8]]:38, [[@LINE-8]]:44 -> [[@LINE-8]]:47, [[@LINE-6]]:2 -> [[@LINE-6]]:5 + + }]]; + // CHECK37: [[@LINE-12]]:59 -> [[@LINE-12]]:63, [[@LINE-12]]:76 -> [[@LINE-12]]:81, [[@LINE-12]]:86 -> [[@LINE-12]]:90 + // CHECK38: [[@LINE-15]]:31 -> [[@LINE-15]]:34, [[@LINE-13]]:5 -> [[@LINE-13]]:10, [[@LINE-13]]:13 -> [[@LINE-13]]:23, [[@LINE-13]]:29 -> [[@LINE-13]]:36 + + } +} +// RUN: clang-refactor-test rename-indexed-file -name=object:usingThing:perform -new-name=test:object:z_Z_42 -indexed-file=%s -indexed-at=195:76 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK35 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:foo:bar -new-name=method:part:class -indexed-file=%s -indexed-at=198:35 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK36 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:piece:name -new-name=perform:onEntity:struct -indexed-file=%s -indexed-at=197:59 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK37 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:class:usingThing:perform -new-name=name:z_Z_42:method:test -indexed-file=%s -indexed-at=195:31 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK38 %s + +-(some_type_t) struct { + globalArray[12] = [super a_200: globalArray[i] + name: globalArray[i] world: [] { + [globalObject send: [self part: ']' z_Z_42: ^ { + } + "]" struct: "string" + bar: [] () { + + + }] other: 42]; + // CHECK39: [[@LINE-6]]:30 -> [[@LINE-6]]:34, [[@LINE-6]]:40 -> [[@LINE-6]]:46, [[@LINE-5]]:11 -> [[@LINE-5]]:17, [[@LINE-4]]:2 -> [[@LINE-4]]:5 + + } onEntity: ']' == globalArray[i]]; + // CHECK40: [[@LINE-11]]:28 -> [[@LINE-11]]:33, [[@LINE-10]]:2 -> [[@LINE-10]]:6, [[@LINE-10]]:23 -> [[@LINE-10]]:28, [[@LINE-1]]:4 -> [[@LINE-1]]:12 + [self a_200: [] () { BOOL class = 12; + } part: ']' test: ^ () { int z_Z_42 = [self struct: "]" perform: "]" method: globalArray[i]]; + // CHECK41: [[@LINE-1]]:50 -> [[@LINE-1]]:56, [[@LINE-1]]:62 -> [[@LINE-1]]:69, [[@LINE-1]]:75 -> [[@LINE-1]]:81 + }]; + // CHECK42: [[@LINE-4]]:9 -> [[@LINE-4]]:14, [[@LINE-3]]:5 -> [[@LINE-3]]:9, [[@LINE-3]]:15 -> [[@LINE-3]]:19 +} +// RUN: clang-refactor-test rename-indexed-file -name=part:z_Z_42:struct:bar -new-name=world:a_200:a_200:test -indexed-file=%s -indexed-at=222:30 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK39 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:name:world:onEntity -new-name=object:object:perform:z_Z_42 -indexed-file=%s -indexed-at=220:28 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK40 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:perform:method -new-name=perform:test:bar -indexed-file=%s -indexed-at=233:50 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK41 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:part:test -new-name=class:method:test -indexed-file=%s -indexed-at=232:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK42 %s + ++(some_type_t) piece { + call() = [self class: "string" part: 12]; + // CHECK43: [[@LINE-1]]:18 -> [[@LINE-1]]:23, [[@LINE-1]]:34 -> [[@LINE-1]]:38 + [self struct: globalArray[i] part: "]" part: [self method: @"string literal" == globalArray[i] + [self onEntity: ^ { [_undef_ivar piece: @{ @1, @3 } world: globalArray[i]]; + // CHECK44: [[@LINE-1]]:137 -> [[@LINE-1]]:142, [[@LINE-1]]:156 -> [[@LINE-1]]:161 + } object: (^ () { [self class: 12 object: [] { + + + } == ^ () { + + + }]; + // CHECK45: [[@LINE-7]]:30 -> [[@LINE-7]]:35, [[@LINE-7]]:40 -> [[@LINE-7]]:46 + }) piece: ']' world: 12 part: (12)] struct: /*]*/ globalArray[i] foo: [self object: ^ { // comment + // CHECK46: [[@LINE-11]]:106 -> [[@LINE-11]]:114, [[@LINE-9]]:5 -> [[@LINE-9]]:11, [[@LINE-1]]:6 -> [[@LINE-1]]:11, [[@LINE-1]]:17 -> [[@LINE-1]]:22, [[@LINE-1]]:27 -> [[@LINE-1]]:31 + } world: (^ { + int bar = [_undef_ivar test: globalArray[i] + "string" == ']' class: 12]; + // CHECK47: [[@LINE-1]]:27 -> [[@LINE-1]]:31, [[@LINE-1]]:66 -> [[@LINE-1]]:71 + + }) part: ']' +] perform: "]"] perform: @{ @1, @3 }]; + // CHECK48: [[@LINE-8]]:79 -> [[@LINE-8]]:85, [[@LINE-6]]:5 -> [[@LINE-6]]:10, [[@LINE-2]]:5 -> [[@LINE-2]]:9 + // CHECK49: [[@LINE-19]]:54 -> [[@LINE-19]]:60, [[@LINE-9]]:39 -> [[@LINE-9]]:45, [[@LINE-9]]:68 -> [[@LINE-9]]:71, [[@LINE-2]]:3 -> [[@LINE-2]]:10 + // CHECK50: [[@LINE-20]]:9 -> [[@LINE-20]]:15, [[@LINE-20]]:32 -> [[@LINE-20]]:36, [[@LINE-20]]:42 -> [[@LINE-20]]:46, [[@LINE-3]]:17 -> [[@LINE-3]]:24 + some_type_t foo = [self.undef_property class: "]" part: [] { + [self bar: [] () { + } z_Z_42: "]" name: globalArray[i]]; + // CHECK51: [[@LINE-2]]:10 -> [[@LINE-2]]:13, [[@LINE-1]]:5 -> [[@LINE-1]]:11, [[@LINE-1]]:17 -> [[@LINE-1]]:21 + + } + onEntity: ^ { return [super withSomething: "string" usingThing: @"string literal" test: @"string literal" withSomething: ']' world: [] () { + + + } +]; + // CHECK52: [[@LINE-5]]:34 -> [[@LINE-5]]:47, [[@LINE-5]]:58 -> [[@LINE-5]]:68, [[@LINE-5]]:88 -> [[@LINE-5]]:92, [[@LINE-5]]:112 -> [[@LINE-5]]:125, [[@LINE-5]]:131 -> [[@LINE-5]]:136 + } class: 12 struct: @"string literal" == globalArray[i]]; + // CHECK53: [[@LINE-13]]:42 -> [[@LINE-13]]:47, [[@LINE-13]]:53 -> [[@LINE-13]]:57, [[@LINE-7]]:2 -> [[@LINE-7]]:10, [[@LINE-1]]:5 -> [[@LINE-1]]:10, [[@LINE-1]]:15 -> [[@LINE-1]]:21 +} +// RUN: clang-refactor-test rename-indexed-file -name=class:part -new-name=a_200:class -indexed-file=%s -indexed-at=244:18 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK43 %s +// RUN: clang-refactor-test rename-indexed-file -name=piece:world -new-name=part:name -indexed-file=%s -indexed-at=246:137 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK44 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:object -new-name=onEntity:z_Z_42 -indexed-file=%s -indexed-at=248:30 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK45 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:object:piece:world:part -new-name=onEntity:piece:a_200:class:struct -indexed-file=%s -indexed-at=246:106 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK46 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:class -new-name=a_200:test -indexed-file=%s -indexed-at=259:27 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK47 %s +// RUN: clang-refactor-test rename-indexed-file -name=object:world:part -new-name=class:z_Z_42:onEntity -indexed-file=%s -indexed-at=256:79 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK48 %s +// RUN: clang-refactor-test rename-indexed-file -name=method:struct:foo:perform -new-name=onEntity:bar:part:test -indexed-file=%s -indexed-at=246:54 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK49 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:part:part:perform -new-name=piece:object:world:usingThing -indexed-file=%s -indexed-at=246:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK50 %s +// RUN: clang-refactor-test rename-indexed-file -name=bar:z_Z_42:name -new-name=bar:test:struct -indexed-file=%s -indexed-at=268:10 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK51 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:usingThing:test:withSomething:world -new-name=foo:test:usingThing:piece:piece -indexed-file=%s -indexed-at=273:34 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK52 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:part:onEntity:class:struct -new-name=z_Z_42:onEntity:onEntity:a_200:class -indexed-file=%s -indexed-at=267:42 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK53 %s + ++(void) z_Z_42 { + int a_200 = @{ @1, @3 }; + // comment + [globalObject send: ([self onEntity: [] () { [super onEntity: ^ { + } class: @"string literal" perform: @"string literal"] other: 42]; + // CHECK54: [[@LINE-2]]:58 -> [[@LINE-2]]:66, [[@LINE-1]]:5 -> [[@LINE-1]]:10, [[@LINE-1]]:30 -> [[@LINE-1]]:37 + } class: globalArray[i] a_200: ^ () { ] } +]); + // CHECK55: [[@LINE-5]]:30 -> [[@LINE-5]]:38, [[@LINE-2]]:5 -> [[@LINE-2]]:10, [[@LINE-2]]:27 -> [[@LINE-2]]:32 + globalArray[12] = [super method: @"string literal" test: ^ () { [self struct: "]" method: globalArray[i]]; + // CHECK56: [[@LINE-1]]:76 -> [[@LINE-1]]:82, [[@LINE-1]]:88 -> [[@LINE-1]]:94 + } name: globalArray[i]]; + // CHECK57: [[@LINE-3]]:28 -> [[@LINE-3]]:34, [[@LINE-3]]:54 -> [[@LINE-3]]:58, [[@LINE-1]]:5 -> [[@LINE-1]]:9 + BOOL struct = 12; +} +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:class:perform -new-name=bar:z_Z_42:onEntity -indexed-file=%s -indexed-at=297:58 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK54 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:class:a_200 -new-name=withSomething:piece:foo -indexed-file=%s -indexed-at=297:30 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK55 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:method -new-name=onEntity:struct -indexed-file=%s -indexed-at=303:76 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK56 %s +// RUN: clang-refactor-test rename-indexed-file -name=method:test:name -new-name=name:z_Z_42:object -indexed-file=%s -indexed-at=303:28 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK57 %s + ++(int) onEntity { + call() = [self piece: [] { return "string"; + } foo: ']' bar: globalArray[i]]; + // CHECK58: [[@LINE-2]]:18 -> [[@LINE-2]]:23, [[@LINE-1]]:5 -> [[@LINE-1]]:8, [[@LINE-1]]:14 -> [[@LINE-1]]:17 + [globalObject send: [self usingThing: [] { + [_undef_ivar test: globalArray[i] part: 12 world: "string" onEntity: 12] other: 42]; + // CHECK59: [[@LINE-1]]:17 -> [[@LINE-1]]:21, [[@LINE-1]]:38 -> [[@LINE-1]]:42, [[@LINE-1]]:47 -> [[@LINE-1]]:52, [[@LINE-1]]:63 -> [[@LINE-1]]:71 + + } a_200: @"string literal" a_200: "string" object: ("string") +//comment +]; + // CHECK60: [[@LINE-7]]:29 -> [[@LINE-7]]:39, [[@LINE-3]]:4 -> [[@LINE-3]]:9, [[@LINE-3]]:29 -> [[@LINE-3]]:34, [[@LINE-3]]:45 -> [[@LINE-3]]:51 + [globalObject send: [super object: ']' name: ^ { + [globalObject send: [self.undef_property bar: == [self name: "string" bar: ']'] + part: @"string literal" z_Z_42: ']' part: ']' test: "string"] other: 42] other: 42]; + // CHECK61: [[@LINE-2]]:60 -> [[@LINE-2]]:64, [[@LINE-2]]:75 -> [[@LINE-2]]:78 + // CHECK62: [[@LINE-3]]:45 -> [[@LINE-3]]:48, [[@LINE-2]]:2 -> [[@LINE-2]]:6, [[@LINE-2]]:26 -> [[@LINE-2]]:32, [[@LINE-2]]:38 -> [[@LINE-2]]:42, [[@LINE-2]]:48 -> [[@LINE-2]]:52 + + } + object: globalArray[i] foo: (']')]; + // CHECK63: [[@LINE-8]]:30 -> [[@LINE-8]]:36, [[@LINE-8]]:42 -> [[@LINE-8]]:46, [[@LINE-1]]:2 -> [[@LINE-1]]:8, [[@LINE-1]]:25 -> [[@LINE-1]]:28 + [self.undef_property world: globalArray[i] onEntity: ']' object: @"string literal" == 12 struct: [] { + int bar = [self onEntity: "]" piece: [] () { + }]; + // CHECK64: [[@LINE-2]]:20 -> [[@LINE-2]]:28, [[@LINE-2]]:34 -> [[@LINE-2]]:39 + + } struct: @"string literal"]; + // CHECK65: [[@LINE-6]]:24 -> [[@LINE-6]]:29, [[@LINE-6]]:46 -> [[@LINE-6]]:54, [[@LINE-6]]:60 -> [[@LINE-6]]:66, [[@LINE-6]]:92 -> [[@LINE-6]]:98, [[@LINE-1]]:4 -> [[@LINE-1]]:10 +} +// RUN: clang-refactor-test rename-indexed-file -name=piece:foo:bar -new-name=class:onEntity:method -indexed-file=%s -indexed-at=315:18 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK58 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:part:world:onEntity -new-name=z_Z_42:bar:piece:perform -indexed-file=%s -indexed-at=319:17 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK59 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:a_200:a_200:object -new-name=a_200:object:method:perform -indexed-file=%s -indexed-at=318:29 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK60 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:bar -new-name=z_Z_42:z_Z_42 -indexed-file=%s -indexed-at=327:60 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK61 %s +// RUN: clang-refactor-test rename-indexed-file -name=bar:part:z_Z_42:part:test -new-name=name:a_200:bar:name:z_Z_42 -indexed-file=%s -indexed-at=327:45 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK62 %s +// RUN: clang-refactor-test rename-indexed-file -name=object:name:object:foo -new-name=z_Z_42:object:usingThing:world -indexed-file=%s -indexed-at=326:30 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK63 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:piece -new-name=struct:piece -indexed-file=%s -indexed-at=336:20 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK64 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:onEntity:object:struct:struct -new-name=withSomething:test:foo:object:bar -indexed-file=%s -indexed-at=335:24 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK65 %s + ++(const Object &) object { + globalArray[12] = [self world: globalArray[i] object: ']' usingThing: usingThing: globalArray[i]]; + // CHECK66: [[@LINE-1]]:27 -> [[@LINE-1]]:32, [[@LINE-1]]:49 -> [[@LINE-1]]:55, [[@LINE-1]]:61 -> [[@LINE-1]]:71, [[@LINE-1]]:74 -> [[@LINE-1]]:84 + globalArray[12] = ([self.undef_property struct: ']' usingThing: "string"]); + // CHECK67: [[@LINE-1]]:43 -> [[@LINE-1]]:49, [[@LINE-1]]:55 -> [[@LINE-1]]:65 +} +// RUN: clang-refactor-test rename-indexed-file -name=world:object:usingThing:usingThing -new-name=a_200:struct:test:z_Z_42 -indexed-file=%s -indexed-at=353:27 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK66 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:usingThing -new-name=struct:a_200 -indexed-file=%s -indexed-at=355:43 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK67 %s + +-(void) part { + [self.undef_property withSomething: "string" method: [] { call() = [self.undef_property object: @"string literal" test: 12 + test: globalArray[i]]; + // CHECK68: [[@LINE-2]]:95 -> [[@LINE-2]]:101, [[@LINE-2]]:121 -> [[@LINE-2]]:125, [[@LINE-1]]:2 -> [[@LINE-1]]:6 + }]; + // CHECK69: [[@LINE-4]]:24 -> [[@LINE-4]]:37, [[@LINE-4]]:48 -> [[@LINE-4]]:54 +} +// RUN: clang-refactor-test rename-indexed-file -name=object:test:test -new-name=piece:bar:a_200 -indexed-file=%s -indexed-at=362:95 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK68 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:method -new-name=withSomething:test -indexed-file=%s -indexed-at=362:24 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK69 %s + ++(BOOL) method { + [self object: ^ () { ] } a_200: "]" foo: @{ @1, @3 } piece: [] { [self.undef_property a_200: 12 foo: 12 withSomething: globalArray[i] onEntity: ']' struct: 12]; + // CHECK70: [[@LINE-1]]:93 -> [[@LINE-1]]:98, [[@LINE-1]]:103 -> [[@LINE-1]]:106, [[@LINE-1]]:111 -> [[@LINE-1]]:124, [[@LINE-1]]:141 -> [[@LINE-1]]:149, [[@LINE-1]]:155 -> [[@LINE-1]]:161 + } +//comment +]; + // CHECK71: [[@LINE-5]]:9 -> [[@LINE-5]]:15, [[@LINE-5]]:28 -> [[@LINE-5]]:33, [[@LINE-5]]:39 -> [[@LINE-5]]:42, [[@LINE-5]]:56 -> [[@LINE-5]]:61 + [super method: 12 struct: ^ { /*comment*/[super foo: [self bar: @"string literal" method: "string" test: 12 test: "string" world: ']'] + onEntity: globalArray[i] method: ^ { + }]; + // CHECK72: [[@LINE-3]]:66 -> [[@LINE-3]]:69, [[@LINE-3]]:89 -> [[@LINE-3]]:95, [[@LINE-3]]:106 -> [[@LINE-3]]:110, [[@LINE-3]]:115 -> [[@LINE-3]]:119, [[@LINE-3]]:130 -> [[@LINE-3]]:135 + // CHECK73: [[@LINE-4]]:55 -> [[@LINE-4]]:58, [[@LINE-3]]:2 -> [[@LINE-3]]:10, [[@LINE-3]]:27 -> [[@LINE-3]]:33 + } a_200: "string"]; + // CHECK74: [[@LINE-6]]:10 -> [[@LINE-6]]:16, [[@LINE-6]]:21 -> [[@LINE-6]]:27, [[@LINE-1]]:5 -> [[@LINE-1]]:10 + if (12) { + [self world: "]" usingThing: @"string literal" + [] () { call() = [_undef_ivar part: "]" usingThing: 12 +]; + // CHECK75: [[@LINE-2]]:89 -> [[@LINE-2]]:93, [[@LINE-2]]:99 -> [[@LINE-2]]:109 + } foo: 12]; + // CHECK76: [[@LINE-4]]:13 -> [[@LINE-4]]:18, [[@LINE-4]]:24 -> [[@LINE-4]]:34, [[@LINE-1]]:5 -> [[@LINE-1]]:8 + + } + [super test: (12) foo: "string" name: [] { + return @{ @1, @3 }; + + } object: "]"]; + // CHECK77: [[@LINE-4]]:10 -> [[@LINE-4]]:14, [[@LINE-4]]:21 -> [[@LINE-4]]:24, [[@LINE-4]]:35 -> [[@LINE-4]]:39, [[@LINE-1]]:4 -> [[@LINE-1]]:10 + return ']'; +} +// RUN: clang-refactor-test rename-indexed-file -name=a_200:foo:withSomething:onEntity:struct -new-name=world:bar:class:perform:z_Z_42 -indexed-file=%s -indexed-at=372:93 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK70 %s +// RUN: clang-refactor-test rename-indexed-file -name=object:a_200:foo:piece -new-name=part:withSomething:name:foo -indexed-file=%s -indexed-at=372:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK71 %s +// RUN: clang-refactor-test rename-indexed-file -name=bar:method:test:test:world -new-name=withSomething:perform:usingThing:usingThing:method -indexed-file=%s -indexed-at=378:66 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK72 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:onEntity:method -new-name=foo:name:a_200 -indexed-file=%s -indexed-at=378:55 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK73 %s +// RUN: clang-refactor-test rename-indexed-file -name=method:struct:a_200 -new-name=method:z_Z_42:struct -indexed-file=%s -indexed-at=378:10 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK74 %s +// RUN: clang-refactor-test rename-indexed-file -name=part:usingThing -new-name=withSomething:name -indexed-file=%s -indexed-at=386:89 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK75 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:usingThing:foo -new-name=usingThing:method:class -indexed-file=%s -indexed-at=386:13 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK76 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:foo:name:object -new-name=z_Z_42:world:name:object -indexed-file=%s -indexed-at=393:10 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK77 %s + +-(BOOL) object { + [self part: @{ @1, @3 } foo: "]" method: ([self a_200: @"string literal" test: part: globalArray[i] object: "]"]) usingThing: @{ @1, @3 }]; + // CHECK78: [[@LINE-1]]:51 -> [[@LINE-1]]:56, [[@LINE-1]]:76 -> [[@LINE-1]]:80, [[@LINE-1]]:83 -> [[@LINE-1]]:87, [[@LINE-1]]:104 -> [[@LINE-1]]:110 + // CHECK79: [[@LINE-2]]:9 -> [[@LINE-2]]:13, [[@LINE-2]]:27 -> [[@LINE-2]]:30, [[@LINE-2]]:36 -> [[@LINE-2]]:42, [[@LINE-2]]:118 -> [[@LINE-2]]:128 + [self onEntity: [] () { int bar = [self test: "string"/*comment*/ bar: globalArray[i] method: [] () { + }]; + // CHECK80: [[@LINE-2]]:46 -> [[@LINE-2]]:50, [[@LINE-2]]:72 -> [[@LINE-2]]:75, [[@LINE-2]]:92 -> [[@LINE-2]]:98 + } part: @"string literal" perform: [self.undef_property onEntity: globalArray[i] test: [] { + if ("]") { + [super foo: "string" z_Z_42: 12]; + // CHECK81: [[@LINE-1]]:14 -> [[@LINE-1]]:17, [[@LINE-1]]:28 -> [[@LINE-1]]:34 + + } + + } method: @"string literal" usingThing: ^ () { + Object * method = globalArray[i]; + + }]]; + // CHECK82: [[@LINE-11]]:59 -> [[@LINE-11]]:67, [[@LINE-11]]:84 -> [[@LINE-11]]:88, [[@LINE-4]]:4 -> [[@LINE-4]]:10, [[@LINE-4]]:30 -> [[@LINE-4]]:40 + // CHECK83: [[@LINE-15]]:9 -> [[@LINE-15]]:17, [[@LINE-12]]:5 -> [[@LINE-12]]:9, [[@LINE-12]]:29 -> [[@LINE-12]]:36 + [self usingThing: ^ () { int bar = [self onEntity: [self.undef_property perform: "]" + struct: [self name: "string" withSomething: ^ () { + + + }]] foo: ']' object: [_undef_ivar world: [super withSomething: (@{ @1, @3 }) name: @"string literal" +] onEntity: [super perform: "string" class: @"string literal"]] usingThing: 12 == @"string literal" +]; + // CHECK84: [[@LINE-6]]:16 -> [[@LINE-6]]:20, [[@LINE-6]]:31 -> [[@LINE-6]]:44 + // CHECK85: [[@LINE-8]]:78 -> [[@LINE-8]]:85, [[@LINE-7]]:2 -> [[@LINE-7]]:8 + // CHECK86: [[@LINE-5]]:50 -> [[@LINE-5]]:63, [[@LINE-5]]:79 -> [[@LINE-5]]:83 + // CHECK87: [[@LINE-5]]:20 -> [[@LINE-5]]:27, [[@LINE-5]]:38 -> [[@LINE-5]]:43 + // CHECK88: [[@LINE-7]]:36 -> [[@LINE-7]]:41, [[@LINE-6]]:3 -> [[@LINE-6]]:11 + // CHECK89: [[@LINE-12]]:47 -> [[@LINE-12]]:55, [[@LINE-8]]:6 -> [[@LINE-8]]:9, [[@LINE-8]]:15 -> [[@LINE-8]]:21, [[@LINE-7]]:65 -> [[@LINE-7]]:75 + } withSomething: "string" world: "string"]; + // CHECK90: [[@LINE-14]]:9 -> [[@LINE-14]]:19, [[@LINE-1]]:5 -> [[@LINE-1]]:18, [[@LINE-1]]:29 -> [[@LINE-1]]:34 + globalArray[12] = [self.undef_property bar: "string" z_Z_42: ^ { + [self class: ']' struct: @{ @1, @3 } + withSomething: ']' class: (']') world: @{ @1, @3 }]; + // CHECK91: [[@LINE-2]]:10 -> [[@LINE-2]]:15, [[@LINE-2]]:21 -> [[@LINE-2]]:27, [[@LINE-1]]:2 -> [[@LINE-1]]:15, [[@LINE-1]]:21 -> [[@LINE-1]]:26, [[@LINE-1]]:34 -> [[@LINE-1]]:39 + + } + a_200: [] () { ; ;[_undef_ivar class: ']' struct: ^ { + } z_Z_42: [super withSomething: ^ { + + + } foo: globalArray[i] perform: (@"string literal") perform: 12]]; + // CHECK92: [[@LINE-4]]:20 -> [[@LINE-4]]:33, [[@LINE-1]]:4 -> [[@LINE-1]]:7, [[@LINE-1]]:24 -> [[@LINE-1]]:31, [[@LINE-1]]:53 -> [[@LINE-1]]:60 + // CHECK93: [[@LINE-6]]:36 -> [[@LINE-6]]:41, [[@LINE-6]]:47 -> [[@LINE-6]]:53, [[@LINE-5]]:5 -> [[@LINE-5]]:11 + } +]; + // CHECK94: [[@LINE-15]]:42 -> [[@LINE-15]]:45, [[@LINE-15]]:56 -> [[@LINE-15]]:62, [[@LINE-9]]:2 -> [[@LINE-9]]:7 +} +// RUN: clang-refactor-test rename-indexed-file -name=a_200:test:part:object -new-name=object:a_200:object:object -indexed-file=%s -indexed-at=410:51 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK78 %s +// RUN: clang-refactor-test rename-indexed-file -name=part:foo:method:usingThing -new-name=world:onEntity:foo:a_200 -indexed-file=%s -indexed-at=410:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK79 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:bar:method -new-name=object:method:withSomething -indexed-file=%s -indexed-at=413:46 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK80 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:z_Z_42 -new-name=foo:class -indexed-file=%s -indexed-at=418:14 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK81 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:test:method:usingThing -new-name=class:a_200:class:object -indexed-file=%s -indexed-at=416:59 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK82 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:part:perform -new-name=name:z_Z_42:class -indexed-file=%s -indexed-at=413:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK83 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:withSomething -new-name=withSomething:z_Z_42 -indexed-file=%s -indexed-at=430:16 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK84 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:struct -new-name=usingThing:struct -indexed-file=%s -indexed-at=429:78 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK85 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:name -new-name=name:foo -indexed-file=%s -indexed-at=433:50 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK86 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:class -new-name=test:z_Z_42 -indexed-file=%s -indexed-at=434:20 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK87 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:onEntity -new-name=foo:z_Z_42 -indexed-file=%s -indexed-at=433:36 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK88 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:foo:object:usingThing -new-name=test:part:struct:object -indexed-file=%s -indexed-at=429:47 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK89 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:withSomething:world -new-name=a_200:test:struct -indexed-file=%s -indexed-at=429:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK90 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:struct:withSomething:class:world -new-name=test:bar:foo:usingThing:usingThing -indexed-file=%s -indexed-at=445:10 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK91 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:foo:perform:perform -new-name=onEntity:struct:piece:withSomething -indexed-file=%s -indexed-at=451:20 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK92 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:struct:z_Z_42 -new-name=onEntity:method:a_200 -indexed-file=%s -indexed-at=450:36 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK93 %s +// RUN: clang-refactor-test rename-indexed-file -name=bar:z_Z_42:a_200 -new-name=withSomething:class:z_Z_42 -indexed-file=%s -indexed-at=444:42 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK94 %s + ++(BOOL) world { + ; ;[self struct: [] { call() = [self part: 12 class: ^ { + + + } test: ^ () { + + + } a_200: "string"]; + // CHECK95: [[@LINE-7]]:44 -> [[@LINE-7]]:48, [[@LINE-7]]:53 -> [[@LINE-7]]:58, [[@LINE-4]]:4 -> [[@LINE-4]]:8, [[@LINE-1]]:4 -> [[@LINE-1]]:9 + } piece: "string" z_Z_42: [] { [self onEntity: struct: ^ () { + } world: [_undef_ivar withSomething: globalArray[i] piece: ^ { + + + } method: + part: @"string literal" world: @"string literal" +]]; + // CHECK96: [[@LINE-6]]:25 -> [[@LINE-6]]:38, [[@LINE-6]]:55 -> [[@LINE-6]]:60, [[@LINE-3]]:4 -> [[@LINE-3]]:10, [[@LINE-2]]:2 -> [[@LINE-2]]:6, [[@LINE-2]]:26 -> [[@LINE-2]]:31 + // CHECK97: [[@LINE-8]]:44 -> [[@LINE-8]]:52, [[@LINE-8]]:55 -> [[@LINE-8]]:61, [[@LINE-7]]:5 -> [[@LINE-7]]:10 + }]; + // CHECK98: [[@LINE-18]]:12 -> [[@LINE-18]]:18, [[@LINE-10]]:5 -> [[@LINE-10]]:10, [[@LINE-10]]:21 -> [[@LINE-10]]:27 + if (globalArray[i]) { + ([_undef_ivar world: ']' class: usingThing: 12]); + // CHECK99: [[@LINE-1]]:21 -> [[@LINE-1]]:26, [[@LINE-1]]:32 -> [[@LINE-1]]:37, [[@LINE-1]]:40 -> [[@LINE-1]]:50 + + } + some_type_t foo = [self.undef_property object: ']' + test: ^ () { + globalArray[12] = [_undef_ivar z_Z_42: 12 withSomething: [self a_200: ']' perform: globalArray[i] z_Z_42: [] { + + + } perform: @"string literal" z_Z_42: globalArray[i]] + name: 12]; + // CHECK100: [[@LINE-5]]:67 -> [[@LINE-5]]:72, [[@LINE-5]]:78 -> [[@LINE-5]]:85, [[@LINE-5]]:102 -> [[@LINE-5]]:108, [[@LINE-2]]:4 -> [[@LINE-2]]:11, [[@LINE-2]]:31 -> [[@LINE-2]]:37 + // CHECK101: [[@LINE-6]]:35 -> [[@LINE-6]]:41, [[@LINE-6]]:46 -> [[@LINE-6]]:59, [[@LINE-2]]:2 -> [[@LINE-2]]:6 + + } bar: "]" +//comment + usingThing: [] { + return [self onEntity: 12 < 12 withSomething: [] { + + + }]; + // CHECK102: [[@LINE-4]]:17 -> [[@LINE-4]]:25, [[@LINE-4]]:35 -> [[@LINE-4]]:48 + + } withSomething: ^ { + [super class: ([self.undef_property bar: [self withSomething: [self piece: "string" < globalArray[i] onEntity: [self method: 12 + part: "string" usingThing: @{ @1, @3 }]] perform: globalArray[i] bar: [] { + } name: 12] withSomething: "]"]) world: [] () { + } usingThing: "]"]; + // CHECK103: [[@LINE-4]]:121 -> [[@LINE-4]]:127, [[@LINE-3]]:2 -> [[@LINE-3]]:6, [[@LINE-3]]:17 -> [[@LINE-3]]:27 + // CHECK104: [[@LINE-5]]:72 -> [[@LINE-5]]:77, [[@LINE-5]]:105 -> [[@LINE-5]]:113 + // CHECK105: [[@LINE-6]]:51 -> [[@LINE-6]]:64, [[@LINE-5]]:43 -> [[@LINE-5]]:50, [[@LINE-5]]:67 -> [[@LINE-5]]:70, [[@LINE-4]]:5 -> [[@LINE-4]]:9 + // CHECK106: [[@LINE-7]]:40 -> [[@LINE-7]]:43, [[@LINE-5]]:15 -> [[@LINE-5]]:28 + // CHECK107: [[@LINE-8]]:11 -> [[@LINE-8]]:16, [[@LINE-6]]:36 -> [[@LINE-6]]:41, [[@LINE-5]]:5 -> [[@LINE-5]]:15 + + }]; + // CHECK108: [[@LINE-31]]:42 -> [[@LINE-31]]:48, [[@LINE-30]]:2 -> [[@LINE-30]]:6, [[@LINE-21]]:4 -> [[@LINE-21]]:7, [[@LINE-19]]:2 -> [[@LINE-19]]:12, [[@LINE-12]]:4 -> [[@LINE-12]]:17 +} +// RUN: clang-refactor-test rename-indexed-file -name=part:class:test:a_200 -new-name=perform:name:test:struct -indexed-file=%s -indexed-at=480:44 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK95 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:piece:method:part:world -new-name=object:piece:world:piece:world -indexed-file=%s -indexed-at=489:25 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK96 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:struct:world -new-name=onEntity:world:world -indexed-file=%s -indexed-at=488:44 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK97 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:piece:z_Z_42 -new-name=part:object:onEntity -indexed-file=%s -indexed-at=480:12 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK98 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:class:usingThing -new-name=class:struct:method -indexed-file=%s -indexed-at=500:21 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK99 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:perform:z_Z_42:perform:z_Z_42 -new-name=piece:part:piece:class:onEntity -indexed-file=%s -indexed-at=506:67 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK100 %s +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:withSomething:name -new-name=usingThing:onEntity:a_200 -indexed-file=%s -indexed-at=506:35 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK101 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:withSomething -new-name=method:usingThing -indexed-file=%s -indexed-at=517:17 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK102 %s +// RUN: clang-refactor-test rename-indexed-file -name=method:part:usingThing -new-name=object:piece:method -indexed-file=%s -indexed-at=524:121 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK103 %s +// RUN: clang-refactor-test rename-indexed-file -name=piece:onEntity -new-name=bar:struct -indexed-file=%s -indexed-at=524:72 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK104 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:perform:bar:name -new-name=usingThing:z_Z_42:class:part -indexed-file=%s -indexed-at=524:51 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK105 %s +// RUN: clang-refactor-test rename-indexed-file -name=bar:withSomething -new-name=world:perform -indexed-file=%s -indexed-at=524:40 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK106 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:world:usingThing -new-name=object:name:name -indexed-file=%s -indexed-at=524:11 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK107 %s +// RUN: clang-refactor-test rename-indexed-file -name=object:test:bar:usingThing:withSomething -new-name=bar:piece:class:perform:class -indexed-file=%s -indexed-at=504:42 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK108 %s + ++(Object *) test { + [super foo: globalArray[i] a_200: globalArray[i] +//comment + foo: 12 + struct: ^ () { + [_undef_ivar class: globalArray[i] struct: 12 object: @"string literal" foo: "string"]; + // CHECK109: [[@LINE-1]]:17 -> [[@LINE-1]]:22, [[@LINE-1]]:39 -> [[@LINE-1]]:45, [[@LINE-1]]:50 -> [[@LINE-1]]:56, [[@LINE-1]]:76 -> [[@LINE-1]]:79 + + } * [super struct: [self world: ']' struct: [] { + [self method: [] { + + + } withSomething: "string" usingThing: [] () { + + + }]; + // CHECK110: [[@LINE-7]]:10 -> [[@LINE-7]]:16, [[@LINE-4]]:4 -> [[@LINE-4]]:17, [[@LINE-4]]:28 -> [[@LINE-4]]:38 + + } struct: ^ () { + return [] () { + }; + + } class: [_undef_ivar part: "string" name: [] { + int bar = [self withSomething: "string" bar: @"string literal" usingThing: @"string literal"]; + // CHECK111: [[@LINE-1]]:20 -> [[@LINE-1]]:33, [[@LINE-1]]:44 -> [[@LINE-1]]:47, [[@LINE-1]]:67 -> [[@LINE-1]]:77 + + }] class: "]"] < 12 foo: * "]"] + "string" + "]" == [] () { + some_type_t foo = [_undef_ivar part: 12 name: @"string literal" test: "]" bar: ^ { + }]; + // CHECK112: [[@LINE-7]]:24 -> [[@LINE-7]]:28, [[@LINE-7]]:39 -> [[@LINE-7]]:43 + // CHECK113: [[@LINE-22]]:27 -> [[@LINE-22]]:32, [[@LINE-22]]:38 -> [[@LINE-22]]:44, [[@LINE-12]]:4 -> [[@LINE-12]]:10, [[@LINE-8]]:4 -> [[@LINE-8]]:9, [[@LINE-4]]:5 -> [[@LINE-4]]:10 + // CHECK114: [[@LINE-23]]:13 -> [[@LINE-23]]:19, [[@LINE-5]]:22 -> [[@LINE-5]]:25 + // CHECK115: [[@LINE-5]]:35 -> [[@LINE-5]]:39, [[@LINE-5]]:44 -> [[@LINE-5]]:48, [[@LINE-5]]:68 -> [[@LINE-5]]:72, [[@LINE-5]]:78 -> [[@LINE-5]]:81 + + } < ^ { + int bar = [self struct: ^ () { + + + } a_200: @{ @1, @3 }]; + // CHECK116: [[@LINE-4]]:20 -> [[@LINE-4]]:26, [[@LINE-1]]:4 -> [[@LINE-1]]:9 + + } == [super withSomething: [] { return [_undef_ivar usingThing: [] () { + } + world: [] () { + + + } foo: 12 perform: globalArray[i] name: @"string literal"]; + // CHECK117: [[@LINE-6]]:58 -> [[@LINE-6]]:68, [[@LINE-4]]:2 -> [[@LINE-4]]:7, [[@LINE-1]]:4 -> [[@LINE-1]]:7, [[@LINE-1]]:12 -> [[@LINE-1]]:19, [[@LINE-1]]:36 -> [[@LINE-1]]:40 + } world: ^ { // comment + } object: ^ { int method = "]"; + } +]]; + // CHECK118: [[@LINE-11]]:14 -> [[@LINE-11]]:27, [[@LINE-4]]:5 -> [[@LINE-4]]:10, [[@LINE-3]]:5 -> [[@LINE-3]]:11 + // CHECK119: [[@LINE-52]]:10 -> [[@LINE-52]]:13, [[@LINE-52]]:30 -> [[@LINE-52]]:35, [[@LINE-50]]:2 -> [[@LINE-50]]:5, [[@LINE-49]]:2 -> [[@LINE-49]]:8 + return [] { return 12; + }; + if ([] { + some_type_t foo = [_undef_ivar perform: @"string literal" name: globalArray[i] * ']' + z_Z_42: "]" perform: globalArray[i] class: globalArray[i] +]; + // CHECK120: [[@LINE-3]]:35 -> [[@LINE-3]]:42, [[@LINE-3]]:62 -> [[@LINE-3]]:66, [[@LINE-2]]:2 -> [[@LINE-2]]:8, [[@LINE-2]]:14 -> [[@LINE-2]]:21, [[@LINE-2]]:38 -> [[@LINE-2]]:43 + + }) { + [globalObject message] = [self.undef_property withSomething: ']' test: @"string literal" onEntity: ^ { + return @{ @1, @3 }; + + } onEntity: "string"]; + // CHECK121: [[@LINE-4]]:53 -> [[@LINE-4]]:66, [[@LINE-4]]:72 -> [[@LINE-4]]:76, [[@LINE-4]]:96 -> [[@LINE-4]]:104, [[@LINE-1]]:4 -> [[@LINE-1]]:12 + + } + [globalObject message] = [self onEntity: "string" onEntity: ^ { call() = [self withSomething: ^ () { + } == ']' perform: ("string") bar: (']')]; + // CHECK122: [[@LINE-2]]:86 -> [[@LINE-2]]:99, [[@LINE-1]]:12 -> [[@LINE-1]]:19, [[@LINE-1]]:32 -> [[@LINE-1]]:35 + } +]; + // CHECK123: [[@LINE-5]]:34 -> [[@LINE-5]]:42, [[@LINE-5]]:53 -> [[@LINE-5]]:61 + globalArray[12] = [self withSomething: @"string literal" piece: ^ { + call() = [_undef_ivar perform: (12) bar: ^ { + + + } test: "]" name: "string" bar: "]"]; + // CHECK124: [[@LINE-4]]:26 -> [[@LINE-4]]:33, [[@LINE-4]]:40 -> [[@LINE-4]]:43, [[@LINE-1]]:4 -> [[@LINE-1]]:8, [[@LINE-1]]:14 -> [[@LINE-1]]:18, [[@LINE-1]]:29 -> [[@LINE-1]]:32 + + } z_Z_42: [] () { [self onEntity: [] () { + + + } piece: "string"]; + // CHECK125: [[@LINE-4]]:29 -> [[@LINE-4]]:37, [[@LINE-1]]:4 -> [[@LINE-1]]:9 + } + @"string literal" object: ^ { + [globalObject send: [self world: "]" onEntity: ']' + struct: [] () { + } perform: [self.undef_property piece: [] () { + + + } method: 12 foo: [] { + + + } onEntity: "string" * "]" method: "]"] < ']'] other: 42]; + // CHECK126: [[@LINE-7]]:35 -> [[@LINE-7]]:40, [[@LINE-4]]:4 -> [[@LINE-4]]:10, [[@LINE-4]]:15 -> [[@LINE-4]]:18, [[@LINE-1]]:4 -> [[@LINE-1]]:12, [[@LINE-1]]:29 -> [[@LINE-1]]:35 + // CHECK127: [[@LINE-10]]:30 -> [[@LINE-10]]:35, [[@LINE-10]]:41 -> [[@LINE-10]]:49, [[@LINE-9]]:2 -> [[@LINE-9]]:8, [[@LINE-8]]:5 -> [[@LINE-8]]:12 + + } method: ^ { // comment + }]; + // CHECK128: [[@LINE-27]]:27 -> [[@LINE-27]]:40, [[@LINE-27]]:60 -> [[@LINE-27]]:65, [[@LINE-20]]:4 -> [[@LINE-20]]:10, [[@LINE-15]]:25 -> [[@LINE-15]]:31, [[@LINE-2]]:4 -> [[@LINE-2]]:10 +} +// RUN: clang-refactor-test rename-indexed-file -name=class:struct:object:foo -new-name=onEntity:onEntity:struct:foo -indexed-file=%s -indexed-at=557:17 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK109 %s +// RUN: clang-refactor-test rename-indexed-file -name=method:withSomething:usingThing -new-name=object:class:withSomething -indexed-file=%s -indexed-at=561:10 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK110 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:bar:usingThing -new-name=piece:class:test -indexed-file=%s -indexed-at=575:20 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK111 %s +// RUN: clang-refactor-test rename-indexed-file -name=part:name -new-name=bar:object -indexed-file=%s -indexed-at=574:24 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK112 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:struct:struct:class:class -new-name=name:onEntity:z_Z_42:piece:foo -indexed-file=%s -indexed-at=560:27 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK113 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:foo -new-name=foo:test -indexed-file=%s -indexed-at=560:13 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK114 %s +// RUN: clang-refactor-test rename-indexed-file -name=part:name:test:bar -new-name=class:z_Z_42:onEntity:usingThing -indexed-file=%s -indexed-at=579:35 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK115 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:a_200 -new-name=usingThing:bar -indexed-file=%s -indexed-at=587:20 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK116 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:world:foo:perform:name -new-name=withSomething:foo:world:test:test -indexed-file=%s -indexed-at=593:58 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK117 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:world:object -new-name=withSomething:class:part -indexed-file=%s -indexed-at=593:14 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK118 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:a_200:foo:struct -new-name=usingThing:foo:object:test -indexed-file=%s -indexed-at=553:10 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK119 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:name:z_Z_42:perform:class -new-name=method:usingThing:class:class:world -indexed-file=%s -indexed-at=609:35 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK120 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:test:onEntity:onEntity -new-name=onEntity:z_Z_42:a_200:piece -indexed-file=%s -indexed-at=615:53 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK121 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:perform:bar -new-name=foo:usingThing:world -indexed-file=%s -indexed-at=622:86 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK122 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:onEntity -new-name=foo:perform -indexed-file=%s -indexed-at=622:34 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK123 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:bar:test:name:bar -new-name=onEntity:withSomething:object:method:test -indexed-file=%s -indexed-at=629:26 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK124 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:piece -new-name=world:z_Z_42 -indexed-file=%s -indexed-at=635:29 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK125 %s +// RUN: clang-refactor-test rename-indexed-file -name=piece:method:foo:onEntity:method -new-name=foo:method:z_Z_42:piece:bar -indexed-file=%s -indexed-at=643:35 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK126 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:onEntity:struct:perform -new-name=test:object:z_Z_42:object -indexed-file=%s -indexed-at=641:30 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK127 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:piece:z_Z_42:object:method -new-name=test:test:withSomething:z_Z_42:name -indexed-file=%s -indexed-at=628:27 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK128 %s + ++(int) object { + [self usingThing: [] () { ([self.undef_property object: ^ { + } usingThing: @"string literal" + test: "string" usingThing: globalArray[i] + foo: /*]*/ globalArray[i]]); + // CHECK129: [[@LINE-4]]:54 -> [[@LINE-4]]:60, [[@LINE-3]]:5 -> [[@LINE-3]]:15, [[@LINE-2]]:2 -> [[@LINE-2]]:6, [[@LINE-2]]:17 -> [[@LINE-2]]:27, [[@LINE-1]]:2 -> [[@LINE-1]]:5 + } < ^ () { + [self z_Z_42: name: globalArray[i] test: globalArray[i] + perform: globalArray[i]]; + // CHECK130: [[@LINE-2]]:10 -> [[@LINE-2]]:16, [[@LINE-2]]:19 -> [[@LINE-2]]:23, [[@LINE-2]]:40 -> [[@LINE-2]]:44, [[@LINE-1]]:2 -> [[@LINE-1]]:9 + + } perform: a_200: ([] () { + [super z_Z_42: "string" z_Z_42: [self perform: [] () { + + + } withSomething: ^ { + + + } +] world: "]" onEntity: globalArray[i] foo: "]"]; + // CHECK131: [[@LINE-8]]:42 -> [[@LINE-8]]:49, [[@LINE-5]]:4 -> [[@LINE-5]]:17 + // CHECK132: [[@LINE-9]]:11 -> [[@LINE-9]]:17, [[@LINE-9]]:28 -> [[@LINE-9]]:34, [[@LINE-2]]:3 -> [[@LINE-2]]:8, [[@LINE-2]]:14 -> [[@LINE-2]]:22, [[@LINE-2]]:39 -> [[@LINE-2]]:42 + + } * "string") a_200: ']']; + // CHECK133: [[@LINE-23]]:9 -> [[@LINE-23]]:19, [[@LINE-13]]:4 -> [[@LINE-13]]:11, [[@LINE-13]]:14 -> [[@LINE-13]]:19, [[@LINE-1]]:16 -> [[@LINE-1]]:21 + if (12) { + int bar = [super piece: 12 == globalArray[i] method: [self class: [] () { return "string"; + } + class: @"string literal" test: ^ () { + globalArray[12] = [self class: globalArray[i] z_Z_42: [] () { + } method: [super test: [] () { + } struct: @"string literal" + withSomething: [] { + + + } == globalArray[i] class: "]" struct: @"string literal"]]; + // CHECK134: [[@LINE-6]]:20 -> [[@LINE-6]]:24, [[@LINE-5]]:5 -> [[@LINE-5]]:11, [[@LINE-4]]:2 -> [[@LINE-4]]:15, [[@LINE-1]]:22 -> [[@LINE-1]]:27, [[@LINE-1]]:33 -> [[@LINE-1]]:39 + // CHECK135: [[@LINE-8]]:28 -> [[@LINE-8]]:33, [[@LINE-8]]:50 -> [[@LINE-8]]:56, [[@LINE-7]]:5 -> [[@LINE-7]]:11 + + } struct: ']' * 12 + @"string literal"]]; + // CHECK136: [[@LINE-14]]:66 -> [[@LINE-14]]:71, [[@LINE-12]]:2 -> [[@LINE-12]]:7, [[@LINE-12]]:27 -> [[@LINE-12]]:31, [[@LINE-1]]:4 -> [[@LINE-1]]:10 + // CHECK137: [[@LINE-15]]:24 -> [[@LINE-15]]:29, [[@LINE-15]]:52 -> [[@LINE-15]]:58 + + } + [globalObject send: [super struct: ^ { + [globalObject message] = [self.undef_property test: "string" onEntity: "]"] other: 42]; + // CHECK138: [[@LINE-1]]:50 -> [[@LINE-1]]:54, [[@LINE-1]]:65 -> [[@LINE-1]]:73 + + } z_Z_42: @"string literal" perform: @"string literal" part: globalArray[i] < 12 struct: [self onEntity: @"string literal" part: "]"]]; + // CHECK139: [[@LINE-1]]:97 -> [[@LINE-1]]:105, [[@LINE-1]]:125 -> [[@LINE-1]]:129 + // CHECK140: [[@LINE-6]]:30 -> [[@LINE-6]]:36, [[@LINE-2]]:4 -> [[@LINE-2]]:10, [[@LINE-2]]:30 -> [[@LINE-2]]:37, [[@LINE-2]]:57 -> [[@LINE-2]]:61, [[@LINE-2]]:83 -> [[@LINE-2]]:89 + if ("]") { + [super onEntity: ^ { [self.undef_property withSomething: globalArray[i] + method: ^ () { + } onEntity: [self test: [_undef_ivar bar: @{ @1, @3 } < globalArray[i] < part: ] * "]" method: (([self struct: "string" piece: [] () { + } struct: "string"])) withSomething: [self a_200: "]" foo: ']' < [self a_200: @"string literal" object: ']' onEntity: "]"] part: 12 usingThing: globalArray[i] name: [self.undef_property perform: @"string literal" world: globalArray[i] method: (12) method: [self.undef_property foo: @"string literal" part: [] { + + + } * "string" withSomething: "string" +] perform: @"string literal"]] bar: ']' +]]; + // CHECK141: [[@LINE-7]]:40 -> [[@LINE-7]]:43, [[@LINE-7]]:77 -> [[@LINE-7]]:81 + // CHECK142: [[@LINE-8]]:107 -> [[@LINE-8]]:113, [[@LINE-8]]:124 -> [[@LINE-8]]:129, [[@LINE-7]]:5 -> [[@LINE-7]]:11 + // CHECK143: [[@LINE-8]]:74 -> [[@LINE-8]]:79, [[@LINE-8]]:99 -> [[@LINE-8]]:105, [[@LINE-8]]:111 -> [[@LINE-8]]:119 + // CHECK144: [[@LINE-9]]:280 -> [[@LINE-9]]:283, [[@LINE-9]]:303 -> [[@LINE-9]]:307, [[@LINE-6]]:15 -> [[@LINE-6]]:28 + // CHECK145: [[@LINE-10]]:189 -> [[@LINE-10]]:196, [[@LINE-10]]:216 -> [[@LINE-10]]:221, [[@LINE-10]]:238 -> [[@LINE-10]]:244, [[@LINE-10]]:251 -> [[@LINE-10]]:257, [[@LINE-6]]:3 -> [[@LINE-6]]:10 + // CHECK146: [[@LINE-11]]:46 -> [[@LINE-11]]:51, [[@LINE-11]]:57 -> [[@LINE-11]]:60, [[@LINE-11]]:126 -> [[@LINE-11]]:130, [[@LINE-11]]:135 -> [[@LINE-11]]:145, [[@LINE-11]]:162 -> [[@LINE-11]]:166 + // CHECK147: [[@LINE-13]]:21 -> [[@LINE-13]]:25, [[@LINE-13]]:91 -> [[@LINE-13]]:97, [[@LINE-12]]:25 -> [[@LINE-12]]:38, [[@LINE-8]]:32 -> [[@LINE-8]]:35 + // CHECK148: [[@LINE-16]]:53 -> [[@LINE-16]]:66, [[@LINE-15]]:2 -> [[@LINE-15]]:8, [[@LINE-14]]:5 -> [[@LINE-14]]:13 + } usingThing: +]; + // CHECK149: [[@LINE-19]]:14 -> [[@LINE-19]]:22, [[@LINE-2]]:5 -> [[@LINE-2]]:15 + + } + [self.undef_property test: [] { if (@"string literal") { + call() = [self z_Z_42: "string" usingThing: @"string literal" +]; + // CHECK150: [[@LINE-2]]:22 -> [[@LINE-2]]:28, [[@LINE-2]]:39 -> [[@LINE-2]]:49 + + } + } object: globalArray[i] + test: ^ () { if ("string" == @"string literal" + ^ () { + + + }) { + int name = 12 * 12; + + } + } a_200: "]"]; + // CHECK151: [[@LINE-15]]:24 -> [[@LINE-15]]:28, [[@LINE-9]]:5 -> [[@LINE-9]]:11, [[@LINE-8]]:2 -> [[@LINE-8]]:6, [[@LINE-1]]:5 -> [[@LINE-1]]:10 +} +// RUN: clang-refactor-test rename-indexed-file -name=object:usingThing:test:usingThing:foo -new-name=part:piece:object:a_200:name -indexed-file=%s -indexed-at=679:54 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK129 %s +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:name:test:perform -new-name=a_200:usingThing:usingThing:withSomething -indexed-file=%s -indexed-at=685:10 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK130 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:withSomething -new-name=piece:struct -indexed-file=%s -indexed-at=690:42 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK131 %s +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:z_Z_42:world:onEntity:foo -new-name=withSomething:method:perform:onEntity:bar -indexed-file=%s -indexed-at=690:11 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK132 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:perform:a_200:a_200 -new-name=a_200:world:usingThing:class -indexed-file=%s -indexed-at=679:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK133 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:struct:withSomething:class:struct -new-name=withSomething:part:a_200:method:perform -indexed-file=%s -indexed-at=708:20 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK134 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:z_Z_42:method -new-name=part:a_200:part -indexed-file=%s -indexed-at=707:28 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK135 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:class:test:struct -new-name=a_200:world:struct:world -indexed-file=%s -indexed-at=704:66 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK136 %s +// RUN: clang-refactor-test rename-indexed-file -name=piece:method -new-name=name:class -indexed-file=%s -indexed-at=704:24 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK137 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:onEntity -new-name=foo:piece -indexed-file=%s -indexed-at=723:50 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK138 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:part -new-name=class:name -indexed-file=%s -indexed-at=726:97 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK139 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:z_Z_42:perform:part:struct -new-name=onEntity:part:usingThing:struct:perform -indexed-file=%s -indexed-at=722:30 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK140 %s +// RUN: clang-refactor-test rename-indexed-file -name=bar:part -new-name=part:class -indexed-file=%s -indexed-at=732:40 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK141 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:piece:struct -new-name=part:onEntity:foo -indexed-file=%s -indexed-at=732:107 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK142 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:object:onEntity -new-name=world:bar:onEntity -indexed-file=%s -indexed-at=733:74 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK143 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:part:withSomething -new-name=usingThing:withSomething:perform -indexed-file=%s -indexed-at=733:280 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK144 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:world:method:method:perform -new-name=piece:bar:usingThing:class:piece -indexed-file=%s -indexed-at=733:189 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK145 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:foo:part:usingThing:name -new-name=z_Z_42:object:name:perform:foo -indexed-file=%s -indexed-at=733:46 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK146 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:method:withSomething:bar -new-name=piece:class:a_200:z_Z_42 -indexed-file=%s -indexed-at=732:21 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK147 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:method:onEntity -new-name=usingThing:foo:object -indexed-file=%s -indexed-at=730:53 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK148 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:usingThing -new-name=perform:piece -indexed-file=%s -indexed-at=730:14 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK149 %s +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:usingThing -new-name=test:usingThing -indexed-file=%s -indexed-at=753:22 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK150 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:object:test:a_200 -new-name=foo:world:piece:z_Z_42 -indexed-file=%s -indexed-at=752:24 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK151 %s + ++(void) onEntity { + int perform = [_undef_ivar onEntity: [] () { + /*comment*/([self z_Z_42: ']' + globalArray[i] a_200: method: "]" == "]" < ^ () { + + + }]); + // CHECK152: [[@LINE-4]]:22 -> [[@LINE-4]]:28, [[@LINE-4]]:51 -> [[@LINE-4]]:56, [[@LINE-4]]:59 -> [[@LINE-4]]:65 + + } method: "]" < globalArray[i] == @"string literal" struct: "string" == @{ @1, @3 } onEntity: @"string literal" + perform: ^ () { globalArray[12] = [self foo: ']' * @"string literal" piece: ^ { + } perform: "string" name: "]"]; + // CHECK153: [[@LINE-2]]:45 -> [[@LINE-2]]:48, [[@LINE-2]]:74 -> [[@LINE-2]]:79, [[@LINE-1]]:5 -> [[@LINE-1]]:12, [[@LINE-1]]:23 -> [[@LINE-1]]:27 + }]; + // CHECK154: [[@LINE-12]]:30 -> [[@LINE-12]]:38, [[@LINE-5]]:4 -> [[@LINE-5]]:10, [[@LINE-5]]:54 -> [[@LINE-5]]:60, [[@LINE-5]]:86 -> [[@LINE-5]]:94, [[@LINE-4]]:2 -> [[@LINE-4]]:9 + return [] { + [self part: (@"string literal") world: [self struct: [self a_200: "]" test: ']'] piece: "]" withSomething: @"string literal" struct: "]" object: ] bar: ']']; + // CHECK155: [[@LINE-1]]:63 -> [[@LINE-1]]:68, [[@LINE-1]]:74 -> [[@LINE-1]]:78 + // CHECK156: [[@LINE-2]]:49 -> [[@LINE-2]]:55, [[@LINE-2]]:85 -> [[@LINE-2]]:90, [[@LINE-2]]:96 -> [[@LINE-2]]:109, [[@LINE-2]]:129 -> [[@LINE-2]]:135, [[@LINE-2]]:141 -> [[@LINE-2]]:147 + // CHECK157: [[@LINE-3]]:10 -> [[@LINE-3]]:14, [[@LINE-3]]:36 -> [[@LINE-3]]:41, [[@LINE-3]]:151 -> [[@LINE-3]]:154 + + }; +} +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:a_200:method -new-name=name:world:part -indexed-file=%s -indexed-at=795:22 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK152 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:piece:perform:name -new-name=name:class:withSomething:method -indexed-file=%s -indexed-at=802:45 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK153 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:method:struct:onEntity:perform -new-name=withSomething:piece:bar:struct:piece -indexed-file=%s -indexed-at=794:30 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK154 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:test -new-name=part:z_Z_42 -indexed-file=%s -indexed-at=808:63 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK155 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:piece:withSomething:struct:object -new-name=foo:name:piece:z_Z_42:bar -indexed-file=%s -indexed-at=808:49 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK156 %s +// RUN: clang-refactor-test rename-indexed-file -name=part:world:bar -new-name=test:a_200:z_Z_42 -indexed-file=%s -indexed-at=808:10 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK157 %s + ++(const Object &) foo { + [self foo: "]" bar: ']' method: ^ () { ] } z_Z_42: [super usingThing: "string" world: [self world: globalArray[i] a_200: [] { [self z_Z_42: ']' usingThing: ("]")]; + // CHECK158: [[@LINE-1]]:139 -> [[@LINE-1]]:145, [[@LINE-1]]:151 -> [[@LINE-1]]:161 + } foo: (12)] usingThing: [] { + globalArray[12] = [self name: [] { + + + } foo: @"string literal"/*comment*/ withSomething: globalArray[i] test: [] { + }]; + // CHECK159: [[@LINE-8]]:95 -> [[@LINE-8]]:100, [[@LINE-8]]:117 -> [[@LINE-8]]:122, [[@LINE-6]]:5 -> [[@LINE-6]]:8 + // CHECK160: [[@LINE-6]]:28 -> [[@LINE-6]]:32, [[@LINE-3]]:4 -> [[@LINE-3]]:7, [[@LINE-3]]:38 -> [[@LINE-3]]:51, [[@LINE-3]]:68 -> [[@LINE-3]]:72 + + } foo: ^ { call() = [_undef_ivar world: @"string literal" usingThing: 12 onEntity: @"string literal" struct: ^ { + }]; + // CHECK161: [[@LINE-2]]:39 -> [[@LINE-2]]:44, [[@LINE-2]]:64 -> [[@LINE-2]]:74, [[@LINE-2]]:79 -> [[@LINE-2]]:87, [[@LINE-2]]:107 -> [[@LINE-2]]:113 + } < "]" test: ^ () { if ([] { + }) { + call() = [self withSomething: globalArray[i] z_Z_42: globalArray[i] + foo: 12 onEntity: @"string literal"]; + // CHECK162: [[@LINE-2]]:22 -> [[@LINE-2]]:35, [[@LINE-2]]:52 -> [[@LINE-2]]:58, [[@LINE-1]]:2 -> [[@LINE-1]]:5, [[@LINE-1]]:10 -> [[@LINE-1]]:18 + + } + }]]; + // CHECK163: [[@LINE-22]]:61 -> [[@LINE-22]]:71, [[@LINE-22]]:82 -> [[@LINE-22]]:87, [[@LINE-20]]:16 -> [[@LINE-20]]:26, [[@LINE-11]]:4 -> [[@LINE-11]]:7, [[@LINE-8]]:11 -> [[@LINE-8]]:15 + // CHECK164: [[@LINE-23]]:9 -> [[@LINE-23]]:12, [[@LINE-23]]:18 -> [[@LINE-23]]:21, [[@LINE-23]]:27 -> [[@LINE-23]]:33, [[@LINE-23]]:46 -> [[@LINE-23]]:52 + const Object & struct = ; + [globalObject message] = [self object: 12 + name: 12 a_200: ^ () { if ([self object: ^ { + } foo: "string" * @"string literal"]) { + [self withSomething: [] { + } +//comment + a_200: ^ { + } foo: "string" piece: [] () { + + + }]; + // CHECK165: [[@LINE-10]]:38 -> [[@LINE-10]]:44, [[@LINE-9]]:5 -> [[@LINE-9]]:8 + // CHECK166: [[@LINE-9]]:13 -> [[@LINE-9]]:26, [[@LINE-6]]:2 -> [[@LINE-6]]:7, [[@LINE-5]]:5 -> [[@LINE-5]]:8, [[@LINE-5]]:19 -> [[@LINE-5]]:24 + + } + }]; + // CHECK167: [[@LINE-16]]:34 -> [[@LINE-16]]:40, [[@LINE-15]]:2 -> [[@LINE-15]]:6, [[@LINE-15]]:11 -> [[@LINE-15]]:16 + [self world: @"string literal" withSomething: @{ @1, @3 }]; + // CHECK168: [[@LINE-1]]:9 -> [[@LINE-1]]:14, [[@LINE-1]]:34 -> [[@LINE-1]]:47 +} +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:usingThing -new-name=method:onEntity -indexed-file=%s -indexed-at=823:139 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK158 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:a_200:foo -new-name=class:test:bar -indexed-file=%s -indexed-at=823:95 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK159 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:foo:withSomething:test -new-name=a_200:perform:piece:method -indexed-file=%s -indexed-at=826:28 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK160 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:usingThing:onEntity:struct -new-name=usingThing:name:onEntity:method -indexed-file=%s -indexed-at=834:39 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK161 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:z_Z_42:foo:onEntity -new-name=perform:struct:bar:object -indexed-file=%s -indexed-at=839:22 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK162 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:world:usingThing:foo:test -new-name=method:onEntity:part:part:bar -indexed-file=%s -indexed-at=823:61 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK163 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:bar:method:z_Z_42 -new-name=class:onEntity:method:name -indexed-file=%s -indexed-at=823:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK164 %s +// RUN: clang-refactor-test rename-indexed-file -name=object:foo -new-name=object:usingThing -indexed-file=%s -indexed-at=849:38 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK165 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:a_200:foo:piece -new-name=class:struct:bar:onEntity -indexed-file=%s -indexed-at=851:13 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK166 %s +// RUN: clang-refactor-test rename-indexed-file -name=object:name:a_200 -new-name=piece:withSomething:withSomething -indexed-file=%s -indexed-at=848:34 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK167 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:withSomething -new-name=part:withSomething -indexed-file=%s -indexed-at=865:9 -indexed-symbol-kind=objc-message %s | FileCheck --check-prefix=CHECK168 %s + ++(void) object { + int test = globalArray[i]; +} diff --git a/clang/test/Refactor/Rename/IndexedObjCMethod.m b/clang/test/Refactor/Rename/IndexedObjCMethod.m new file mode 100644 index 0000000000000..f9182c6c1eca5 --- /dev/null +++ b/clang/test/Refactor/Rename/IndexedObjCMethod.m @@ -0,0 +1,128 @@ +@interface Test + +- (int)performAction:(int)action with:(int)value; // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:21, [[@LINE]]:34 -> [[@LINE]]:38 + +@end + +@implementation Test + +- (int)performAction:(int)action + with:(int)value { // CHECK: rename [[@LINE-1]]:8 -> [[@LINE-1]]:21, [[@LINE]]:8 -> [[@LINE]]:12 + return action + value; +} + ++ (void)foo:(Test*)t { + [t performAction: 2 with: 4]; // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:21, [[@LINE]]:25 -> [[@LINE]]:29 + SEL s1 = @selector(performAction: + with:); // CHECK-NOT: selector [[@LINE-1]]:24 -> [[@LINE-1]]:37, [[@LINE]]:24 -> [[@LINE]]:28 + SEL s2 = @selector(performAction:); // CHECK-NOT: selector [[@LINE]] + SEL s3 = @selector(performAction); // CHECK-NOT: selector [[@LINE]] + // Not indexed + [t performAction: 1 with: 2]; // CHECK-NOT: rename [[@LINE]] +} + +@end + +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=performAction:with -new-name=foo:bar -indexed-file=%s -indexed-at=3:8 -indexed-at=9:8 -indexed-at=objc-message:15:8 -indexed-symbol-kind=objc-im %s | FileCheck %s + +@interface SemicolonIsExcluded +-(void)/*A_propA4_set_decl*/setPropA4X:(int)value; +@end +// CHECK2: rename [[@LINE-2]]:29 -> [[@LINE-2]]:39 + +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=setPropA4X: -new-name=foo -indexed-file=%s -indexed-at=29:29 -indexed-symbol-kind=objc-im %s -x objective-c | FileCheck --check-prefix=CHECK2 %s + +// It should be possible to have the filename as one of the compilation arguments +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -ignore-filename-for-initiation-tu -name=performAction:with: -new-name=foo:bar: -indexed-file=%s -indexed-at=3:8 -indexed-at=9:8 -indexed-at=objc-message:15:8 -indexed-symbol-kind=objc-cm %s -c %s -Wall | FileCheck %s +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -ignore-filename-for-initiation-tu -name=performAction:with: -new-name=foo:bar: -indexed-file=%s -indexed-at=3:8 -indexed-at=9:8 -indexed-at=objc-message:15:8 -indexed-symbol-kind=objc-cm %s %s -fsyntax-only | FileCheck %s + +// -gmodules should be stripped to avoid -fmodule-format=obj in CC1 arguments: +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=performAction:with: -new-name=foo:bar: -indexed-file=%s -indexed-at=3:8 -indexed-at=9:8 -indexed-at=objc-message:15:8 -indexed-symbol-kind=objc-cm %s -fmodules -gmodules | FileCheck %s + +// These texual matches should be reported as comment or selector occurrences: +// CHECK3: rename [[@LINE-40]]:8 -> [[@LINE-40]]:21, [[@LINE-40]]:34 -> [[@LINE-40]]:38 +// performAction +/* performAction: with: 2 performAction */ +/*! performAction+1 +// performAction with +!*/ +/// Hello performAction with World +/// \c performAction. + +// CHECK3: comment [[@LINE-8]]:4 -> [[@LINE-8]]:17 +// CHECK3-NEXT: comment [[@LINE-8]]:4 -> [[@LINE-8]]:17 +// CHECK3-NEXT: comment [[@LINE-9]]:27 -> [[@LINE-9]]:40 +// CHECK3-NEXT: documentation [[@LINE-9]]:5 -> [[@LINE-9]]:18 +// CHECK3-NEXT: documentation [[@LINE-9]]:4 -> [[@LINE-9]]:17 +// CHECK3-NEXT: documentation [[@LINE-8]]:11 -> [[@LINE-8]]:24 +// CHECK3-NEXT: documentation [[@LINE-8]]:8 -> [[@LINE-8]]:21 + +// "performAction:with:" +// 'performAction:'with: +// CHECK3-NEXT: comment [[@LINE-2]]:5 -> [[@LINE-2]]:18 +// CHECK3-NEXT: comment [[@LINE-2]]:5 -> [[@LINE-2]]:18 + +// CHECK3-NEXT: selector [[@LINE+1]]:11 -> [[@LINE+1]]:24, [[@LINE+1]]:25 -> [[@LINE+1]]:29 +@selector(performAction:with:); +// CHECK3-NEXT: selector [[@LINE+1]]:11 -> [[@LINE+1]]:24, [[@LINE+1]]:28 -> [[@LINE+1]]:32 +@selector(performAction : with ); +// CHECK3-NEXT: selector [[@LINE+2]]:19 -> [[@LINE+2]]:32, [[@LINE+2]]:46 -> [[@LINE+2]]:50 +SEL s = @selector(//comment + performAction: /*comment*/ with + ); +// CHECK3-NEXT: selector [[@LINE+1]]:33 -> [[@LINE+1]]:46, [[@LINE+2]]:33 -> [[@LINE+2]]:37 +void call = @selector(@selector(performAction: + with: )); + +// CHECK3-NEXT: comment [[@LINE+1]]:55 +// RUN: clang-refactor-test rename-indexed-file -name=performAction:with: -new-name=foo:bar -indexed-file=%s -indexed-at=objc-cm:3:8 %s | FileCheck --check-prefix=CHECK3 %s + +// These ones shouldn't: +// performAction2 PERFORMACTION performActionWith +const char *test = "performAction:with:"; + +@selector(performAction: with ::) +@selector(performAction:) +@selector(performAction) +@selector(performAction with) +@selector(performAction:without:) +@selector(performAction:with:somethingElse:) +@selector(performAction:with "") +@selector("performAction:with:") +@selector(with: performAction:) +selector(performAction:with) +(performAction:with:) + +// CHECK3-NOT: comment +// CHECK3-NOT: documentation +// CHECK3-NOT: selector + +// It should be possible to find a selector in a file without any indexed occurrences: + +// CHECK4: selector [[@LINE+1]]:11 +@selector(nonIndexedSelector) +// CHECK4-NEXT: comment +// CHECK4-NOT: selector + +// RUN: clang-refactor-test rename-indexed-file -indexed-symbol-kind=objc-im -name=nonIndexedSelector -new-name=test -indexed-file=%s %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-indexed-file -indexed-symbol-kind=objc-cm -name=nonIndexedSelector -new-name=test -indexed-file=%s %s | FileCheck --check-prefix=CHECK4 %s + +#define MACRO doStuff +#define MACRO2(x, y) doStuff:(x) with: (y) + +@interface I +- (void)doStuff:(int)x with: y; +@end + +@implementation I +- (void)MACRO:(int)x with: y { + [self MACRO2(x, y)]; +} +@end + +// CHECK-MACRO: macro [[@LINE-5]]:9 -> [[@LINE-5]]:9 +// CHECK-MACRO-NEXT: macro [[@LINE-5]]:9 -> [[@LINE-5]]:9 +// CHECK-MACRO-NEXT: rename [[@LINE-11]]:9 -> [[@LINE-11]]:16, [[@LINE-11]]:24 -> [[@LINE-11]]:28 +// CHECK-MACRO-NOT: macro + +// RUN: clang-refactor-test rename-indexed-file -indexed-symbol-kind=objc-im -name=doStuff:with: -new-name=foo:bar -indexed-file=%s -indexed-at=114:9 -indexed-at=118:9 -indexed-at=119:9 %s | FileCheck --check-prefix=CHECK-MACRO %s diff --git a/clang/test/Refactor/Rename/IndexedObjCMethodDecl.mm b/clang/test/Refactor/Rename/IndexedObjCMethodDecl.mm new file mode 100644 index 0000000000000..23e41ac4087ff --- /dev/null +++ b/clang/test/Refactor/Rename/IndexedObjCMethodDecl.mm @@ -0,0 +1,748 @@ +@implementation foo ++(some_type_t)a_200:(void)a_200 name:(int[1 + 2 - 3])z_Z_42 usingThing:(some_type_t)world method:(void)test +class:(int[1 + 2 - 3])method __attribute__((eval { int x = 0 + 1; })) method:(({}))method { + const Object & piece = 12; +} +// CHECK1: [[@LINE-4]]:15 -> [[@LINE-4]]:20, [[@LINE-4]]:33 -> [[@LINE-4]]:37, [[@LINE-4]]:61 -> [[@LINE-4]]:71, [[@LINE-4]]:91 -> [[@LINE-4]]:97, [[@LINE-3]]:1 -> [[@LINE-3]]:6, [[@LINE-3]]:71 -> [[@LINE-3]]:77 ++(some_type_t)world:(BOOL)withSomething class:(const Object &)name struct:(some_type_t)method __attribute__((test()))a_200:(int)name +a_200:(({}))perform withSomething:(Object * (^)(BOOL, Object *))onEntity +{ + const Object & class = globalArray[i]; +} +// CHECK2: [[@LINE-5]]:15 -> [[@LINE-5]]:20, [[@LINE-5]]:41 -> [[@LINE-5]]:46, [[@LINE-5]]:68 -> [[@LINE-5]]:74, [[@LINE-5]]:118 -> [[@LINE-5]]:123, [[@LINE-4]]:1 -> [[@LINE-4]]:6, [[@LINE-4]]:21 -> [[@LINE-4]]:34 +-(some_type_t)struct:(int[1 + 2 - 3])bar +class:(int[1 + 2 - 3])foo +part:(void)piece +{ + int onEntity = "]"; +} +// CHECK3: [[@LINE-6]]:15 -> [[@LINE-6]]:21, [[@LINE-5]]:1 -> [[@LINE-5]]:6, [[@LINE-4]]:1 -> [[@LINE-4]]:5 +-(some_type_t)test:(^ { })a_200 world:(BOOL)onEntity a_200:(BOOL)perform +withSomething:(({}))method +{ + [self part: [self z_Z_42: "]" world: ("]")] world: globalArray[i] class: "string" test: globalArray[i]]; +} +// CHECK4: [[@LINE-5]]:15 -> [[@LINE-5]]:19, [[@LINE-5]]:33 -> [[@LINE-5]]:38, [[@LINE-5]]:54 -> [[@LINE-5]]:59, [[@LINE-4]]:1 -> [[@LINE-4]]:14 +-(int)a_200:(BOOL)bar piece:(Object *)perform class:(^ { })onEntity { + [globalObject send: [self perform: ']' object: [] () { ([self name: ']' a_200: ']']) other: 42]; + } foo: ']']; +} +// CHECK5: [[@LINE-4]]:7 -> [[@LINE-4]]:12, [[@LINE-4]]:23 -> [[@LINE-4]]:28, [[@LINE-4]]:47 -> [[@LINE-4]]:52 +@end +// RUN: clang-refactor-test rename-indexed-file -name=a_200:name:usingThing:method:class:method -new-name=withSomething:struct:class:onEntity:withSomething:test -indexed-file=%s -indexed-at=2:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:class:struct:a_200:a_200:withSomething -new-name=onEntity:a_200:perform:onEntity:usingThing:onEntity -indexed-file=%s -indexed-at=7:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:class:part -new-name=z_Z_42:name:usingThing -indexed-file=%s -indexed-at=13:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:world:a_200:withSomething -new-name=part:withSomething:test:struct -indexed-file=%s -indexed-at=20:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:piece:class -new-name=a_200:a_200:piece -indexed-file=%s -indexed-at=26:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK5 %s + +@interface method ++(some_type_t)bar:(BOOL)bar bar:(Object *)usingThing struct:(int[1 + 2 - 3])usingThing +perform:(BOOL)onEntity ; +// CHECK6: [[@LINE-2]]:15 -> [[@LINE-2]]:18, [[@LINE-2]]:29 -> [[@LINE-2]]:32, [[@LINE-2]]:54 -> [[@LINE-2]]:60, [[@LINE-1]]:1 -> [[@LINE-1]]:8 ++(void)struct:(^ { })part __attribute__((test()))foo:(^ { })part part:(void)class __attribute__((eval { int x = 0 + 1; })) onEntity:(Object *)method foo:(const Object &)class /*comment*/ ; +// CHECK7: [[@LINE-1]]:8 -> [[@LINE-1]]:14, [[@LINE-1]]:50 -> [[@LINE-1]]:53, [[@LINE-1]]:66 -> [[@LINE-1]]:70, [[@LINE-1]]:124 -> [[@LINE-1]]:132, [[@LINE-1]]:150 -> [[@LINE-1]]:153 +@end +// RUN: clang-refactor-test rename-indexed-file -name=bar:bar:struct:perform -new-name=bar:withSomething:test:foo -indexed-file=%s -indexed-at=39:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:foo:part:onEntity:foo -new-name=z_Z_42:part:world:piece:perform -indexed-file=%s -indexed-at=42:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK7 %s + +@interface foo ++(BOOL)class:(Object *)method struct:(void)method +perform:(Object *)class +; +// CHECK8: [[@LINE-3]]:8 -> [[@LINE-3]]:13, [[@LINE-3]]:31 -> [[@LINE-3]]:37, [[@LINE-2]]:1 -> [[@LINE-2]]:8 ++(some_type_t)usingThing:(({}))struct withSomething:(const Object &)z_Z_42 method:(some_type_t)class +a_200:(int)a_200 ; +// CHECK9: [[@LINE-2]]:15 -> [[@LINE-2]]:25, [[@LINE-2]]:39 -> [[@LINE-2]]:52, [[@LINE-2]]:76 -> [[@LINE-2]]:82, [[@LINE-1]]:1 -> [[@LINE-1]]:6 ++(Object *)a_200:(const Object &)class //comment +class:(BOOL)bar usingThing:(void (*)(some_type_t, some_type_t))name +bar:(BOOL)onEntity method:(void)method +part:(BOOL)usingThing /*comment*/ ; +// CHECK10: [[@LINE-4]]:12 -> [[@LINE-4]]:17, [[@LINE-3]]:1 -> [[@LINE-3]]:6, [[@LINE-3]]:17 -> [[@LINE-3]]:27, [[@LINE-2]]:1 -> [[@LINE-2]]:4, [[@LINE-2]]:20 -> [[@LINE-2]]:26, [[@LINE-1]]:1 -> [[@LINE-1]]:5 +@end +// RUN: clang-refactor-test rename-indexed-file -name=class:struct:perform -new-name=withSomething:foo:a_200 -indexed-file=%s -indexed-at=49:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:withSomething:method:a_200 -new-name=piece:bar:test:perform -indexed-file=%s -indexed-at=53:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK9 %s +// RUN: clang-refactor-test rename-indexed-file -name=a_200:class:usingThing:bar:method:part -new-name=a_200:piece:class:z_Z_42:piece:world -indexed-file=%s -indexed-at=56:12 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK10 %s + +@implementation onEntity +-(const Object &)test:(some_type_t (*)(some_type_t))z_Z_42 //comment +perform:(Object *)name /*comment*/ method:(int)bar __attribute__((eval { int x = 0 + 1; })) part:(Object *)name z_Z_42:(BOOL)a_200 test:(^ { })usingThing __attribute__((test())){ + call() = [self test: "]" z_Z_42: ^ { [_undef_ivar foo: "string" piece: @{ @1, @3 } perform: @"string literal" world: ']']; + } == "]" a_200: 12/*comment*/ object: 12 test: 12]; +} +// CHECK11: [[@LINE-5]]:18 -> [[@LINE-5]]:22, [[@LINE-4]]:1 -> [[@LINE-4]]:8, [[@LINE-4]]:36 -> [[@LINE-4]]:42, [[@LINE-4]]:93 -> [[@LINE-4]]:97, [[@LINE-4]]:113 -> [[@LINE-4]]:119, [[@LINE-4]]:132 -> [[@LINE-4]]:136 +-(const Object &)z_Z_42:(({}))usingThing onEntity:(^ { })world +a_200:(const Object &)withSomething __attribute__((test()))onEntity:(({}))onEntity { + [_undef_ivar object: globalArray[i] perform: ^ { globalArray[12] = [_undef_ivar class: "]" onEntity: [] { + } bar: "string" < ^ { + } foo: "string" piece: @{ @1, @3 }]; + } test: /*]*/ method: globalArray[i] +]; +} +// CHECK12: [[@LINE-8]]:18 -> [[@LINE-8]]:24, [[@LINE-8]]:42 -> [[@LINE-8]]:50, [[@LINE-7]]:1 -> [[@LINE-7]]:6, [[@LINE-7]]:60 -> [[@LINE-7]]:68 +@end +// RUN: clang-refactor-test rename-indexed-file -name=test:perform:method:part:z_Z_42:test -new-name=a_200:piece:onEntity:withSomething:world:test -indexed-file=%s -indexed-at=67:18 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK11 %s +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:onEntity:a_200:onEntity -new-name=part:world:z_Z_42:onEntity -indexed-file=%s -indexed-at=73:18 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK12 %s + +@implementation method +-(BOOL)usingThing:(void)part part:(void)world +class:(int)part perform:(some_type_t)withSomething +z_Z_42:(const Object &)class //comment +usingThing:(({}))part { + some_type_t foo = [self.undef_property onEntity: "string" withSomething: ']']; +} +// CHECK13: [[@LINE-6]]:8 -> [[@LINE-6]]:18, [[@LINE-6]]:30 -> [[@LINE-6]]:34, [[@LINE-5]]:1 -> [[@LINE-5]]:6, [[@LINE-5]]:17 -> [[@LINE-5]]:24, [[@LINE-4]]:1 -> [[@LINE-4]]:7, [[@LINE-3]]:1 -> [[@LINE-3]]:11 +@end +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:part:class:perform:z_Z_42:usingThing -new-name=class:method:withSomething:world:name:name -indexed-file=%s -indexed-at=87:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK13 %s + +@interface withSomething +-(Object *)object:(^ { })onEntity __attribute__((test()))piece:(^ { })z_Z_42 /*comment*/ ; +// CHECK14: [[@LINE-1]]:12 -> [[@LINE-1]]:18, [[@LINE-1]]:58 -> [[@LINE-1]]:63 +@end +// RUN: clang-refactor-test rename-indexed-file -name=object:piece -new-name=foo:bar -indexed-file=%s -indexed-at=98:12 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK14 %s + +@interface usingThing ++(void)part:(some_type_t)onEntity +a_200:(int)bar perform:(some_type_t)struct +; +// CHECK15: [[@LINE-3]]:8 -> [[@LINE-3]]:12, [[@LINE-2]]:1 -> [[@LINE-2]]:6, [[@LINE-2]]:16 -> [[@LINE-2]]:23 +-(some_type_t)piece:(^ { })foo name:(({}))perform part:(^ { })name z_Z_42:(Object *)test +; +// CHECK16: [[@LINE-2]]:15 -> [[@LINE-2]]:20, [[@LINE-2]]:32 -> [[@LINE-2]]:36, [[@LINE-2]]:51 -> [[@LINE-2]]:55, [[@LINE-2]]:68 -> [[@LINE-2]]:74 ++(Object *)method:(const Object &)method onEntity:(^ { })withSomething +withSomething:(int (*)(const Object &, BOOL))piece ; +// CHECK17: [[@LINE-2]]:12 -> [[@LINE-2]]:18, [[@LINE-2]]:42 -> [[@LINE-2]]:50, [[@LINE-1]]:1 -> [[@LINE-1]]:14 +-(BOOL)foo:(int[1 + 2 - 3])world +foo:(int[1 + 2 - 3])struct method:(void (*)())piece foo:(BOOL)test __attribute__((eval { int x = 0 + 1; })) ; +// CHECK18: [[@LINE-2]]:8 -> [[@LINE-2]]:11, [[@LINE-1]]:1 -> [[@LINE-1]]:4, [[@LINE-1]]:28 -> [[@LINE-1]]:34, [[@LINE-1]]:53 -> [[@LINE-1]]:56 +@end +// RUN: clang-refactor-test rename-indexed-file -name=part:a_200:perform -new-name=part:test:part -indexed-file=%s -indexed-at=104:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK15 %s +// RUN: clang-refactor-test rename-indexed-file -name=piece:name:part:z_Z_42 -new-name=usingThing:struct:onEntity:piece -indexed-file=%s -indexed-at=108:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK16 %s +// RUN: clang-refactor-test rename-indexed-file -name=method:onEntity:withSomething -new-name=z_Z_42:method:usingThing -indexed-file=%s -indexed-at=111:12 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK17 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:foo:method:foo -new-name=piece:usingThing:class:foo -indexed-file=%s -indexed-at=114:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK18 %s + +@interface foo +-(void)foo:(({}))part method:(({}))method ; +// CHECK19: [[@LINE-1]]:8 -> [[@LINE-1]]:11, [[@LINE-1]]:23 -> [[@LINE-1]]:29 +-(const Object &)foo:(({}))usingThing test:(Object *)struct __attribute__((test()))name:(const Object &)z_Z_42 /*comment*/ name:(^ { })onEntity a_200:(Object * (^)())usingThing +usingThing:(Object *)method /*comment*/ ; +// CHECK20: [[@LINE-2]]:18 -> [[@LINE-2]]:21, [[@LINE-2]]:39 -> [[@LINE-2]]:43, [[@LINE-2]]:84 -> [[@LINE-2]]:88, [[@LINE-2]]:124 -> [[@LINE-2]]:128, [[@LINE-2]]:145 -> [[@LINE-2]]:150, [[@LINE-1]]:1 -> [[@LINE-1]]:11 ++(BOOL)name:(Object *)a_200 part:(int)bar +perform:(some_type_t)method ; +// CHECK21: [[@LINE-2]]:8 -> [[@LINE-2]]:12, [[@LINE-2]]:29 -> [[@LINE-2]]:33, [[@LINE-1]]:1 -> [[@LINE-1]]:8 +-(some_type_t)usingThing:(void)test //comment +foo:(void)world z_Z_42:(Object *)bar ; +// CHECK22: [[@LINE-2]]:15 -> [[@LINE-2]]:25, [[@LINE-1]]:1 -> [[@LINE-1]]:4, [[@LINE-1]]:17 -> [[@LINE-1]]:23 +@end +// RUN: clang-refactor-test rename-indexed-file -name=foo:method -new-name=part:world -indexed-file=%s -indexed-at=124:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK19 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:test:name:name:a_200:usingThing -new-name=method:withSomething:struct:z_Z_42:bar:z_Z_42 -indexed-file=%s -indexed-at=126:18 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK20 %s +// RUN: clang-refactor-test rename-indexed-file -name=name:part:perform -new-name=withSomething:part:object -indexed-file=%s -indexed-at=129:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK21 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:foo:z_Z_42 -new-name=perform:test:class -indexed-file=%s -indexed-at=132:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK22 %s + +@implementation struct ++(void)bar:(void)piece a_200:(some_type_t)class { + [self name: [] () { + [globalObject send: [super name: [] () { + + + } perform: globalArray[i]] other: 42]; + + } usingThing: @"string literal"]; + + call() = [self usingThing: @"string literal" perform: "string"]; +} +// CHECK23: [[@LINE-11]]:8 -> [[@LINE-11]]:11, [[@LINE-11]]:24 -> [[@LINE-11]]:29 +-(void)perform:(BOOL)test +withSomething:(const Object &)foo { + return [_undef_ivar piece: globalArray[i] + globalArray[i] object: globalArray[i] + a_200: [self withSomething: [] () { [self.undef_property usingThing: ']' struct: "]" + perform: "string"]; + } + piece: ']' name: ']' a_200: "string" == ^ () { + globalArray[12] = [_undef_ivar foo: ']' foo: [] { + } + bar: ^ { + } + [] { + + + } +]; + + }] z_Z_42: ^ () { + if (^ () { + }) { + return @"string literal" < globalArray[i] == [self world: [] { + + + } object: @"string literal" part: ']']; + + } + + } a_200: "string"]; +} +// CHECK24: [[@LINE-28]]:8 -> [[@LINE-28]]:15, [[@LINE-27]]:1 -> [[@LINE-27]]:14 +@end +// RUN: clang-refactor-test rename-indexed-file -name=bar:a_200 -new-name=bar:name -indexed-file=%s -indexed-at=142:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK23 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:withSomething -new-name=withSomething:test -indexed-file=%s -indexed-at=154:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK24 %s + +@implementation object +-(int)a_200:(Object *)perform usingThing:(int)bar //comment +{ + [self bar: ']' withSomething: "string"]; + + Object * part = ']'; +} +// CHECK25: [[@LINE-6]]:7 -> [[@LINE-6]]:12, [[@LINE-6]]:31 -> [[@LINE-6]]:41 +@end +// RUN: clang-refactor-test rename-indexed-file -name=a_200:usingThing -new-name=object:bar -indexed-file=%s -indexed-at=188:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK25 %s + +@implementation test ++(void)method:(const Object &)z_Z_42 //comment +piece:(some_type_t (*)(Object *))withSomething __attribute__((eval { int x = 0 + 1; })) perform:(void)piece method:(BOOL)object { + [self piece: [] () { + ; ;[self part: 12 a_200: @"string literal"]; + + } struct: globalArray[i] part: "]"]; + + ; +} +// CHECK26: [[@LINE-9]]:8 -> [[@LINE-9]]:14, [[@LINE-8]]:1 -> [[@LINE-8]]:6, [[@LINE-8]]:89 -> [[@LINE-8]]:96, [[@LINE-8]]:109 -> [[@LINE-8]]:115 +-(some_type_t)world:(some_type_t)world +a_200:(void)bar //comment +{ + int piece = ; + + if ("string") { + // comment + + } +} +// CHECK27: [[@LINE-10]]:15 -> [[@LINE-10]]:20, [[@LINE-9]]:1 -> [[@LINE-9]]:6 +@end +// RUN: clang-refactor-test rename-indexed-file -name=method:piece:perform:method -new-name=perform:test:bar:object -indexed-file=%s -indexed-at=199:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK26 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:a_200 -new-name=z_Z_42:method -indexed-file=%s -indexed-at=209:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK27 %s + +@implementation perform +-(Object *)class:(Object * (*)(BOOL))onEntity perform:(({}))class +usingThing:(int (*)())object __attribute__((eval { int x = 0 + 1; })) piece:(BOOL (^)(BOOL, BOOL))class bar:(int)name withSomething:(const Object &)onEntity __attribute__((test())){ + // comment +} +// CHECK28: [[@LINE-4]]:12 -> [[@LINE-4]]:17, [[@LINE-4]]:47 -> [[@LINE-4]]:54, [[@LINE-3]]:1 -> [[@LINE-3]]:11, [[@LINE-3]]:71 -> [[@LINE-3]]:76, [[@LINE-3]]:105 -> [[@LINE-3]]:108, [[@LINE-3]]:119 -> [[@LINE-3]]:132 +-(int)test:(({}))onEntity +a_200:(BOOL)struct { + ([self name: ']' part: [] { + ; ;([_undef_ivar usingThing: globalArray[i] class: globalArray[i] method: globalArray[i] method: ']' class: 12]); + + } bar: 12 withSomething: "]" == [] { [self test: [self world: [_undef_ivar struct: "]" bar: @"string literal" method: globalArray[i]] usingThing: @"string literal" class: [] () { + } bar: @"string literal" +] withSomething: ([] { + + + } + "]" * ']') withSomething: @"string literal"]; + } usingThing: "]"]); + + call() = [self test: globalArray[i] name: @"string literal" perform: [self class: "]" + piece: globalArray[i] method: "string" +] +//comment + piece: "string"]; +} +// CHECK29: [[@LINE-19]]:7 -> [[@LINE-19]]:11, [[@LINE-18]]:1 -> [[@LINE-18]]:6 +-(Object *)test:(^ { })z_Z_42 class:(void)usingThing +{ + if ([self.undef_property class: globalArray[i] a_200: ^ () { ] } +]) { + if (globalArray[i]) { + some_type_t foo = [self foo: "]" perform: "string" +]; + + } + + } +} +// CHECK30: [[@LINE-12]]:12 -> [[@LINE-12]]:16, [[@LINE-12]]:31 -> [[@LINE-12]]:36 ++(int)struct:(void (^)())method +a_200:(BOOL)onEntity usingThing:(Object *)a_200 bar:(void)z_Z_42 z_Z_42:(Object *)struct perform:(BOOL (*)(Object *, Object *))method { + globalArray[12] = [self bar: ']' test: ^ () { ] } method: globalArray[i]]; + + if ([super test: "string" < globalArray[i] part: 12]) { + int bar = [self object: "]" + perform: [self a_200: "string" object: ("string") +//comment +]]; + + } +} +// CHECK31: [[@LINE-12]]:7 -> [[@LINE-12]]:13, [[@LINE-11]]:1 -> [[@LINE-11]]:6, [[@LINE-11]]:22 -> [[@LINE-11]]:32, [[@LINE-11]]:49 -> [[@LINE-11]]:52, [[@LINE-11]]:66 -> [[@LINE-11]]:72, [[@LINE-11]]:90 -> [[@LINE-11]]:97 +@end +// RUN: clang-refactor-test rename-indexed-file -name=class:perform:usingThing:piece:bar:withSomething -new-name=a_200:piece:foo:struct:onEntity:world -indexed-file=%s -indexed-at=225:12 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK28 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:a_200 -new-name=struct:world -indexed-file=%s -indexed-at=230:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK29 %s +// RUN: clang-refactor-test rename-indexed-file -name=test:class -new-name=perform:name -indexed-file=%s -indexed-at=250:12 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK30 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:a_200:usingThing:bar:z_Z_42:perform -new-name=world:piece:test:world:class:struct -indexed-file=%s -indexed-at=263:7 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK31 %s + +@implementation usingThing +-(int)foo:(int)a_200 part:(Object *)name +object:(BOOL)world __attribute__((test()))method:(const Object & (*)())test name:(some_type_t)z_Z_42 bar:(Object *)class /*comment*/ { + [self piece: ^ { + [self.undef_property name: [self z_Z_42: [self object: @"string literal" + [] { + } + z_Z_42: [] () { + + + }] class: [self method: globalArray[i] usingThing: globalArray[i] part: 12] onEntity: @{ @1, @3 } piece: "string"] object: ([self foo: @"string literal" < [self object: ']' usingThing: usingThing: globalArray[i] a_200: [] () { + }] + name: "string" foo: [] () { + + + }]) struct: "string" struct: "]" + [] { + } usingThing: ]; + + } usingThing: (@"string literal") usingThing: 12 a_200: ^ { [globalObject message] = [self onEntity: [] { + } == ^ { + + + } foo: @{ @1, @3 }]; + } withSomething: "]" == ]; +} +// CHECK32: [[@LINE-23]]:7 -> [[@LINE-23]]:10, [[@LINE-23]]:22 -> [[@LINE-23]]:26, [[@LINE-22]]:1 -> [[@LINE-22]]:7, [[@LINE-22]]:43 -> [[@LINE-22]]:49, [[@LINE-22]]:77 -> [[@LINE-22]]:81, [[@LINE-22]]:102 -> [[@LINE-22]]:105 +-(Object *)withSomething:(void)class __attribute__((test()))onEntity:(void)part struct:(some_type_t (*)(Object *))bar __attribute__((eval { int x = 0 + 1; })) perform:(Object *)world { + globalArray[12] = [self struct: globalArray[i] bar: ^ { if (']' + "string") { + return ^ () { + }; + + } + } class: ^ () { some_type_t foo = [self.undef_property withSomething: ']' bar: "]"]; + } usingThing: ']' +]; +} +// CHECK33: [[@LINE-10]]:12 -> [[@LINE-10]]:25, [[@LINE-10]]:61 -> [[@LINE-10]]:69, [[@LINE-10]]:81 -> [[@LINE-10]]:87, [[@LINE-10]]:160 -> [[@LINE-10]]:167 +@end +// RUN: clang-refactor-test rename-indexed-file -name=foo:part:object:method:name:bar -new-name=z_Z_42:z_Z_42:part:part:usingThing:z_Z_42 -indexed-file=%s -indexed-at=283:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK32 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:onEntity:struct:perform -new-name=withSomething:name:foo:name -indexed-file=%s -indexed-at=307:12 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK33 %s + +@interface a_200 ++(void)bar:(int[1 + 2 - 3])a_200 object:(Object *)z_Z_42 world:(int[1 + 2 - 3])name name:(const Object &)name world:(Object *)world /*comment*/ ; +// CHECK34: [[@LINE-1]]:8 -> [[@LINE-1]]:11, [[@LINE-1]]:34 -> [[@LINE-1]]:40, [[@LINE-1]]:58 -> [[@LINE-1]]:63, [[@LINE-1]]:85 -> [[@LINE-1]]:89, [[@LINE-1]]:111 -> [[@LINE-1]]:116 +-(int)foo:(void (^)())part usingThing:(int)usingThing object:(({}))withSomething class:(some_type_t (^)())perform /*comment*/ method:(int)foo ; +// CHECK35: [[@LINE-1]]:7 -> [[@LINE-1]]:10, [[@LINE-1]]:28 -> [[@LINE-1]]:38, [[@LINE-1]]:55 -> [[@LINE-1]]:61, [[@LINE-1]]:82 -> [[@LINE-1]]:87, [[@LINE-1]]:127 -> [[@LINE-1]]:133 +-(void)z_Z_42:(({}))withSomething name:(void (*)())struct foo:(Object *)part ; +// CHECK36: [[@LINE-1]]:8 -> [[@LINE-1]]:14, [[@LINE-1]]:35 -> [[@LINE-1]]:39, [[@LINE-1]]:59 -> [[@LINE-1]]:62 +-(BOOL)onEntity:(const Object &)z_Z_42 +piece:(const Object &)world +onEntity:(BOOL)part ; +// CHECK37: [[@LINE-3]]:8 -> [[@LINE-3]]:16, [[@LINE-2]]:1 -> [[@LINE-2]]:6, [[@LINE-1]]:1 -> [[@LINE-1]]:9 +-(BOOL)method:(some_type_t)name +part:(BOOL)a_200 part:(void)test __attribute__((eval { int x = 0 + 1; })) ; +// CHECK38: [[@LINE-2]]:8 -> [[@LINE-2]]:14, [[@LINE-1]]:1 -> [[@LINE-1]]:5, [[@LINE-1]]:18 -> [[@LINE-1]]:22 +@end +// RUN: clang-refactor-test rename-indexed-file -name=bar:object:world:name:world -new-name=withSomething:part:struct:part:withSomething -indexed-file=%s -indexed-at=323:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK34 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:usingThing:object:class:method -new-name=part:onEntity:foo:test:struct -indexed-file=%s -indexed-at=325:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK35 %s +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:name:foo -new-name=piece:z_Z_42:world -indexed-file=%s -indexed-at=327:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK36 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:piece:onEntity -new-name=part:usingThing:foo -indexed-file=%s -indexed-at=329:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK37 %s +// RUN: clang-refactor-test rename-indexed-file -name=method:part:part -new-name=perform:test:part -indexed-file=%s -indexed-at=333:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK38 %s + +@interface part ++(some_type_t)z_Z_42:(Object *)onEntity test:(int (*)())a_200 __attribute__((test())); +// CHECK39: [[@LINE-1]]:15 -> [[@LINE-1]]:21, [[@LINE-1]]:41 -> [[@LINE-1]]:45 +@end +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:test -new-name=name:onEntity -indexed-file=%s -indexed-at=344:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK39 %s + +@interface piece +-(some_type_t)part:(some_type_t)onEntity test:(Object *)world +bar:(BOOL)foo +method:(const Object & (^)(int, some_type_t))object +withSomething:(BOOL)part +; +// CHECK40: [[@LINE-5]]:15 -> [[@LINE-5]]:19, [[@LINE-5]]:42 -> [[@LINE-5]]:46, [[@LINE-4]]:1 -> [[@LINE-4]]:4, [[@LINE-3]]:1 -> [[@LINE-3]]:7, [[@LINE-2]]:1 -> [[@LINE-2]]:14 +-(some_type_t)onEntity:(void)object test:(int[1 + 2 - 3])object ; +// CHECK41: [[@LINE-1]]:15 -> [[@LINE-1]]:23, [[@LINE-1]]:37 -> [[@LINE-1]]:41 +-(some_type_t)struct:(void)name class:(some_type_t)foo name:(int (^)(BOOL, int))method +foo:(void)usingThing usingThing:(int)z_Z_42 /*comment*/ a_200:(int[1 + 2 - 3])object +; +// CHECK42: [[@LINE-3]]:15 -> [[@LINE-3]]:21, [[@LINE-3]]:33 -> [[@LINE-3]]:38, [[@LINE-3]]:56 -> [[@LINE-3]]:60, [[@LINE-2]]:1 -> [[@LINE-2]]:4, [[@LINE-2]]:22 -> [[@LINE-2]]:32, [[@LINE-2]]:57 -> [[@LINE-2]]:62 +-(BOOL)bar:(some_type_t (*)(int))part /*comment*/ object:(int[1 + 2 - 3])onEntity perform:(BOOL)usingThing /*comment*/ ; +// CHECK43: [[@LINE-1]]:8 -> [[@LINE-1]]:11, [[@LINE-1]]:51 -> [[@LINE-1]]:57, [[@LINE-1]]:83 -> [[@LINE-1]]:90 +@end +// RUN: clang-refactor-test rename-indexed-file -name=part:test:bar:method:withSomething -new-name=name:usingThing:perform:onEntity:bar -indexed-file=%s -indexed-at=350:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK40 %s +// RUN: clang-refactor-test rename-indexed-file -name=onEntity:test -new-name=onEntity:class -indexed-file=%s -indexed-at=356:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK41 %s +// RUN: clang-refactor-test rename-indexed-file -name=struct:class:name:foo:usingThing:a_200 -new-name=class:a_200:class:object:class:name -indexed-file=%s -indexed-at=358:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK42 %s +// RUN: clang-refactor-test rename-indexed-file -name=bar:object:perform -new-name=a_200:test:object -indexed-file=%s -indexed-at=362:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK43 %s + +@implementation piece ++(BOOL)name:(int (^)(BOOL, const Object &))onEntity /*comment*/ withSomething:(void)foo name:(const Object &)withSomething a_200:(Object *)struct foo:(^ { })object { + if (12) { + return 12; + + } +} +// CHECK44: [[@LINE-6]]:8 -> [[@LINE-6]]:12, [[@LINE-6]]:65 -> [[@LINE-6]]:78, [[@LINE-6]]:89 -> [[@LINE-6]]:93, [[@LINE-6]]:124 -> [[@LINE-6]]:129, [[@LINE-6]]:147 -> [[@LINE-6]]:150 ++(int)class:(some_type_t (*)(BOOL))name +name:(BOOL)onEntity __attribute__((test()))a_200:(^ { })withSomething __attribute__((eval { int x = 0 + 1; })) piece:(int)withSomething usingThing:(Object *)name { + ; ;[super method: part: ^ () { + // comment + + }]; + + [self struct: "string" == "]" withSomething: ']' class: [] () { + [globalObject send: [self.undef_property part: ^ () { + + + } class: (']' < ) + z_Z_42: ^ () { + + + } test: (12)] other: 42]; + + } object: 12 perform: ']']; +} +// CHECK45: [[@LINE-19]]:7 -> [[@LINE-19]]:12, [[@LINE-18]]:1 -> [[@LINE-18]]:5, [[@LINE-18]]:44 -> [[@LINE-18]]:49, [[@LINE-18]]:112 -> [[@LINE-18]]:117, [[@LINE-18]]:137 -> [[@LINE-18]]:147 ++(some_type_t)perform:(int[1 + 2 - 3])world //comment +withSomething:(const Object &)foo part:(int (^)(int))foo part:(int[1 + 2 - 3])perform //comment +{ + [globalObject send: [self object: ']' withSomething: 12 usingThing: ']' perform: @"string literal"] other: 42]; +} +// CHECK46: [[@LINE-5]]:15 -> [[@LINE-5]]:22, [[@LINE-4]]:1 -> [[@LINE-4]]:14, [[@LINE-4]]:35 -> [[@LINE-4]]:39, [[@LINE-4]]:58 -> [[@LINE-4]]:62 +@end +// RUN: clang-refactor-test rename-indexed-file -name=name:withSomething:name:a_200:foo -new-name=object:onEntity:world:world:piece -indexed-file=%s -indexed-at=371:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK44 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:name:a_200:piece:usingThing -new-name=method:test:z_Z_42:part:foo -indexed-file=%s -indexed-at=378:7 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK45 %s +// RUN: clang-refactor-test rename-indexed-file -name=perform:withSomething:part:part -new-name=a_200:object:perform:z_Z_42 -indexed-file=%s -indexed-at=398:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK46 %s + +@implementation z_Z_42 ++(BOOL)piece:(some_type_t (^)(int))class a_200:(BOOL (^)(some_type_t, BOOL))bar +world:(const Object & (*)(const Object &, const Object &))struct piece:(^ { })test z_Z_42:(BOOL)name +usingThing:(void (^)(Object *))struct { + [self object: ([self.undef_property struct: ^ { + /*comment*/[self.undef_property part: @"string literal" world: @"string literal" + object: "]" world: "string" world: @{ @1, @3 }]; + + } == ^ () { [super class: ^ () { + + + } * globalArray[i] piece: [] { + } bar: [self part: @"string literal" perform: "]" world: [] () { + } + world: [self.undef_property world: globalArray[i] perform: "string" struct: "]"]]]; + } + name: 12 usingThing: "]" bar: "]" +//comment +]) withSomething: ^ () { + if ("string") { + [self withSomething: [] { + + + } method: 12 piece: [] () { + + + } a_200: ']' method: "]"]; + + } + + } test: @{ @1, @3 } + class: 12 world: "string"]; +} +// CHECK47: [[@LINE-32]]:8 -> [[@LINE-32]]:13, [[@LINE-32]]:42 -> [[@LINE-32]]:47, [[@LINE-31]]:1 -> [[@LINE-31]]:6, [[@LINE-31]]:66 -> [[@LINE-31]]:71, [[@LINE-31]]:84 -> [[@LINE-31]]:90, [[@LINE-30]]:1 -> [[@LINE-30]]:11 +-(int)method:(Object *)bar //comment +part:(int)bar usingThing:(({}))name { + // comment + + [_undef_ivar world: "]" onEntity: [] { + ; ;[self part: globalArray[i] world: @{ @1, @3 }]; + + } bar: "string" == [super name: 12 piece: /*]*/ @"string literal" part: "string" method: ^ { + some_type_t object = "string"; + + } piece: (^ { + return /*]*/ "]"; + + })] onEntity: [] { + int bar = [self name: "string"/*comment*/ method: globalArray[i]]; + + }]; +} +// CHECK48: [[@LINE-18]]:7 -> [[@LINE-18]]:13, [[@LINE-17]]:1 -> [[@LINE-17]]:5, [[@LINE-17]]:15 -> [[@LINE-17]]:25 ++(Object *)class:(BOOL)object __attribute__((test()))method:(some_type_t)method +world:(Object *)struct +{ + [self method: ^ { call() = [self a_200: ']' a_200: "]"]; + } struct: ^ () { + return [] () { + }; + + } class: [_undef_ivar part: "string" name: [] { + int bar = [self withSomething: "string" bar: @"string literal" usingThing: @"string literal"]; + + }]]; + + [super foo: 12 foo: * "]" foo: ("string") foo: "string" method: 12]; +} +// CHECK49: [[@LINE-15]]:12 -> [[@LINE-15]]:17, [[@LINE-15]]:54 -> [[@LINE-15]]:60, [[@LINE-14]]:1 -> [[@LINE-14]]:6 +@end +// RUN: clang-refactor-test rename-indexed-file -name=piece:a_200:world:piece:z_Z_42:usingThing -new-name=struct:part:bar:struct:class:usingThing -indexed-file=%s -indexed-at=410:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK47 %s +// RUN: clang-refactor-test rename-indexed-file -name=method:part:usingThing -new-name=object:piece:method -indexed-file=%s -indexed-at=443:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK48 %s +// RUN: clang-refactor-test rename-indexed-file -name=class:method:world -new-name=struct:test:z_Z_42 -indexed-file=%s -indexed-at=462:12 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK49 %s + +@implementation a_200 ++(some_type_t)class:(void)z_Z_42 +object:(BOOL)perform +z_Z_42:(const Object &)usingThing name:(const Object &)a_200 test:(({}))world perform:(int[1 + 2 - 3])z_Z_42 +{ + if ("string") { + if ([super withSomething: [] { return [_undef_ivar usingThing: [] () { + } + world: [] () { + + + } foo: 12 perform: globalArray[i] name: @"string literal"]; + } world: ^ { // comment + } object: ^ { int method = "]"; + } +]) { + [globalObject send: [self.undef_property method: globalArray[i] object: [] { [self method: ']' test: [self foo: ("string") a_200: @"string literal" piece: 12 < [self class: @"string literal" piece: "]" + ']']] test: [self onEntity: @"string literal" + z_Z_42: ']'] name: globalArray[i] test: 12] other: 42]; + } class: ^ { ; ;[self perform: ("string") bar: (']') foo: "string" + foo: ^ { + + + }]; + } piece: ^ { + call() = [_undef_ivar perform: (12) bar: ^ { + + + } test: "]" name: "string" bar: "]"]; + + }]; + + } + + } + + [globalObject send: [self bar: globalArray[i] == ']' perform: [_undef_ivar z_Z_42: @"string literal" object: ^ { + [globalObject send: [self world: "]" onEntity: ']' + struct: [] () { + } perform: [self.undef_property piece: [] () { + + + } method: 12 foo: [] { + + + } onEntity: "string" * "]" method: "]"] < ']'] other: 42] other: 42]; + + } method: ^ { // comment + } test: ^ () { return [] { + }; + }] a_200: [] () { ([self.undef_property object: ^ { + } usingThing: @"string literal" + test: "string" usingThing: globalArray[i] + foo: /*]*/ globalArray[i]]); + } piece: 12]; +} +// CHECK50: [[@LINE-54]]:15 -> [[@LINE-54]]:20, [[@LINE-53]]:1 -> [[@LINE-53]]:7, [[@LINE-52]]:1 -> [[@LINE-52]]:7, [[@LINE-52]]:35 -> [[@LINE-52]]:39, [[@LINE-52]]:62 -> [[@LINE-52]]:66, [[@LINE-52]]:79 -> [[@LINE-52]]:86 +@end +// RUN: clang-refactor-test rename-indexed-file -name=class:object:z_Z_42:name:test:perform -new-name=world:foo:part:a_200:name:piece -indexed-file=%s -indexed-at=484:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK50 %s + +@implementation piece (part) ++(BOOL)method:(^ { })foo foo:(({}))foo +usingThing:(int)withSomething perform:(int[1 + 2 - 3])bar a_200:(some_type_t (^)())onEntity test:(const Object &)z_Z_42 __attribute__((eval { int x = 0 + 1; })) { + if ([self method: ']' struct: ^ { [globalObject send: [_undef_ivar world: "]" onEntity: globalArray[i] foo: "]" withSomething: < ']' onEntity: "string"] other: 42]; + } +]) { + call() = [self part: 12 + struct: "string" perform: "string" class: [] () { return "string"; + } +]; + + } +} +// CHECK51: [[@LINE-12]]:8 -> [[@LINE-12]]:14, [[@LINE-12]]:26 -> [[@LINE-12]]:29, [[@LINE-11]]:1 -> [[@LINE-11]]:11, [[@LINE-11]]:31 -> [[@LINE-11]]:38, [[@LINE-11]]:59 -> [[@LINE-11]]:64, [[@LINE-11]]:93 -> [[@LINE-11]]:97 ++(some_type_t)withSomething:(some_type_t (^)(int, Object *))z_Z_42 bar:(^ { })usingThing { + return 12; +} +// CHECK52: [[@LINE-3]]:15 -> [[@LINE-3]]:28, [[@LINE-3]]:68 -> [[@LINE-3]]:71 +-(some_type_t)object:(some_type_t (*)())bar +foo:(some_type_t (*)(const Object &))class +withSomething:(void)onEntity +usingThing:(^ { })bar a_200:(some_type_t (^)())usingThing +usingThing:(^ { })name { + call() = ([self part: "]" piece: "string" * "]" bar: globalArray[i] piece: [] () { int bar = [self test: "string" method: globalArray[i] method: "string" == /*]*/ "string" * @"string literal" object: @"string literal" usingThing: globalArray[i]]; + } < "string"]); + + if ("]" + [self part: @"string literal" class: "string"]) { + [super onEntity: "]" object: "]" piece: (12) test: ']' < ^ { + int bar = [self usingThing: [] () { + + + } z_Z_42: ^ { + } usingThing: "]" object: "]"]; + + } +]; + + } +} +// CHECK53: [[@LINE-21]]:15 -> [[@LINE-21]]:21, [[@LINE-20]]:1 -> [[@LINE-20]]:4, [[@LINE-19]]:1 -> [[@LINE-19]]:14, [[@LINE-18]]:1 -> [[@LINE-18]]:11, [[@LINE-18]]:23 -> [[@LINE-18]]:28, [[@LINE-17]]:1 -> [[@LINE-17]]:11 +@end +// RUN: clang-refactor-test rename-indexed-file -name=method:foo:usingThing:perform:a_200:test -new-name=object:z_Z_42:test:struct:perform:z_Z_42 -indexed-file=%s -indexed-at=543:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK51 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:bar -new-name=world:foo -indexed-file=%s -indexed-at=556:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK52 %s +// RUN: clang-refactor-test rename-indexed-file -name=object:foo:withSomething:usingThing:a_200:usingThing -new-name=part:a_200:method:perform:world:part -indexed-file=%s -indexed-at=560:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK53 %s + +@interface world +-(const Object &)class:(int[1 + 2 - 3])struct onEntity:(void (^)(Object *, int))withSomething __attribute__((test())); +// CHECK54: [[@LINE-1]]:18 -> [[@LINE-1]]:23, [[@LINE-1]]:47 -> [[@LINE-1]]:55 +-(int)world:(int)foo name:(const Object & (^)(int, Object *))piece a_200:(Object *)test object:(BOOL)onEntity __attribute__((eval { int x = 0 + 1; })) onEntity:(BOOL)z_Z_42 /*comment*/ ; +// CHECK55: [[@LINE-1]]:7 -> [[@LINE-1]]:12, [[@LINE-1]]:22 -> [[@LINE-1]]:26, [[@LINE-1]]:68 -> [[@LINE-1]]:73, [[@LINE-1]]:89 -> [[@LINE-1]]:95, [[@LINE-1]]:152 -> [[@LINE-1]]:160 +-(int)usingThing:(^ { })part name:(const Object &)usingThing usingThing:(^ { })perform __attribute__((test()))struct:(^ { })world +withSomething:(int[1 + 2 - 3])method //comment +; +// CHECK56: [[@LINE-3]]:7 -> [[@LINE-3]]:17, [[@LINE-3]]:30 -> [[@LINE-3]]:34, [[@LINE-3]]:62 -> [[@LINE-3]]:72, [[@LINE-3]]:111 -> [[@LINE-3]]:117, [[@LINE-2]]:1 -> [[@LINE-2]]:14 +@end +// RUN: clang-refactor-test rename-indexed-file -name=class:onEntity -new-name=usingThing:usingThing -indexed-file=%s -indexed-at=588:18 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK54 %s +// RUN: clang-refactor-test rename-indexed-file -name=world:name:a_200:object:onEntity -new-name=world:bar:onEntity:name:part -indexed-file=%s -indexed-at=590:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK55 %s +// RUN: clang-refactor-test rename-indexed-file -name=usingThing:name:usingThing:struct:withSomething -new-name=name:struct:method:method:usingThing -indexed-file=%s -indexed-at=592:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK56 %s + +@implementation onEntity +-(BOOL)piece:(some_type_t (^)(int))usingThing perform:(int)foo onEntity:(({}))withSomething __attribute__((test()))bar:(Object *)usingThing usingThing:(const Object &)bar class:(const Object & (^)(int, const Object &))perform { + [self object: "string" foo: ([self onEntity: "string" * [] { if (@"string literal") { + call() = [self z_Z_42: "string" usingThing: @"string literal" +]; + + } + } object: globalArray[i] + test: ^ () { if ("string" == @"string literal" + ^ () { + + + }) { + int name = 12 * 12; + + } + }]) foo: @{ @1, @3 }]; + + call() = [super foo: ^ () { ] } part: ']' onEntity: [self piece: ']' piece: [] () { [super name: "]" perform: "string"]; + }] + struct: "]" part: @"string literal"]; +} +// CHECK57: [[@LINE-20]]:8 -> [[@LINE-20]]:13, [[@LINE-20]]:47 -> [[@LINE-20]]:54, [[@LINE-20]]:64 -> [[@LINE-20]]:72, [[@LINE-20]]:116 -> [[@LINE-20]]:119, [[@LINE-20]]:141 -> [[@LINE-20]]:151, [[@LINE-20]]:172 -> [[@LINE-20]]:177 +-(int)withSomething:(int[1 + 2 - 3])foo usingThing:(Object * (^)(some_type_t, BOOL))a_200 perform:(some_type_t)onEntity __attribute__((test())){ + [globalObject message] = [self.undef_property struct: @"string literal" object: "]" < ']' withSomething: @"string literal" piece: [self.undef_property withSomething: 12 foo: @"string literal" withSomething: "]" object: [] () { Object * z_Z_42 = 12 + "string"; + } part: ^ () { + const Object & z_Z_42 = globalArray[i]; + + }]]; + + some_type_t foo = [self.undef_property foo: 12 * globalArray[i] method: @"string literal" a_200: ^ () { + globalArray[12] = [self onEntity: @"string literal" class: "string" onEntity: "]"]; + + }/*comment*/]; +} +// CHECK58: [[@LINE-12]]:7 -> [[@LINE-12]]:20, [[@LINE-12]]:41 -> [[@LINE-12]]:51, [[@LINE-12]]:91 -> [[@LINE-12]]:98 ++(int)piece:(int)test +onEntity:(int[1 + 2 - 3])test piece:(const Object &)piece { + [self z_Z_42: ']' usingThing: ("]")]; + + if ([self test: [] () { + // comment + + } a_200: [] () { BOOL foo = [self world: [self.undef_property test: globalArray[i] foo: [self method: "]" onEntity: [] { + + + } withSomething: globalArray[i]] piece: 12 struct: ']'] usingThing: [super test: globalArray[i] world: [_undef_ivar bar: @{ @1, @3 } z_Z_42: [_undef_ivar bar: z_Z_42: [self foo: ("string") object: ^ () { + + + } bar: "string"] struct: (12) withSomething: @"string literal" name: [self.undef_property part: @"string literal" onEntity: [] { + + + }]] onEntity: ']' name: 12 object: @{ @1, @3 }] +//comment +] part: globalArray[i] test: 12]; + } + ']' bar: [super piece: "]" world: @"string literal" object: globalArray[i] test: ']' + withSomething: @{ @1, @3 }] piece: [self a_200: ']' world: ']'] onEntity: @"string literal"] == ^ () { ] }) { + if (12) { + [self.undef_property withSomething: [self a_200: globalArray[i] withSomething: "string" + test: [] () { [self perform: "]" object: ']' foo: @{ @1, @3 } + [] () { + }]; + } object: "string" struct: /*]*/ "]"] piece: @{ @1, @3 } a_200: 12 bar: globalArray[i]]; + + } + + } +} +// CHECK59: [[@LINE-31]]:7 -> [[@LINE-31]]:12, [[@LINE-30]]:1 -> [[@LINE-30]]:9, [[@LINE-30]]:31 -> [[@LINE-30]]:36 ++(some_type_t)withSomething:(BOOL)bar +z_Z_42:(BOOL)struct { + [self usingThing: (globalArray[i]) world: @"string literal" name: [super test: globalArray[i] part: ^ () { + [super test: 12 z_Z_42: "string"]; + + } perform: ']' perform: "string"] object: "]" name: 12]; + + /*comment*/[self world: "string" bar: ^ { + [self.undef_property bar: @"string literal" name: ^ { + + + } z_Z_42: "string" + [_undef_ivar name: [self part: 12 struct: [] { + } +] withSomething: @"string literal" class: ']' < "string" == "]" < "string" == "]"] onEntity: globalArray[i] object: "]"]; + + } == ']' test: @"string literal" world: "]" struct: "string"]; +} +// CHECK60: [[@LINE-17]]:15 -> [[@LINE-17]]:28, [[@LINE-16]]:1 -> [[@LINE-16]]:7 +@end +// RUN: clang-refactor-test rename-indexed-file -name=piece:perform:onEntity:bar:usingThing:class -new-name=method:bar:piece:onEntity:foo:piece -indexed-file=%s -indexed-at=602:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK57 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:usingThing:perform -new-name=name:usingThing:foo -indexed-file=%s -indexed-at=623:7 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK58 %s +// RUN: clang-refactor-test rename-indexed-file -name=piece:onEntity:piece -new-name=class:part:world -indexed-file=%s -indexed-at=636:7 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK59 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:z_Z_42 -new-name=object:object -indexed-file=%s -indexed-at=668:15 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK60 %s + +@interface test (z_Z_42) +-(void)struct:(some_type_t)onEntity perform:(void)struct +usingThing:(BOOL)foo onEntity:(some_type_t)onEntity /*comment*/ a_200:(Object *)piece ; +// CHECK61: [[@LINE-2]]:8 -> [[@LINE-2]]:14, [[@LINE-2]]:37 -> [[@LINE-2]]:44, [[@LINE-1]]:1 -> [[@LINE-1]]:11, [[@LINE-1]]:22 -> [[@LINE-1]]:30, [[@LINE-1]]:65 -> [[@LINE-1]]:70 +@end +// RUN: clang-refactor-test rename-indexed-file -name=struct:perform:usingThing:onEntity:a_200 -new-name=method:world:foo:foo:perform -indexed-file=%s -indexed-at=693:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK61 %s + +@implementation part +-(some_type_t)bar:(some_type_t (^)(some_type_t, Object *))class piece:(some_type_t)z_Z_42 +test:(BOOL)z_Z_42 /*comment*/ class:(void)class __attribute__((test())){ + [globalObject send: [self onEntity: == ']' z_Z_42: [] { int bar = [_undef_ivar a_200: [super a_200: @"string literal" +//comment + withSomething: @"string literal" + ^ { + }] foo: "]"] other: 42]; + } test: ^ () { if ([] () { + + + }) { + int bar = [self part: ^ () { + } + class: [] () { + } object: @"string literal"]; + + } + }]; + + [globalObject message] = [_undef_ivar part: (@{ @1, @3 }) struct: "]" z_Z_42: [self perform: @"string literal" usingThing: ']' a_200: ^ { // comment + } struct: ^ () { + if ("string") { + int test = "string"; + + } + + }]]; +} +// CHECK62: [[@LINE-27]]:15 -> [[@LINE-27]]:18, [[@LINE-27]]:65 -> [[@LINE-27]]:70, [[@LINE-26]]:1 -> [[@LINE-26]]:5, [[@LINE-26]]:31 -> [[@LINE-26]]:36 +@end +// RUN: clang-refactor-test rename-indexed-file -name=bar:piece:test:class -new-name=perform:bar:struct:world -indexed-file=%s -indexed-at=700:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK62 %s + +@interface perform ++(void)piece:(BOOL)a_200 onEntity:(const Object & (^)())object bar:(int (*)())method //comment +method:(some_type_t)onEntity +; +// CHECK63: [[@LINE-3]]:8 -> [[@LINE-3]]:13, [[@LINE-3]]:26 -> [[@LINE-3]]:34, [[@LINE-3]]:64 -> [[@LINE-3]]:67, [[@LINE-2]]:1 -> [[@LINE-2]]:7 +-(void)withSomething:(const Object &)piece //comment +piece:(void)perform ; +// CHECK64: [[@LINE-2]]:8 -> [[@LINE-2]]:21, [[@LINE-1]]:1 -> [[@LINE-1]]:6 +-(some_type_t)z_Z_42:(int)method piece:(^ { })struct struct:(BOOL)world /*comment*/ a_200:(const Object &)piece +; +// CHECK65: [[@LINE-2]]:15 -> [[@LINE-2]]:21, [[@LINE-2]]:34 -> [[@LINE-2]]:39, [[@LINE-2]]:54 -> [[@LINE-2]]:60, [[@LINE-2]]:85 -> [[@LINE-2]]:90 ++(Object *)foo:(const Object &)part bar:(some_type_t)a_200 ; +// CHECK66: [[@LINE-1]]:12 -> [[@LINE-1]]:15, [[@LINE-1]]:37 -> [[@LINE-1]]:40 +@end +// RUN: clang-refactor-test rename-indexed-file -name=piece:onEntity:bar:method -new-name=onEntity:withSomething:class:piece -indexed-file=%s -indexed-at=732:8 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK63 %s +// RUN: clang-refactor-test rename-indexed-file -name=withSomething:piece -new-name=perform:foo -indexed-file=%s -indexed-at=736:8 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK64 %s +// RUN: clang-refactor-test rename-indexed-file -name=z_Z_42:piece:struct:a_200 -new-name=test:withSomething:piece:class -indexed-file=%s -indexed-at=739:15 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK65 %s +// RUN: clang-refactor-test rename-indexed-file -name=foo:bar -new-name=usingThing:a_200 -indexed-file=%s -indexed-at=742:12 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK66 %s diff --git a/clang/test/Refactor/Rename/IndexedObjCProperty.m b/clang/test/Refactor/Rename/IndexedObjCProperty.m new file mode 100644 index 0000000000000..d8725754932ea --- /dev/null +++ b/clang/test/Refactor/Rename/IndexedObjCProperty.m @@ -0,0 +1,30 @@ +@interface I1 + +@property int p1; + +@end + +@implementation I1 + +- (void)foo { + self.p1; // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:10 + [self p1]; // CHECK: rename [[@LINE]]:9 -> [[@LINE]]:11 + [self setP1: 2]; // CHECK: rename "setFoo" [[@LINE]]:9 -> [[@LINE]]:14 + _p1 = 3; // CHECK: rename "_foo" [[@LINE]]:3 -> [[@LINE]]:6 +} + +@end + +// RUN: clang-refactor-test rename-indexed-file -name=p1 -name=p1 -name=setP1 -name=_p1 -new-name=foo -new-name=foo -new-name=setFoo -new-name=_foo -indexed-file=%s -indexed-at=10:8 -indexed-at=objc-im:1:11:9 -indexed-at=objc-im:2:12:9 -indexed-at=3:13:3 %s | FileCheck %s + +// p1 _p1 setP1 +// CHECK: comment [[@LINE-1]]:4 +// CHECK: comment "_foo" [[@LINE-2]]:7 +// CHECK: comment "setFoo" [[@LINE-3]]:11 + +@selector(p1) +@selector(setP1:) +@selector(_p1) +// CHECK: selector [[@LINE-3]]:11 +// CHECK: selector "setFoo" [[@LINE-3]]:11 +// CHECK-NOT: selector diff --git a/clang/test/Refactor/Rename/Inputs/MultiFileTUHeader.h b/clang/test/Refactor/Rename/Inputs/MultiFileTUHeader.h new file mode 100644 index 0000000000000..2e8d29fa5824c --- /dev/null +++ b/clang/test/Refactor/Rename/Inputs/MultiFileTUHeader.h @@ -0,0 +1,6 @@ +class Foo { // CHECK: rename "{{.*}}/Inputs/MultiFileTUHeader.h" [[@LINE]]:7 -> [[@LINE]]:10 +public: + Foo(); // CHECK: rename "{{.*}}/Inputs/MultiFileTUHeader.h" [[@LINE]]:3 -> [[@LINE]]:6 + + void method(); +}; diff --git a/clang/test/Refactor/Rename/Inputs/ObjCImplementationTURequestsImplementation.m b/clang/test/Refactor/Rename/Inputs/ObjCImplementationTURequestsImplementation.m new file mode 100644 index 0000000000000..249aa7f36a1ef --- /dev/null +++ b/clang/test/Refactor/Rename/Inputs/ObjCImplementationTURequestsImplementation.m @@ -0,0 +1,3 @@ +@implementation ExplicitIVarsInInterface + +@end diff --git a/clang/test/Refactor/Rename/Inputs/objc-system-header.h b/clang/test/Refactor/Rename/Inputs/objc-system-header.h new file mode 100644 index 0000000000000..23b252a726bab --- /dev/null +++ b/clang/test/Refactor/Rename/Inputs/objc-system-header.h @@ -0,0 +1,5 @@ +@interface MySystemClass + +- (void)someMethod:(int)x with:(int)y; + +@end diff --git a/clang/test/Refactor/Rename/Inputs/rename-indexed-file.cpp b/clang/test/Refactor/Rename/Inputs/rename-indexed-file.cpp new file mode 100644 index 0000000000000..ec6b78d1c9ef1 --- /dev/null +++ b/clang/test/Refactor/Rename/Inputs/rename-indexed-file.cpp @@ -0,0 +1,4 @@ +void Test::otherFile() { + Test m; + m.~Test(); +} diff --git a/clang/test/Refactor/Rename/Inputs/system-header.h b/clang/test/Refactor/Rename/Inputs/system-header.h new file mode 100644 index 0000000000000..ae1af94d1ba57 --- /dev/null +++ b/clang/test/Refactor/Rename/Inputs/system-header.h @@ -0,0 +1,11 @@ +void systemFunction(); + +struct SystemStruct { +}; +typedef struct SystemStruct SystemTypedef; + +enum SystemEnum { + SystemEnumCase +}; + +extern int systemVariable; diff --git a/clang/test/Refactor/Rename/LocalBlockSymbol.m b/clang/test/Refactor/Rename/LocalBlockSymbol.m new file mode 100644 index 0000000000000..93235aac15f19 --- /dev/null +++ b/clang/test/Refactor/Rename/LocalBlockSymbol.m @@ -0,0 +1,58 @@ +__auto_type escaping1 = ^ { + struct Local { // LOCAL1: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:2:10 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=LOCAL1 %s + + int x;// ESCAPES1: rename [[@LINE]] + // NOESCAPE1: rename local [[@LINE-1]] +// RUN: clang-refactor-test rename-initiate -at=%s:5:9 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=ESCAPES1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:5:9 -new-name=name %s -fblocks -x objective-c | FileCheck --check-prefix=NOESCAPE1 %s + }; + struct Local result; + return result; +}; + +__auto_type escaping2 = ^ () { // no prototype, + struct Local { // LOCAL2: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:15:10 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=LOCAL2 %s + + int x;// ESCAPES2: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:18:9 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=ESCAPES2 %s + }; + struct Local result; + return result; +}; + +__auto_type outer1 = ^ { + __auto_type escaping3 = ^ (int x) { // prototype with some arguments. + struct Local { // LOCAL3: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:27:12 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=LOCAL3 %s + + int x;// ESCAPES3: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:30:11 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=ESCAPES3 %s + }; + struct Local result; + return result; + }; + return escaping3(0); +}; + +void outer2() { + __auto_type escaping1 = ^ { + struct Local { + int x;// LOCAL4: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:42:11 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=LOCAL4 %s + }; + struct Local result; + return result; + }; +} + +__auto_type normalBlock = ^int (void) { + struct Local { // LOCAL5: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:51:10 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=LOCAL5 %s + + int x;// LOCAL6: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:54:9 -new-name=name %s -fblocks -x objective-c-header | FileCheck --check-prefix=LOCAL6 %s + }; + return 0; +}; diff --git a/clang/test/Refactor/Rename/LocalBlockSymbolCpp.mm b/clang/test/Refactor/Rename/LocalBlockSymbolCpp.mm new file mode 100644 index 0000000000000..d48f3b5035c20 --- /dev/null +++ b/clang/test/Refactor/Rename/LocalBlockSymbolCpp.mm @@ -0,0 +1,24 @@ +auto escaping1 = ^ { + struct Global { // ESCAPES1: rename [[@LINE]] + // NOESCAPE1: rename local [[@LINE-1]] +// RUN: clang-refactor-test rename-initiate -at=%s:2:10 -new-name=name %s -std=c++14 -fblocks -x objective-c++-header | FileCheck --check-prefix=ESCAPES1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:2:10 -new-name=name %s -std=c++14 -fblocks -x objective-c++ | FileCheck --check-prefix=NOESCAPE1 %s + }; + return Global(); +}; + +void outer1() { + auto escaping1 = ^ { + struct Local { // LOCAL1: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:12:12 -new-name=name %s -std=c++14 -fblocks -x objective-c++-header | FileCheck --check-prefix=LOCAL1 %s + }; + return Local(); + }; +} + +auto normalBlock = ^int () { + struct Local { // LOCAL2: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:20:10 -new-name=name %s -std=c++14 -fblocks -x objective-c++-header | FileCheck --check-prefix=LOCAL2 %s + }; + return 0; +}; diff --git a/clang/test/Refactor/Rename/LocalSymbol.cpp b/clang/test/Refactor/Rename/LocalSymbol.cpp new file mode 100644 index 0000000000000..4bc82ed56c915 --- /dev/null +++ b/clang/test/Refactor/Rename/LocalSymbol.cpp @@ -0,0 +1,232 @@ +static int staticIsGlobalVar = 0; // CHECK1: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:1:12 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK1 %s + +int globalVar = 0; // CHECK2: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:4:5 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK2 %s + +struct GlobalFoo { // CHECK3: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:7:8 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK3 %s + + struct GlobalBar { // CHECK4: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:10:10 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK4 %s + }; + + void foo() { // CHECK5: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:14:8 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK5 %s + + struct LocalFoo { }; // CHECK6: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:17:12 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK6 %s + } + + virtual void bar() { } // CHECK-BAR: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:21:16 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK-BAR %s + + int globalField; // CHECK7: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:24:7 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK7 %s +}; + +enum GlobalEnum { // CHECK8: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:28:6 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK8 %s + + GlobalEnumCase = 0 // CHECK9: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:31:3 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK9 %s +}; + +namespace { + struct AnonymousIsGlobalFoo { }; // CHECK10: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:36:10 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=CHECK10 %s + + void globalFoo() { } // CHECK11: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:39:8 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=CHECK11 %s +} + +namespace globalNamespace { // CHECK12: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:43:11 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK12 %s +} + +void func(int localParam) { // CHECK13: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:47:15 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK13 %s + + int localVar = 0; // CHECK14: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:50:7 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK14 %s + + enum LocalEnum { // CHECK15: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:53:8 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK15 %s + + LocalEnumCase = 0 // CHECK16: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:56:5 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK16 %s + }; + + struct LocalFoo: GlobalFoo { // CHECK17: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:60:10 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK17 %s + + struct LocalBar { }; // CHECK18: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:63:12 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK18 %s + + void foo() { } // CHECK19: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:66:10 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK19 %s + + // Bar is global since it overrides GlobalFoo::bar + void bar() { } // CHECK-BAR: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:70:10 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK-BAR %s + + int localField; // CHECK20: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:73:10 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK20 %s + }; +} + +auto escapable1 = []() -> auto { + struct Global { // ESCAPES1: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:79:10 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES1 %s + + int field = 2; // ESCAPES2: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:82:9 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES2 %s + + void foo() { } // ESCAPES3: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:85:10 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES3 %s + }; + return Global(); +}; + +auto escapable2() { + struct Global { // ESCAPES4: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:92:10 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES4 %s + + int field = 2; // ESCAPES5: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:95:9 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES5 %s + + void foo() { } // ESCAPES6: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:98:10 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES6 %s + }; + return Global(); +} + +template +struct C { + T x; +}; + +decltype(auto) escapable4() { + struct Global { // ESCAPES7: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:110:10 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES7 %s + }; + return C(); +} + +auto escapable5() -> decltype(auto) { + struct Foo { + struct Global { // ESCAPES8: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:118:12 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES8 %s + }; + }; + return C(); +} + +auto escapable6() { + struct Foo { + struct Global { // ESCAPES9: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:127:12 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES9 %s + }; + }; + return Foo::Global(); +} + +auto escapableOuter1() { + struct Foo { + auto escapableInner() { + struct Global { // ESCAPES10: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:137:14 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES10 %s + }; + return Global(); + } + } + return Foo().escapableInner(); +} + +auto escapableOuter2() { + auto escapableInner = []() -> auto { + struct Global { // ESCAPES11: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:148:12 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES11 %s + }; + return Global(); + } + return escapableInner(); +} + +void outer() { + auto escapableInner2 = []() -> auto { + struct Local { // ESCAPES12: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:158:12 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES12 %s + }; + return Local(); + } + struct Foo { + auto foo() { + struct Local { // ESCAPES13: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:165:14 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES13 %s + }; + return Local(); + } + }; +} + +struct Escapable { + auto escapable() { + struct Global { // ESCAPES14: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:175:12 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES14 %s + }; + return Global(); + } +}; + +auto escapableViaTypedef() { + struct Global { // ESCAPES15: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:183:10 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ESCAPES15 %s + }; + typedef Foo Global; + return Foo(); +} + +auto nonescapable1 = []() -> auto { + struct Local { // NOESCAPE1: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:191:10 -new-name=name %s -std=c++14 -x c++ | FileCheck --check-prefix=NOESCAPE1 %s + + int field = 2; // NOESCAPE2: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:194:9 -new-name=name %s -std=c++14 -x c++ | FileCheck --check-prefix=NOESCAPE2 %s + + void foo() { } // NOESCAPE3: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:197:10 -new-name=name %s -std=c++14 -x c++ | FileCheck --check-prefix=NOESCAPE3 %s + }; + return Local(); +}; + +static void localOrGlobal1() { }; // ISGLOBAL1: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:203:13 -new-name=name %s -std=c++14 -x c++ | FileCheck --check-prefix=ISGLOBAL1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:203:13 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ISGLOBAL1 %s + +namespace { + +struct LocalOrGlobal { }; // ISGLOBAL2: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:209:13 -new-name=name %s -std=c++14 -x c++ | FileCheck --check-prefix=ISGLOBAL2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:209:13 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ISGLOBAL2 %s + + +void localOrGlobal2() { }; // ISGLOBAL3: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:214:13 -new-name=name %s -std=c++14 | FileCheck --check-prefix=ISGLOBAL3 %s +} + + +struct LocalOrGlobalWrapper1 { + + static void foo() { } // ISGLOBAL4: rename [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:221:15 -new-name=name %s -std=c++14 -x c++ | FileCheck --check-prefix=ISGLOBAL4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:221:15 -new-name=name %s -std=c++14 -x c++-header | FileCheck --check-prefix=ISGLOBAL4 %s +}; + +void func2(int x) { + auto lambda1 = [x] // CHECK21: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:226:16 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK21 %s + (int z) { // CHECK22: rename local [[@LINE]] +// RUN: clang-refactor-test rename-initiate -at=%s:229:10 -new-name=name %s -std=c++14 | FileCheck --check-prefix=CHECK22 %s + } +} diff --git a/clang/test/Refactor/Rename/MemberExprMacro.cpp b/clang/test/Refactor/Rename/MemberExprMacro.cpp new file mode 100644 index 0000000000000..dfd7ec013d44c --- /dev/null +++ b/clang/test/Refactor/Rename/MemberExprMacro.cpp @@ -0,0 +1,19 @@ +class Baz { +public: + int Foo; /* Test 1 */ // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 +}; + +int qux(int x) { return 0; } +#define MACRO(a) qux(a) + +int main() { + Baz baz; + baz.Foo = 1; /* Test 2 */ // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 + MACRO(baz.Foo); // CHECK: rename [[@LINE]]:13 -> [[@LINE]]:16 + int y = baz.Foo; // CHECK: rename [[@LINE]]:15 -> [[@LINE]]:18 +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:3:7 -new-name=Bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:11:7 -new-name=Bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/MultiFileTU.cpp b/clang/test/Refactor/Rename/MultiFileTU.cpp new file mode 100644 index 0000000000000..14b0be25ab620 --- /dev/null +++ b/clang/test/Refactor/Rename/MultiFileTU.cpp @@ -0,0 +1,9 @@ +#include "Inputs/MultiFileTUHeader.h" + +Foo::Foo() {} // CHECK: rename "{{.*}}MultiFileTU.cpp" [[@LINE]]:1 -> [[@LINE]]:4 +// CHECK: rename "{{.*}}MultiFileTU.cpp" [[@LINE-1]]:6 -> [[@LINE-1]]:9 + +void Foo::method() { } // CHECK: rename "{{.*}}MultiFileTU.cpp" [[@LINE]]:6 -> [[@LINE]]:9 + +// RUN: clang-refactor-test rename-initiate -at=%s:3:6 -at=%s:6:6 -new-name=Bar %s | FileCheck %s +// RUN: clang-refactor-test rename-initiate -at=%s:3:6 -at=%s:6:6 -new-name=Bar %s | FileCheck %S/Inputs/MultiFileTUHeader.h diff --git a/clang/test/Refactor/Rename/Namespace.cpp b/clang/test/Refactor/Rename/Namespace.cpp new file mode 100644 index 0000000000000..1a0900f4abd92 --- /dev/null +++ b/clang/test/Refactor/Rename/Namespace.cpp @@ -0,0 +1,22 @@ +namespace gcc /* Test 1 */ { // CHECK: rename [[@LINE]]:11 -> [[@LINE]]:14 + int x; +} + +void boo() { + gcc::x = 42; // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:1:11 -new-name=clang %s | FileCheck %s + +namespace ns1 { +namespace ns2 { // CHECK2: rename [[@LINE]]:11 -> [[@LINE]]:14 +void f(); +} +} + +void testVisitTwice() { + ns1::ns2::f(); // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:11 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:19:8 -new-name=clang %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Rename/NoNewName.cpp b/clang/test/Refactor/Rename/NoNewName.cpp new file mode 100644 index 0000000000000..0fe0069af369c --- /dev/null +++ b/clang/test/Refactor/Rename/NoNewName.cpp @@ -0,0 +1,4 @@ +// Check for an error while -new-name argument has not been passed to +// clang-rename. +// RUN: not clang-refactor-test rename-initiate -at=%s:1:11 %s 2>&1 | FileCheck %s +// CHECK: clang-refactor-test: for the -new-name option: must be specified at least once diff --git a/clang/test/Refactor/Rename/ObjCClass.m b/clang/test/Refactor/Rename/ObjCClass.m new file mode 100644 index 0000000000000..aacfc2c6b31bb --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCClass.m @@ -0,0 +1,95 @@ +@class I1, // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + I2; // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:10 + +@interface I1 // CHECK1: rename [[@LINE]]:12 -> [[@LINE]]:14 +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:1:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:4:12 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:2:8 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +@implementation I1 { // CHECK1: rename [[@LINE]]:17 -> [[@LINE]]:19 + I1 *interfaceIVar; // CHECK1: rename [[@LINE]]:3 -> [[@LINE]]:5 + // CHECK4: rename [[@LINE-1]]:7 -> [[@LINE-1]]:20 + int ivar; // CHECK3: rename [[@LINE]]:7 -> [[@LINE]]:11 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:11:17 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:12:3 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:21:20 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +-(void)foo: (const I1 *)bar { // CHECK1: rename [[@LINE]]:20 -> [[@LINE]]:22 + + ivar = 1; // CHECK3: rename [[@LINE]]:3 -> [[@LINE]]:7 + self->ivar = 2; // CHECK3: rename [[@LINE]]:9 -> [[@LINE]]:13 + print(bar->ivar);// CHECK3: rename [[@LINE]]:14 -> [[@LINE]]:18 + interfaceIVar->ivar = 4; // CHECK4: rename [[@LINE]]:3 -> [[@LINE]]:16 + // CHECK3: rename [[@LINE-1]]:18 -> [[@LINE-1]]:22 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:14:7 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:23:3 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:24:9 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:25:14 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:26:18 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:12:7 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:26:3 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s + +@end + +@interface I1 (Category) // CHECK1: rename [[@LINE]]:12 -> [[@LINE]]:14 +@end // CHECK5: rename [[@LINE-1]]:16 -> [[@LINE-1]]:24 + +@implementation I1 (Category) // CHECK1: rename [[@LINE]]:17 -> [[@LINE]]:19 +@end // CHECK5: rename [[@LINE-1]]:21 -> [[@LINE-1]]:29 + +// RUN: clang-refactor-test rename-initiate -at=%s:41:12 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:44:17 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:41:16 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test rename-initiate -at=%s:44:21 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s + +// Implementation only-category: + +@interface I3 // CHECK6: rename [[@LINE]]:12 -> [[@LINE]]:14 +@end + +@implementation I3 (DummyCategory) // CHECK6: rename [[@LINE]]:17 -> [[@LINE]]:19 +@end // CHECK7: rename [[@LINE-1]]:21 -> [[@LINE-1]]:34 + +// RUN: clang-refactor-test rename-initiate -at=%s:55:12 -new-name=foo %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test rename-initiate -at=%s:58:17 -new-name=foo %s | FileCheck --check-prefix=CHECK6 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:58:21 -new-name=foo %s | FileCheck --check-prefix=CHECK7 %s + +// Class extension: + +@interface I3 () // CHECK6: rename [[@LINE]]:12 -> [[@LINE]]:14 +@end + +@implementation I3 // CHECK6: rename [[@LINE]]:17 -> [[@LINE]]:19 +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:68:12 -new-name=foo %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test rename-initiate -at=%s:71:17 -new-name=foo %s | FileCheck --check-prefix=CHECK6 %s + +// Ivar declared in the interface: + +@interface I4 { + @public + int ivar1; // CHECK8: rename [[@LINE]]:7 -> [[@LINE]]:12 +} +@end + +@implementation I4 { +} + +- (void)foo { + ivar1 = 0; // CHECK8: rename [[@LINE]]:3 -> [[@LINE]]:8 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:81:7 -new-name=foo %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test rename-initiate -at=%s:89:3 -new-name=foo %s | FileCheck --check-prefix=CHECK8 %s diff --git a/clang/test/Refactor/Rename/ObjCClassProperty.m b/clang/test/Refactor/Rename/ObjCClassProperty.m new file mode 100644 index 0000000000000..4c8c9e0267d1e --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCClassProperty.m @@ -0,0 +1,100 @@ +@interface ExplicitClassProperty + +@property(class) int p1; // CHECK1: rename [[@LINE]]:22 -> [[@LINE]]:24 +@property(class, readonly) int p2; // CHECK2: rename [[@LINE]]:32 -> [[@LINE]]:34 + ++ (int)p1; // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + +@end + +@implementation ExplicitClassProperty + +@dynamic p1; // CHECK1: rename [[@LINE]]:10 -> [[@LINE]]:12 + +@dynamic p2; // CHECK2: rename [[@LINE]]:10 -> [[@LINE]]:12 + ++ (int)p1 { // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + return 1; +} +// TODO: remove ++ (void)setP1:(int)x { // CHECK1: rename [[@LINE]]:9 -> [[@LINE]]:14 +} + ++ (int)p2 { // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:10 + return 2; +} + +- (void)foo { + ExplicitClassProperty.p1 = // CHECK1: rename [[@LINE]]:25 -> [[@LINE]]:27 + ExplicitClassProperty.p2; // CHECK2: rename [[@LINE]]:27 -> [[@LINE]]:29 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:3:22 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:6:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:12:10 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:16:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:20:9 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:28:25 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:4:32 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:14:10 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:23:8 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:29:27 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +@interface ImplicitClassProperty + ++(int)p3; // CHECK3: rename [[@LINE]]:7 -> [[@LINE]]:9 ++(void)setP3:(int)x; // CHECK4: rename [[@LINE]]:8 -> [[@LINE]]:13 ++(int)p4; // CHECK5: rename [[@LINE]]:7 -> [[@LINE]]:9 + +@end + +@implementation ImplicitClassProperty + ++ (int)p3 { // CHECK3: rename [[@LINE]]:8 -> [[@LINE]]:10 + return 0; +} + +- (void)foo { + ImplicitClassProperty.p3 = // CHECK3: implicit-property [[@LINE]]:25 -> [[@LINE]]:27 + // CHECK4: implicit-property [[@LINE-1]]:25 -> [[@LINE-1]]:30 + ImplicitClassProperty.p4; // CHECK5: implicit-property [[@LINE]]:31 -> [[@LINE]]:33 + (void)ImplicitClassProperty.p3; // CHECK3: implicit-property [[@LINE]]:31 -> [[@LINE]]:33 + // CHECK4: implicit-property [[@LINE-1]]:31 -> [[@LINE-1]]:36 + + int x = [ImplicitClassProperty p3]; // CHECK3: rename [[@LINE]]:34 -> [[@LINE]]:36 + [ImplicitClassProperty setP3: x]; // CHECK4: rename [[@LINE]]:26 -> [[@LINE]]:31 + x = [ImplicitClassProperty p4]; // CHECK5: rename [[@LINE]]:30 -> [[@LINE]]:32 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:48:7 -at=%s:64:31 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:49:8 -at=%s:61:25 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:50:7 -at=%s:63:31 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s + + + + + + + + +@interface ClassReceivers // CHECK-RECEIVER: rename [[@LINE]]:12 -> [[@LINE]]:26 + +@property(class) int p1; ++ (int)implicit; ++ (void)setImplicit:(int)x; + +@end + +void classReceivers() { + ClassReceivers.p1 = 0; // CHECK-RECEIVER: rename [[@LINE]]:3 -> [[@LINE]]:17 + int y = ClassReceivers.p1; // CHECK-RECEIVER: rename [[@LINE]]:11 -> [[@LINE]]:25 + ClassReceivers.implicit = 0; // CHECK-RECEIVER: rename [[@LINE]]:3 -> [[@LINE]]:17 + int x = ClassReceivers.implicit; // CHECK-RECEIVER: rename [[@LINE]]:11 -> [[@LINE]]:25 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:94:3 -at=%s:95:11 -at=%s:96:3 -at=%s:97:11 -new-name=x %s | FileCheck --check-prefix=CHECK-RECEIVER %s diff --git a/clang/test/Refactor/Rename/ObjCCompatibilityAlias.m b/clang/test/Refactor/Rename/ObjCCompatibilityAlias.m new file mode 100644 index 0000000000000..da98da63e8874 --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCCompatibilityAlias.m @@ -0,0 +1,44 @@ +@class I1, // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + I2; // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:10 + +@interface I1 // CHECK1: rename [[@LINE]]:12 -> [[@LINE]]:14 +@end + +@compatibility_alias I1Alias I1; // CHECK3: rename [[@LINE]]:22 -> [[@LINE]]:29 + // CHECK1: rename [[@LINE-1]]:30 -> [[@LINE-1]]:32 + +@compatibility_alias I2Alias I2; // CHECK4: rename [[@LINE]]:22 -> [[@LINE]]:29 + // CHECK2: rename [[@LINE-1]]:30 -> [[@LINE-1]]:32 + +// RUN: clang-refactor-test rename-initiate -at=%s:7:30 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:10:30 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:7:22 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:10:22 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s + +// TODO: Implement TypeLocs for @compatibility_alias (rdar://29245831) +// XFAIL: * +void foo(I1Alias *object) { // CHECK3: rename [[@LINE]]:10 -> [[@LINE]]:17 +} + +@implementation I1 { // CHECK1: rename [[@LINE]]:17 -> [[@LINE]]:19 + I1Alias *object; // CHECK3: rename [[@LINE]]:3 -> [[@LINE]]:10 +} + +-(const I1Alias *)foo:(I2Alias *)object { // CHECK3: rename [[@LINE]]:9 -> [[@LINE]]:16 + // CHECK4: rename [[@LINE-1]]:24 -> [[@LINE-1]]:31 + return (const I1Alias *)self->object; // CHECK3: rename [[@LINE]]:17 -> [[@LINE]]:24 +} + +@end + +@interface I3: I1Alias // CHECK3: rename [[@LINE]]:16 -> [[@LINE]]:23 +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:21:10 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:25:3 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:28:9 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:30:17 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:35:16 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:28:24 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s diff --git a/clang/test/Refactor/Rename/ObjCImplementationTURequests.m b/clang/test/Refactor/Rename/ObjCImplementationTURequests.m new file mode 100644 index 0000000000000..9486a8a5824e9 --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCImplementationTURequests.m @@ -0,0 +1,32 @@ +@interface ExplicitIVarsInInterface { + int _requiresImplementationTU; +} + +@property int requiresImplementationTU; + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:2:7 -new-name=foo -implementation-tu="%S/Inputs/ObjCImplementationTURequestsImplementation.m" -dump-symbols %s | FileCheck --check-prefix=CHECK1 %s +// CHECK1: Implementation TU USR: 'c:objc(cs)ExplicitIVarsInInterface@_requiresImplementationTU' + +// RUN: not clang-refactor-test rename-initiate -at=%s:2:7 -new-name=foo -implementation-tu="%S/MissingFile.m" -dump-symbols %s 2>&1 | FileCheck --check-prefix=CHECK-ERR1 %s +// CHECK-ERR1: failed to load implementation TU + +@interface NoNeedForImplementationTUs { + int _p1; +} + +@property int p1; +@property int p2; + +@end + +@implementation NoNeedForImplementationTUs { + int _p2; +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:16:7 -new-name=foo %s | FileCheck --check-prefix=CHECK-NO %s +// RUN: clang-refactor-test rename-initiate -at=%s:25:7 -new-name=foo %s | FileCheck --check-prefix=CHECK-NO %s +// CHECK-NO-NOT: Implementation TU USR diff --git a/clang/test/Refactor/Rename/ObjCImplicitProperty.m b/clang/test/Refactor/Rename/ObjCImplicitProperty.m new file mode 100644 index 0000000000000..b587f41cdf23e --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCImplicitProperty.m @@ -0,0 +1,31 @@ +@interface I1 + +-(int)p1; // CHECK1: rename [[@LINE]]:7 -> [[@LINE]]:9 +-(void)setP1:(int)x; // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:13 +-(int)p2; // CHECK3: rename [[@LINE]]:7 -> [[@LINE]]:9 + +@end + +@implementation I1 + +- (int)p1 { // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + return 0; +} + +- (void)foo: (I1 *)other { + self.p1 = // CHECK1: implicit-property [[@LINE]]:8 -> [[@LINE]]:10 + // CHECK2: implicit-property [[@LINE-1]]:8 -> [[@LINE-1]]:13 + self.p2; // CHECK3: implicit-property [[@LINE]]:19 -> [[@LINE]]:21 + (void)other.p1; // CHECK1: implicit-property [[@LINE]]:15 -> [[@LINE]]:17 + // CHECK2: implicit-property [[@LINE-1]]:15 -> [[@LINE-1]]:20 + + int x = [self p1]; // CHECK1: rename [[@LINE]]:17 -> [[@LINE]]:19 + [self setP1: x]; // CHECK2: rename [[@LINE]]:9 -> [[@LINE]]:14 + x = [other p2]; // CHECK3: rename [[@LINE]]:14 -> [[@LINE]]:16 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:3:7 -at=%s:19:15 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:4:8 -at=%s:16:8 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:5:7 -at=%s:18:19 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s diff --git a/clang/test/Refactor/Rename/ObjCMethod.m b/clang/test/Refactor/Rename/ObjCMethod.m new file mode 100644 index 0000000000000..7f3655d8979e3 --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCMethod.m @@ -0,0 +1,135 @@ +@interface Test + +- (void)foo; // CHECK1: rename [[@LINE]]:9 -> [[@LINE]]:12 +- (int)performAction:(int)action with:(int)value; // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:21, [[@LINE]]:34 -> [[@LINE]]:38 + +@end + +@implementation Test + +- (void)foo { // CHECK1: rename [[@LINE]]:9 -> [[@LINE]]:12 +} + +- (int)performAction:(int)action with:(int)value { // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:21, [[@LINE]]:34 -> [[@LINE]]:38 + return action + value; +} + ++ (void)foo:(Test*)t { // CHECK1-NOT: rename [[@LINE]] + [t foo]; // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:11 + SEL s = @selector(foo); + [Test foo:t]; // CHECK1-NOT: rename [[@LINE]] + [t performAction: 2 with: 4]; // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:21, [[@LINE]]:25 -> [[@LINE]]:29 + SEL s1 = @selector(foo:); + SEL s2 = @selector(performAction: + with:); + SEL s3 = @selector(performAction:); + SEL s4 = @selector(performAction); +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:3:9 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:10:9 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:18:8 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:4:8 -new-name=doSomething:to %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:13:8 -new-name=doSomething:to: %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:21:8 -new-name=doSomething:to: %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK2 %s + + + + +@interface SuperClass + +- (void)foo; // CHECK-OVERRIDEFOO: rename [[@LINE]]:9 -> [[@LINE]]:12 +- (int)compareTo:(SuperClass *)other with:(int)options; // CHECK-OVERRIDECOMP: rename [[@LINE]]:8 -> [[@LINE]]:17, [[@LINE]]:38 -> [[@LINE]]:42 + +@end + +@implementation SuperClass + +- (void)foo { // CHECK-OVERRIDEFOO: rename [[@LINE]]:9 -> [[@LINE]]:12 + return; +} + +- (int)compareTo:(SuperClass *)other with:(int)options { // CHECK-OVERRIDECOMP: rename [[@LINE]]:8 -> [[@LINE]]:17, [[@LINE]]:38 -> [[@LINE]]:42 + return 0; +} + +@end + +@interface SubClass : SuperClass + +- (void)foo; // CHECK-OVERRIDEFOO: rename [[@LINE]]:9 -> [[@LINE]]:12 + +@end + +@implementation SubClass + +- (void)foo { // CHECK-OVERRIDEFOO: rename [[@LINE]]:9 -> [[@LINE]]:12 + [super foo]; // CHECK-OVERRIDEFOO: rename [[@LINE]]:10 -> [[@LINE]]:13 +} + +@end + +@interface SubClassTheSecond : SubClass + +- (void)foo; // CHECK-OVERRIDEFOO: rename [[@LINE]]:9 -> [[@LINE]]:12 +- (int)compareTo:(SuperClass *)other with:(int)options; // CHECK-OVERRIDECOMP: rename [[@LINE]]:8 -> [[@LINE]]:17, [[@LINE]]:38 -> [[@LINE]]:42 + +@end + +@implementation SubClassTheSecond + +- (void)foo { // CHECK-OVERRIDEFOO: rename [[@LINE]]:9 -> [[@LINE]]:12 + return; +} +- (int)compareTo:(SuperClass *)other // CHECK-OVERRIDECOMP: rename [[@LINE]]:8 -> [[@LINE]]:17, [[@LINE+1]]:8 -> [[@LINE+1]]:12 + with:(int)options { + [other foo]; // CHECK-OVERRIDEFOO: rename [[@LINE]]:10 -> [[@LINE]]:13 + return [super compareTo: other with: options]; // CHECK-OVERRIDECOMP: rename [[@LINE]]:17 -> [[@LINE]]:26, [[@LINE]]:34 -> [[@LINE]]:38 +} + +@end + +@interface UnrelatedClass + +- (void)foo; // CHECK-OVERRIDEFOO-NOT: rename [[@LINE]] + +@end + +@interface UnrelatedSubClass : UnrelatedClass +// This method doesn't override SuperClass.foo, so verify that this occurrence +// isn't renamed even though its selector is the same. +- (void)foo; // CHECK-OVERRIDEFOO-NOT: rename [[@LINE]] + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:44:9 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDEFOO %s +// RUN: clang-refactor-test rename-initiate -at=%s:51:9 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDEFOO %s +// RUN: clang-refactor-test rename-initiate -at=%s:63:9 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDEFOO %s +// RUN: clang-refactor-test rename-initiate -at=%s:69:9 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDEFOO %s +// RUN: clang-refactor-test rename-initiate -at=%s:70:10 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDEFOO %s +// RUN: clang-refactor-test rename-initiate -at=%s:77:9 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDEFOO %s +// RUN: clang-refactor-test rename-initiate -at=%s:84:9 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDEFOO %s +// RUN: clang-refactor-test rename-initiate -at=%s:89:10 -new-name=bar %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDEFOO %s + +// RUN: clang-refactor-test rename-initiate -at=%s:45:8 -new-name=a:b: %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDECOMP %s +// RUN: clang-refactor-test rename-initiate -at=%s:55:8 -new-name=a:b: %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDECOMP %s +// RUN: clang-refactor-test rename-initiate -at=%s:78:8 -new-name=a:b %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDECOMP %s +// RUN: clang-refactor-test rename-initiate -at=%s:87:9 -new-name=a:b: %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDECOMP %s +// RUN: clang-refactor-test rename-initiate -at=%s:90:17 -new-name=a:b %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDECOMP %s +// RUN: clang-refactor-test rename-initiate -at=%s:90:34 -new-name=a:b %s -Wno-objc-root-class | FileCheck --check-prefix=CHECK-OVERRIDECOMP %s + +// Don't allow implicit parameters: +@interface Foo +- (void)foo; +@end + +@implementation Foo +- (void)foo { + self = 0; +} +@end +// RUN: not clang-refactor-test rename-initiate -at=%s:130:3 -new-name=foo %s -Wno-objc-root-class 2>&1 | FileCheck --check-prefix=CHECK-NORENAME %s +// CHECK-NORENAME: could not rename symbol at the given location diff --git a/clang/test/Refactor/Rename/ObjCMethodMacro.m b/clang/test/Refactor/Rename/ObjCMethodMacro.m new file mode 100644 index 0000000000000..c6d6322a9e99c --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCMethodMacro.m @@ -0,0 +1,28 @@ +#define FOO foo // CHECK1-NOT: rename [[@LINE]] +// CHECK1-NOT: macro [[@LINE-1]] + +@interface I + +- (void)FOO; // CHECK1: macro [[@LINE]]:9 -> [[@LINE]]:9 +- (void)foo: (int)x FOO: (int)y; // CHECK2: macro [[@LINE]]:21 -> [[@LINE]]:21 + +@end + +@implementation I + +- (void)foo { // CHECK1-NEXT: rename [[@LINE]]:9 -> [[@LINE]]:12 + [self FOO: 1 FOO: 2]; // CHECK2-NEXT: macro [[@LINE]]:9 -> [[@LINE]]:9 +} + +- (void)foo: (int)x foo: (int)y { // CHECK2-NEXT: rename [[@LINE]]:9 -> [[@LINE]]:12, [[@LINE]]:21 -> [[@LINE]]:24 + [self FOO]; // CHECK1-NEXT: macro [[@LINE]]:9 -> [[@LINE]]:9 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:13:9 -new-name=bar %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:17:9 -new-name=foo:bar %s | FileCheck --check-prefix=CHECK2 %s + +// RUN: not clang-refactor-test rename-initiate -at=%s:1:13 -new-name=foo %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s +// RUN: not clang-refactor-test rename-initiate -at=%s:6:9 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s +// CHECK-ERROR: could not rename symbol at the given location diff --git a/clang/test/Refactor/Rename/ObjCProperty.m b/clang/test/Refactor/Rename/ObjCProperty.m new file mode 100644 index 0000000000000..f0ed85233b74d --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCProperty.m @@ -0,0 +1,303 @@ +// XFAIL: * +// TODO: Remove or cut it down to one symbol rename. + +@interface I1 + +@property int p1; // CHECK1: rename [[@LINE]]:15 -> [[@LINE]]:17 +@property (readwrite, nonatomic) int p2; // CHECK2: rename [[@LINE]]:38 -> [[@LINE]]:40 + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:3:15 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:4:38 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +@implementation I1 + +- (void)foo:(I1 *)other { + self.p2 = // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:10 + self.p1; // CHECK1: rename [[@LINE]]:18 -> [[@LINE]]:20 + (void)other.p1; // CHECK1: rename [[@LINE]]:15 -> [[@LINE]]:17 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:14:8 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:15:18 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:16:15 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +@implementation I1 (gettersAndSetters) + +- (void)foo2:(I1 *)other { + int x = [self p1]; // CHECK1: rename [[@LINE]]:17 -> [[@LINE]]:19 + [self setP1: x]; // CHECK1: rename "setFoo" [[@LINE]]:9 -> [[@LINE]]:14 + [other setP2: // CHECK2: rename "setFoo" [[@LINE]]:10 -> [[@LINE]]:15 + [other p2]]; // CHECK2: rename [[@LINE]]:12 -> [[@LINE]]:14 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:28:17 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:29:9 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:30:10 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:31:12 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +@interface I2 + +@property int noImplementation; // CHECK3: rename [[@LINE]]:15 -> [[@LINE]]:31 + +@end + +void noImplementationGetterSetters(I2 *object) { + object.noImplementation = 2; // CHECK3: rename [[@LINE]]:10 -> [[@LINE]]:26 + int x = object.noImplementation; // CHECK3: rename [[@LINE]]:18 -> [[@LINE]]:34 + [object setNoImplementation: x]; // CHECK3: rename "setFoo" [[@LINE]]:11 -> [[@LINE]]:30 + (void)[object noImplementation]; // CHECK3: rename [[@LINE]]:17 -> [[@LINE]]:33 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:43:15 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:48:10 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:49:18 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:50:11 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:51:17 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s + +@interface I3 + +@property (readonly) int noSetter; // CHECK4: rename [[@LINE]]:26 -> [[@LINE]]:34 + +@end + +void noPropertySetter(I3 *object) { + (void)object.noSetter; // CHECK4: rename [[@LINE]]:16 -> [[@LINE]]:24 + object.noSetter = 2; // CHECK4-NOT: rename [[@LINE]] + (void)[object noSetter]; // CHECK4: rename [[@LINE]]:17 -> [[@LINE]]:25 + [object setNoSetter: 2]; // CHECK4-NOT: rename "setFoo" [[@LINE]] +} + +// RUN: clang-refactor-test rename-initiate -at=%s:62:26 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:67:16 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:69:17 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s + +@interface PropertyOverrides1: I1 + +- (int)p1; // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 +- (void)setP1:(int)x; // CHECK1: rename "setFoo" [[@LINE]]:9 -> [[@LINE]]:14 +- (int)p2; // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:10 + +@end + +@implementation PropertyOverrides1 { + I1 *object; +} + +- (int)p1 { // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + return [super p1]; // CHECK1: rename [[@LINE]]:17 -> [[@LINE]]:19 +} +- (void)setP1:(int)x { // CHECK1: rename "setFoo" [[@LINE]]:9 -> [[@LINE]]:14 + [object setP1: x]; // CHECK1: rename "setFoo" [[@LINE]]:11 -> [[@LINE]]:16 + [super setP1: x]; // CHECK1: rename "setFoo" [[@LINE]]:10 -> [[@LINE]]:15 +} +- (int)p2 { // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:10 + return super.p2; // CHECK2: rename [[@LINE]]:16 -> [[@LINE]]:18 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:79:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:80:9 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:89:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:90:17 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:92:9 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:93:11 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:94:10 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:81:8 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:96:8 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:97:16 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +@interface PropertyOverrides2: I3 + +- (int)noSetter; // CHECK4: rename [[@LINE]]:8 -> [[@LINE]]:16 +- (void)setNoSetter:(int)x; // CHECK4-NOT: rename "setFoo" [[@LINE]] + +@end + +void getterOnlyOverrideWithoutImplementation(PropertyOverrides2 *object) { + (void)object.noSetter; // CHECK4: rename [[@LINE]]:16 -> [[@LINE]]:24 + int _ = [object noSetter]; // CHECK4: rename [[@LINE]]:19 -> [[@LINE]]:27 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:115:8 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:121:16 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:122:19 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s + +@interface MismatchedPropertyOverrides: I1 + +- (void)p1; // CHECK1: rename [[@LINE]]:9 -> [[@LINE]]:11 + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:131:9 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +@interface ExplicitlyNamedGetterSetters1 + +@property (getter=getP3) int p3; // CHECK5: rename [[@LINE]]:30 -> [[@LINE]]:32 + // CHECK5GET: rename [[@LINE-1]]:19 -> [[@LINE-1]]:24 +@property (getter=a, setter=b:) int p4; // CHECK6: rename [[@LINE]]:37 -> [[@LINE]]:39 + // CHECK6GET: rename [[@LINE-1]]:19 -> [[@LINE-1]]:20 + // CHECK6SET: rename [[@LINE-2]]:29 -> [[@LINE-2]]:30 +@property (readonly, getter=local) bool isLocal; // CHECK7: rename [[@LINE]]:41 -> [[@LINE]]:48 + // CHECK7GET: rename [[@LINE-1]]:29 -> [[@LINE-1]]:34 +@end + +@implementation ExplicitlyNamedGetterSetters1 + +- (void)foo:(ExplicitlyNamedGetterSetters *)other { + self.p3 = 2; // CHECK5: rename [[@LINE]]:8 -> [[@LINE]]:10 + [self p3]; // CHECK5-NOT: rename [[@LINE]] + [self setP3: // CHECK5: rename "setFoo" [[@LINE]]:9 -> [[@LINE]]:14 + [other getP3]]; // CHECK5-NOT: rename [[@LINE]] + // CHECK5GET: rename [[@LINE-1]]:12 -> [[@LINE-1]]:17 + + self.p4 = 3; // CHECK6: rename [[@LINE]]:8 -> [[@LINE]]:10 + [self p4]; // CHECK6-NOT: rename [[@LINE]] + [self setP4: 2]; // CHECK6-NOT: rename "setFoo" [[@LINE]] + [self b: // CHECK6-NOT: rename "setFoo" [[@LINE]] + // CHECK6SET: rename [[@LINE-1]]:9 -> [[@LINE-1]]:10 + [other a]]; // CHECK6-NOT: rename [[@LINE]] + // CHECK6GET: rename [[@LINE-1]]:12 -> [[@LINE-1]]:13 + + (void)self.isLocal; // CHECK7: rename [[@LINE]]:14 -> [[@LINE]]:21 + [self isLocal]; // CHECK7-NOT: rename [[@LINE]] + [other local]; // CHECK7-NOT: rename [[@LINE]] + // CHECK7GET: rename [[@LINE-1]]:10 -> [[@LINE-1]]:15 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:139:30 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test rename-initiate -at=%s:151:8 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test rename-initiate -at=%s:153:9 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:141:37 -new-name=foo %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test rename-initiate -at=%s:157:8 -new-name=foo %s | FileCheck --check-prefix=CHECK6 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:144:41 -new-name=foo %s | FileCheck --check-prefix=CHECK7 %s +// RUN: clang-refactor-test rename-initiate -at=%s:165:14 -new-name=foo %s | FileCheck --check-prefix=CHECK7 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:139:19 -new-name=foo %s | FileCheck --check-prefix=CHECK5GET %s +// RUN: clang-refactor-test rename-initiate -at=%s:154:12 -new-name=foo %s | FileCheck --check-prefix=CHECK5GET %s + +// RUN: clang-refactor-test rename-initiate -at=%s:141:19 -new-name=foo %s | FileCheck --check-prefix=CHECK6GET %s +// RUN: clang-refactor-test rename-initiate -at=%s:162:12 -new-name=foo %s | FileCheck --check-prefix=CHECK6GET %s +// RUN: clang-refactor-test rename-initiate -at=%s:141:29 -new-name=foo %s | FileCheck --check-prefix=CHECK6SET %s +// RUN: clang-refactor-test rename-initiate -at=%s:160:9 -new-name=foo %s | FileCheck --check-prefix=CHECK6SET %s + +// RUN: clang-refactor-test rename-initiate -at=%s:144:29 -new-name=foo %s | FileCheck --check-prefix=CHECK7GET %s +// RUN: clang-refactor-test rename-initiate -at=%s:167:10 -new-name=foo %s | FileCheck --check-prefix=CHECK7GET %s + +void ivars1(I1 *object) { + object->_p1 = 2; // CHECK1: rename "_foo" [[@LINE]]:11 -> [[@LINE]]:14 + object->_p2 = // CHECK2: rename "_foo" [[@LINE]]:11 -> [[@LINE]]:14 + object->_p1; // CHECK1: rename "_foo" [[@LINE]]:25 -> [[@LINE]]:28 +} + +void ivars2(ExplicitlyNamedGetterSetters1 *object) { + object->_p3 = // CHECK5: rename "_foo" [[@LINE]]:11 -> [[@LINE]]:14 + object->_p4; // CHECK6: rename "_foo" [[@LINE]]:25 -> [[@LINE]]:28 + object->_isLocal = 0; // CHECK7: rename "_foo" [[@LINE]]:11 -> [[@LINE]]:19 +} + +void ivarsNoImplementation(I2 *object) { + object->_noImplementation = 4; // CHECK2-NOT: rename "_foo" [[@LINE]] +} + +// RUN: clang-refactor-test rename-initiate -at=%s:195:11 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:197:25 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:196:11 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:201:11 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test rename-initiate -at=%s:202:25 -new-name=foo %s | FileCheck --check-prefix=CHECK6 %s +// RUN: clang-refactor-test rename-initiate -at=%s:203:11 -new-name=foo %s | FileCheck --check-prefix=CHECK7 %s + +@interface ExplicitIVars + +@property int p5; // CHECK8: rename [[@LINE]]:15 -> [[@LINE]]:17 +@property(readonly) int p6; // CHECK9: rename [[@LINE]]:25 -> [[@LINE]]:27 + +@end + +@implementation ExplicitIVars { + int _p5; // CHECK8: rename "_foo" [[@LINE]]:7 -> [[@LINE]]:10 + int _p6; // CHECK9: rename "_foo" [[@LINE]]:7 -> [[@LINE]]:10 +} + +- (void)foo:(ExplicitIVars *)other { + _p5 = // CHECK8: rename "_foo" [[@LINE]]:3 -> [[@LINE]]:6 + other->_p6; // CHECK9: rename "_foo" [[@LINE]]:16 -> [[@LINE]]:19 + other->_p6 = // CHECK9: rename "_foo" [[@LINE]]:10 -> [[@LINE]]:13 + _p5; // CHECK8: rename "_foo" [[@LINE]]:16 -> [[@LINE]]:19 + self.p5 = // CHECK8: rename [[@LINE]]:8 -> [[@LINE]]:10 + other.p6; // CHECK9: rename [[@LINE]]:18 -> [[@LINE]]:20 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:220:15 -new-name=foo %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test rename-initiate -at=%s:226:7 -new-name=foo %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test rename-initiate -at=%s:231:3 -new-name=foo %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test rename-initiate -at=%s:234:16 -new-name=foo %s | FileCheck --check-prefix=CHECK8 %s +// RUN: clang-refactor-test rename-initiate -at=%s:235:8 -new-name=foo %s | FileCheck --check-prefix=CHECK8 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:221:25 -new-name=foo %s | FileCheck --check-prefix=CHECK9 %s +// RUN: clang-refactor-test rename-initiate -at=%s:227:7 -new-name=foo %s | FileCheck --check-prefix=CHECK9 %s +// RUN: clang-refactor-test rename-initiate -at=%s:232:16 -new-name=foo %s | FileCheck --check-prefix=CHECK9 %s +// RUN: clang-refactor-test rename-initiate -at=%s:233:10 -new-name=foo %s | FileCheck --check-prefix=CHECK9 %s +// RUN: clang-refactor-test rename-initiate -at=%s:236:18 -new-name=foo %s | FileCheck --check-prefix=CHECK9 %s + +@interface ExplicitIVarsInInterface { + int _p7; // CHECK10: rename "_foo" [[@LINE]]:7 -> [[@LINE]]:10 + @public + int _p8; // CHECK11: rename "_foo" [[@LINE]]:7 -> [[@LINE]]:10 +} + +@property int p7; // CHECK10: rename [[@LINE]]:15 -> [[@LINE]]:17 +@property int p8; // CHECK11: rename [[@LINE]]:15 -> [[@LINE]]:17 + +@end + +@implementation ExplicitIVarsInInterface +@end + +void explicitIVarsInInterface(ExplicitIVarsInInterface* object) { + object->_p7 = // CHECK10: rename "_foo" [[@LINE]]:11 -> [[@LINE]]:14 + object->_p8; // CHECK11: rename "_foo" [[@LINE]]:25 -> [[@LINE]]:28 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:254:7 -new-name=foo %s | FileCheck --check-prefix=CHECK10 %s +// RUN: clang-refactor-test rename-initiate -at=%s:259:15 -new-name=foo %s | FileCheck --check-prefix=CHECK10 %s +// RUN: clang-refactor-test rename-initiate -at=%s:268:11 -new-name=foo %s | FileCheck --check-prefix=CHECK10 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:256:7 -new-name=foo %s | FileCheck --check-prefix=CHECK11 %s +// RUN: clang-refactor-test rename-initiate -at=%s:260:15 -new-name=foo %s | FileCheck --check-prefix=CHECK11 %s +// RUN: clang-refactor-test rename-initiate -at=%s:269:25 -new-name=foo %s | FileCheck --check-prefix=CHECK11 %s + +@interface GetterSetterDefinedInInterfaceOnly + +@property int p9; // CHECK12: rename [[@LINE]]:15 -> [[@LINE]]:17 + +@end + +@implementation GetterSetterDefinedInInterfaceOnly + +- (int)p9 { return 0; } // CHECK12: rename [[@LINE]]:8 -> [[@LINE]]:10 +- (void)setP9:(int)x { } // CHECK12: rename "setFoo" [[@LINE]]:9 -> [[@LINE]]:14 + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:288:8 -new-name=foo %s | FileCheck --check-prefix=CHECK12 %s +// RUN: clang-refactor-test rename-initiate -at=%s:289:9 -new-name=foo %s | FileCheck --check-prefix=CHECK12 %s + +void matchingGetterSetterSelector() { + @selector(p1); // CHECK1: selector [[@LINE]]:13 -> [[@LINE]]:15 + @selector(setP1:); // CHECK1: selector "setFoo" [[@LINE]]:13 -> [[@LINE]]:18 + @selector(setP1); // CHECK1-NOT: selector "setFoo" [[@LINE]] +} diff --git a/clang/test/Refactor/Rename/ObjCPropertyDynamic.m b/clang/test/Refactor/Rename/ObjCPropertyDynamic.m new file mode 100644 index 0000000000000..8ac7aac2d6b48 --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCPropertyDynamic.m @@ -0,0 +1,41 @@ +@interface DynamicProperty + +@property int p1; // CHECK1: rename [[@LINE]]:15 -> [[@LINE]]:17 +@property(readonly) int p2; // CHECK2: rename [[@LINE]]:25 -> [[@LINE]]:27 + +@end + +@implementation DynamicProperty + +@dynamic p1; // CHECK1: rename [[@LINE]]:10 -> [[@LINE]]:12 + +@dynamic p2; // CHECK2: rename [[@LINE]]:10 -> [[@LINE]]:12 + +- (int)p1 { // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + return 1; +} +// TODO: Remove +- (void)setP1:(int)x { // CHECK1: rename [[@LINE]]:9 -> [[@LINE]]:14 +} + +- (int)p2 { // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:10 + return 2; +} + +- (void)foo:(DynamicProperty *)other { + self.p1 = // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + other.p2; // CHECK2: rename [[@LINE]]:19 -> [[@LINE]]:21 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:3:15 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:10:10 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:14:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:18:9 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:26:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:4:25 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:12:10 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:21:8 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:27:19 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Rename/ObjCPropertyIVarInInterfaceWithoutImplementation.m b/clang/test/Refactor/Rename/ObjCPropertyIVarInInterfaceWithoutImplementation.m new file mode 100644 index 0000000000000..fda9e824a7e0a --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCPropertyIVarInInterfaceWithoutImplementation.m @@ -0,0 +1,28 @@ +@interface ExplicitIVarsInInterface { + int _p1; // CHECK1: rename "_foo" [[@LINE]]:7 -> [[@LINE]]:10 + @public + int _p2; // CHECK2: rename "_foo" [[@LINE]]:7 -> [[@LINE]]:10 +} + +@property int p1; // CHECK1: rename [[@LINE]]:15 -> [[@LINE]]:17 +@property int p2; // CHECK2: rename [[@LINE]]:15 -> [[@LINE]]:17 + +@end + +void explicitIVarsInInterface(ExplicitIVarsInInterface* object) { + object->_p7 = // CHECK1: rename "_foo" [[@LINE]]:11 -> [[@LINE]]:14 + object->_p8; // CHECK2: rename "_foo" [[@LINE]]:25 -> [[@LINE]]:28 +} + +// XFAIL: * +// This test is currently disabled as renaming can't initiate a property +// renaming operation in a TU without @implementation. +// rdar://29329980 + +// RUN: clang-refactor-test rename-initiate -at=%s:2:7 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:7:15 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:13:11 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:4:7 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:8:15 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:14:25 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Rename/ObjCPropertyInCategory.m b/clang/test/Refactor/Rename/ObjCPropertyInCategory.m new file mode 100644 index 0000000000000..9c8bc79c17dae --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCPropertyInCategory.m @@ -0,0 +1,45 @@ +@interface I1 + +@end + +@interface I1 (Category) + +@property int p1; // CHECK1: rename [[@LINE]]:15 -> [[@LINE]]:17 +@property(readonly) int p2; // CHECK2: rename [[@LINE]]:25 -> [[@LINE]]:27 + +- (int)p1; // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + +@end + +@implementation I1 (Category) + +@dynamic p2; // CHECK2: rename [[@LINE]]:10 -> [[@LINE]]:12 + +- (int)p1 { // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + return 1; +} +// TODO: Remove +- (void)setP1:(int)x { // CHECK1: rename [[@LINE]]:9 -> [[@LINE]]:14 +} + +- (int)p2 { // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:10 + return 2; +} + +- (void)foo:(I1 *)other { + self.p1 = // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + other.p2; // CHECK2: rename [[@LINE]]:19 -> [[@LINE]]:21 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:7:15 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:10:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:18:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:22:9 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:30:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:8:25 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:16:10 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:25:8 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:31:19 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/test/Refactor/Rename/ObjCPropertyMacro.m b/clang/test/Refactor/Rename/ObjCPropertyMacro.m new file mode 100644 index 0000000000000..9d3ffa314cbcd --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCPropertyMacro.m @@ -0,0 +1,21 @@ +#define IMPLICIT implicit +#define SETIMPLICIT setImplicit + +@interface I + +- (int)IMPLICIT; +- (void)setImplicit:(int)x; // CHECK1: rename [[@LINE]] + +@end + +@implementation I + +- (void)foo { + self.implicit; // CHECK1-NEXT: implicit-property [[@LINE]] + self.IMPLICIT; // CHECK1-NEXT: implicit-property in macro [[@LINE]] + self.IMPLICIT = 2; // CHECK1-NEXT: implicit-property in macro [[@LINE]] +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:7:9 -new-name=bar %s | FileCheck --check-prefix=CHECK1 %s diff --git a/clang/test/Refactor/Rename/ObjCPropertySynthesize.m b/clang/test/Refactor/Rename/ObjCPropertySynthesize.m new file mode 100644 index 0000000000000..2b12b6fe76b8b --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCPropertySynthesize.m @@ -0,0 +1,112 @@ +// XFAIL: * +// TODO: Remove or cut it down to one symbol rename. + +@interface SynthesizedIVars + +@property int p1; // CHECK1: rename [[@LINE]]:15 -> [[@LINE]]:17 +@property(readonly) int p2; // CHECK2PROP: rename [[@LINE]]:25 -> [[@LINE]]:27 + +@end + +@implementation SynthesizedIVars + +@synthesize p1 = _p1; // CHECK1: rename [[@LINE]]:13 -> [[@LINE]]:15 + // CHECK1: rename "_foo" [[@LINE-1]]:18 -> [[@LINE-1]]:21 + +// The rename of ivar 'p2_' shouldn't initiate the rename of property 'p2' +// because it doesn't follow the default naming convention. +@synthesize p2 = p2_; // CHECK2PROP: rename [[@LINE]]:13 -> [[@LINE]]:15 + // CHECK2IVAR: rename [[@LINE-1]]:18 -> [[@LINE-1]]:21 + +- (void)foo:(SynthesizedIVars *)other { + _p1 = // CHECK1: rename "_foo" [[@LINE]]:3 -> [[@LINE]]:6 + other->p2_; // CHECK2IVAR: rename [[@LINE]]:16 -> [[@LINE]]:19 + other->p2_ = // CHECK2IVAR: rename [[@LINE]]:10 -> [[@LINE]]:13 + _p1; // CHECK1: rename "_foo" [[@LINE]]:16 -> [[@LINE]]:19 + self.p1 = // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + other.p2; // CHECK2PROP: rename [[@LINE]]:18 -> [[@LINE]]:20 +} + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:3:15 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:10:13 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:10:18 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:19:3 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:22:16 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:23:8 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:4:25 -new-name=foo %s | FileCheck --check-prefix=CHECK2PROP %s +// RUN: clang-refactor-test rename-initiate -at=%s:15:13 -new-name=foo %s | FileCheck --check-prefix=CHECK2PROP %s +// RUN: clang-refactor-test rename-initiate -at=%s:24:18 -new-name=foo %s | FileCheck --check-prefix=CHECK2PROP %s + +// RUN: clang-refactor-test rename-initiate -at=%s:15:18 -new-name=foo %s | FileCheck --check-prefix=CHECK2IVAR %s +// RUN: clang-refactor-test rename-initiate -at=%s:20:16 -new-name=foo %s | FileCheck --check-prefix=CHECK2IVAR %s +// RUN: clang-refactor-test rename-initiate -at=%s:21:10 -new-name=foo %s | FileCheck --check-prefix=CHECK2IVAR %s + +@interface SynthesizedExplicitIVars { + int _p3; // CHECK3: rename "_foo" [[@LINE]]:7 -> [[@LINE]]:10 +} + +@property int p3; // CHECK3: rename [[@LINE]]:15 -> [[@LINE]]:17 +@property int p4; // CHECK4: rename [[@LINE]]:15 -> [[@LINE]]:17 + +@end + +@implementation SynthesizedExplicitIVars { + int _p4; // CHECK4: rename "_foo" [[@LINE]]:7 -> [[@LINE]]:10 +} + +@synthesize p3 = _p3; // CHECK3: rename [[@LINE]]:13 -> [[@LINE]]:15 + // CHECK3: rename "_foo" [[@LINE-1]]:18 -> [[@LINE-1]]:21 +@synthesize p4 = _p4; // CHECK4: rename [[@LINE]]:13 -> [[@LINE]]:15 + // CHECK4: rename "_foo" [[@LINE-1]]:18 -> [[@LINE-1]]:21 + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:45:7 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:48:15 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:57:13 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:57:18 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:49:15 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:54:7 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:59:13 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:59:18 -new-name=foo %s | FileCheck --check-prefix=CHECK4 %s + +@interface SynthesizedWithoutIVarName + +@property int p5; // CHECK5: rename [[@LINE]]:15 -> [[@LINE]]:17 + +@end + +@implementation SynthesizedWithoutIVarName { + int _p5; // CHECK5-NOT: rename "" [[@LINE]] + // CHECK5-NOT: rename [[@LINE-1]] + int p5; // CHECK5: rename [[@LINE]]:7 -> [[@LINE]]:9 +} + +@synthesize p5; // CHECK5: rename [[@LINE]]:13 -> [[@LINE]]:15 + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:76:15 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test rename-initiate -at=%s:83:7 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test rename-initiate -at=%s:86:13 -new-name=foo %s | FileCheck --check-prefix=CHECK5 %s + +@interface A +@property int p6; +@end + +@interface C : A +@end + +@implementation C + +@synthesize p6; +// CHECK6: Renaming 4 symbols +// CHECK6-NEXT: 'c:objc(cs)A(py)p6' + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:103:13 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK6 %s diff --git a/clang/test/Refactor/Rename/ObjCProtocol.m b/clang/test/Refactor/Rename/ObjCProtocol.m new file mode 100644 index 0000000000000..661d83b7b02b8 --- /dev/null +++ b/clang/test/Refactor/Rename/ObjCProtocol.m @@ -0,0 +1,67 @@ +@protocol P1, // CHECK1: rename [[@LINE]]:11 -> [[@LINE]]:13 + P2; // CHECK2: rename [[@LINE]]:11 -> [[@LINE]]:13 + +@protocol P1 // CHECK1: rename [[@LINE]]:11 -> [[@LINE]]:13 +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:1:11 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:4:11 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:2:11 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +void protocolExpressions(id foo) { + (void)@protocol(P1); // CHECK1: rename [[@LINE]]:19 -> [[@LINE]]:21 + [foo p: @protocol(P2)]; // CHECK2: rename [[@LINE]]:21 -> [[@LINE]]:23 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:12:19 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:13:21 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +void qualifiedId(id foo) { // CHECK1: rename [[@LINE]]:21 -> [[@LINE]]:23 + id bar = // CHECK2: rename [[@LINE]]:6 -> [[@LINE]]:8 + // CHECK1: rename [[@LINE-1]]:10 -> [[@LINE-1]]:12 + (id)foo; // CHECK1: rename [[@LINE]]:24 -> [[@LINE]]:26 + // CHECK2: rename [[@LINE-1]]:28 -> [[@LINE-1]]:30 + // CHECK1: rename [[@LINE-2]]:32 -> [[@LINE-2]]:34 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:19:21 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:20:6 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:20:10 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:22:24 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:22:28 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:22:32 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +typedef id TypedefQualifiedID; // CHECK1: rename [[@LINE]]:12 -> [[@LINE]]:14 + +// RUN: clang-refactor-test rename-initiate -at=%s:34:12 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s + +@interface I1 // CHECK1: rename [[@LINE]]:15 -> [[@LINE]]:17 +@end + +@protocol P3 < P1> // CHECK3: rename [[@LINE]]:11 -> [[@LINE]]:13 + // CHECK1: rename [[@LINE-1]]:16 -> [[@LINE-1]]:18 +@end + +@interface I1 (Cat) // CHECK2: rename [[@LINE]]:22 -> [[@LINE]]:24 +@end // CHECK3: rename [[@LINE-1]]:26 -> [[@LINE-1]]:28 + +// RUN: clang-refactor-test rename-initiate -at=%s:38:15 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:41:16 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:45:22 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:41:11 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:45:26 -new-name=foo %s | FileCheck --check-prefix=CHECK3 %s + +typedef I1 * TypedefI1; // CHECK1: rename [[@LINE]]:12 -> [[@LINE]]:14 + // CHECK2: rename [[@LINE-1]]:16 -> [[@LINE-1]]:18 + +void qualifiedClassPointer(I1 *x) { // CHECK1: rename [[@LINE]]:31 -> [[@LINE]]:33 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:54:12 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:57:31 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:54:16 -new-name=foo %s | FileCheck --check-prefix=CHECK2 %s + +void protocolTypeof(typeof(@protocol(P1)) *bar) { // CHECK1: rename [[@LINE]]:38 -> [[@LINE]]:40 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:64:38 -new-name=foo %s | FileCheck --check-prefix=CHECK1 %s diff --git a/clang/test/Refactor/Rename/ProhibitedDeclarations.cpp b/clang/test/Refactor/Rename/ProhibitedDeclarations.cpp new file mode 100644 index 0000000000000..b93f11d193401 --- /dev/null +++ b/clang/test/Refactor/Rename/ProhibitedDeclarations.cpp @@ -0,0 +1,72 @@ +void dontRenameBuiltins(int x) { + __builtin_assume(x != 0); + __builtin_trap(); +} + +// RUN: not clang-refactor-test rename-initiate -at=%s:2:3 -at=%s:3:3 -new-name=foo %s 2>&1 | FileCheck %s +// CHECK: error: could not rename symbol at the given location + +// RUN: not clang-refactor-test list-actions -at=%s:2:3 %s 2>&1 | FileCheck --check-prefix=CHECK-BUILTIN %s +// CHECK-BUILTIN: Failed to initiate 1 actions because: +// CHECK-BUILTIN-NEXT: Rename: '__builtin_assume' is a builtin function that cannot be renamed +// CHECK-BUILTIN-NEXT: No refactoring actions are available at the given location + +#include + +void dontRenameSystemSymbols() { + systemFunction(); +} +// RUN: not clang-refactor-test rename-initiate -at=%s:17:3 -new-name=foo %s -isystem %S/Inputs 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-SYSTEM %s +// CHECK-SYSTEM: 'systemFunction' cannot be renamed because it is declared in a system header + +struct External { + static void foo(); +} __attribute__((external_source_symbol(language="Swift"))); + +void dontRenameExternalSourceSymbols() { + External::foo(); +} +// RUN: not clang-refactor-test rename-initiate -at=%s:27:3 -new-name=foo %s 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-EXTERNAL1 %s +// CHECK-EXTERNAL1: 'External' is declared in a Swift file; rename can be initiated in a Swift file only + +// RUN: not clang-refactor-test rename-initiate -at=%s:27:13 -new-name=foo %s 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-EXTERNAL2 %s +// CHECK-EXTERNAL2: 'foo' is declared in a Swift file; rename can be initiated in a Swift file only + +// Ensure that operators can't be renamed: +struct Stream { +}; + +Stream &operator <<(Stream &, int); + +void renameArgsNotOperator(Stream x) { // CHECK-OP-X: rename local [[@LINE]]:35 -> [[@LINE]]:36 + int y = 0; // CHECK-OP-Y: rename local [[@LINE]]:7 -> [[@LINE]]:8 + x << // CHECK-OP-X: rename local [[@LINE]]:3 -> [[@LINE]]:4 + y << // CHECK-OP-Y: rename local [[@LINE]]:3 -> [[@LINE]]:4 + y; // CHECK-OP-Y: rename local [[@LINE]]:3 -> [[@LINE]]:4 +} +// RUN: clang-refactor-test rename-initiate -at=%s:43:3 -new-name=foo %s | FileCheck --check-prefixes=CHECK-OP-X %s +// RUN: clang-refactor-test rename-initiate -at=%s:44:3 -at=%s:45:3 -new-name=foo %s | FileCheck --check-prefixes=CHECK-OP-Y %s + +struct SystemStruct; + +// RUN: not clang-refactor-test rename-initiate -at=%s:50:8 -new-name=foo %s -isystem %S/Inputs 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-SYSTEM2 %s +// CHECK-SYSTEM2: 'SystemStruct' cannot be renamed because it is declared in a system header + +typedef struct SystemStruct SystemTypedef; + +// RUN: not clang-refactor-test rename-initiate -at=%s:55:29 -new-name=foo %s -isystem %S/Inputs 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-SYSTEM3 %s +// CHECK-SYSTEM3: 'SystemTypedef' cannot be renamed because it is declared in a system header + +enum SystemEnum; + +// RUN: not clang-refactor-test rename-initiate -at=%s:60:6 -new-name=foo %s -isystem %S/Inputs 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-SYSTEM4 %s +// CHECK-SYSTEM4: 'SystemEnum' cannot be renamed because it is declared in a system header + +void systemFunction(); + +// RUN: not clang-refactor-test rename-initiate -at=%s:65:6 -new-name=foo %s -isystem %S/Inputs 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-SYSTEM %s + +int systemVariable; + +// RUN: not clang-refactor-test rename-initiate -at=%s:69:5 -new-name=foo %s -isystem %S/Inputs 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-SYSTEM5 %s +// CHECK-SYSTEM5: 'systemVariable' cannot be renamed because it is declared in a system header diff --git a/clang/test/Refactor/Rename/ProhibitedDeclarations.m b/clang/test/Refactor/Rename/ProhibitedDeclarations.m new file mode 100644 index 0000000000000..cb91a6b5dd6b3 --- /dev/null +++ b/clang/test/Refactor/Rename/ProhibitedDeclarations.m @@ -0,0 +1,35 @@ +@protocol P1 +@end + +void dontRenameProtocol() { + Protocol *p = @protocol(P1); +} +// RUN: not clang-refactor-test rename-initiate -at=%s:5:3 -new-name=foo %s 2>&1 | FileCheck %s +// CHECK: error: could not rename symbol at the given location + +#include + +@interface MyClass: MySystemClass + +- (void)someMethod:(int)x with:(int)y; + +@end + +@implementation MyClass + +- (void)someMethod:(int)x with:(int)y { +} + +@end + +// RUN: not clang-refactor-test rename-initiate -at=%s:14:9 -at=%s:20:9 -at=%s:28:9 -new-name=foo:bar %s -isystem %S/Inputs 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-SYSTEM %s +// CHECK-SYSTEM: method 'someMethod:with:' cannot be renamed because it overrides a method declared in a system framework + +@interface MySubClass: MyClass +@end +@implementation MySubClass +- (void)someMethod:(int)x with:(int)y { +} +@end + +// RUN: not clang-refactor-test rename-initiate -at=%s:31:9 -new-name=foo:bar %s -isystem %S/Inputs 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-SYSTEM %s diff --git a/clang/test/Refactor/Rename/TemplateClassInstantiation.cpp b/clang/test/Refactor/Rename/TemplateClassInstantiation.cpp new file mode 100644 index 0000000000000..48b76030cda74 --- /dev/null +++ b/clang/test/Refactor/Rename/TemplateClassInstantiation.cpp @@ -0,0 +1,39 @@ +template +class Foo { /* Test 1 */ // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 +public: + T foo(T arg, T& ref, T* ptr) { + T value; + int number = 42; + value = (T)number; + value = static_cast(number); + return value; + } + static void foo(T value) {} + T member; +}; + +template +void func() { + Foo obj; /* Test 2 */ // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + obj.member = T(); + Foo::foo(); // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 +} + +int main() { + Foo i; /* Test 3 */ // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + i.member = 0; + Foo::foo(0); // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + + Foo b; // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + b.member = false; + Foo::foo(false); // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + + return 0; +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:2:7 -new-name=Bar %s -fno-delayed-template-parsing | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:17:3 -new-name=Bar %s -fno-delayed-template-parsing | FileCheck %s +// Test 3. +// RUN: clang-refactor-test rename-initiate -at=%s:25:3 -new-name=Bar %s -fno-delayed-template-parsing | FileCheck %s diff --git a/clang/test/Refactor/Rename/TemplateParameters.cpp b/clang/test/Refactor/Rename/TemplateParameters.cpp new file mode 100644 index 0000000000000..2dbff6995909f --- /dev/null +++ b/clang/test/Refactor/Rename/TemplateParameters.cpp @@ -0,0 +1,25 @@ +template // CHECK1: rename local [[@LINE]]:20 -> [[@LINE]]:21 +class Foo { // CHECK2: rename local [[@LINE-1]]:27 -> [[@LINE-1]]:28 + +T func(); // CHECK1-NEXT: rename local [[@LINE]]:1 -> [[@LINE]]:2 + +int array[x]; // CHECK2-NEXT: rename local [[@LINE]]:11 -> [[@LINE]]:12 +}; + +// CHECK1-NOT: rename +// CHECK2-NOT: rename + +// RUN: clang-refactor-test rename-initiate -at=%s:1:20 -new-name=U %s -fno-delayed-template-parsing | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:1:27 -new-name=U %s -fno-delayed-template-parsing | FileCheck --check-prefix=CHECK2 %s + +template +T Foo::func() { return T(); } + +template class H, typename S> // CHECK3: rename local [[@LINE]]:36 -> [[@LINE]]:37 +// CHECK4: rename local [[@LINE-1]]:48 -> [[@LINE-1]]:49 +void templateTemplateParam(const H &value) { // CHECK3-NEXT: rename local [[@LINE]]:34 -> [[@LINE]]:35 +// CHECK4-NEXT: rename local [[@LINE-1]]:36 -> [[@LINE-1]]:37 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:18:36 -at=%s:20:34 -new-name=U %s -fno-delayed-template-parsing | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:18:48 -at=%s:20:36 -new-name=U %s -fno-delayed-template-parsing | FileCheck --check-prefix=CHECK4 %s diff --git a/clang/test/Refactor/Rename/TemplateTypename.cpp b/clang/test/Refactor/Rename/TemplateTypename.cpp new file mode 100644 index 0000000000000..da79f67e9572b --- /dev/null +++ b/clang/test/Refactor/Rename/TemplateTypename.cpp @@ -0,0 +1,24 @@ +template // CHECK: rename local [[@LINE]]:20 -> [[@LINE]]:21 +class Foo { +T foo(T arg, T& ref, T* /* Test 2 */ ptr) { // CHECK: rename local [[@LINE]]:1 -> [[@LINE]]:2 + // CHECK: rename local [[@LINE-1]]:7 -> [[@LINE-1]]:8 + // CHECK: rename local [[@LINE-2]]:14 -> [[@LINE-2]]:15 + // CHECK: rename local [[@LINE-3]]:22 -> [[@LINE-3]]:23 + T value; // CHECK: rename local [[@LINE]]:3 -> [[@LINE]]:4 + int number = 42; + value = (T)number; // CHECK: rename local [[@LINE]]:12 -> [[@LINE]]:13 + value = static_cast(number); // CHECK: rename local [[@LINE]]:23 -> [[@LINE]]:24 + return value; +} + +static void foo(T value) {} // CHECK: rename local [[@LINE]]:17 -> [[@LINE]]:18 + +T member; // CHECK: rename local [[@LINE]]:1 -> [[@LINE]]:2 +}; + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:1:20 -new-name=U %s -fno-delayed-template-parsing | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:3:22 -new-name=U %s -fno-delayed-template-parsing | FileCheck %s +// Test 3. +// RUN: clang-refactor-test rename-initiate -at=%s:10:23 -new-name=U %s -fno-delayed-template-parsing | FileCheck %s diff --git a/clang/test/Refactor/Rename/TemplatedClassFunction.cpp b/clang/test/Refactor/Rename/TemplatedClassFunction.cpp new file mode 100644 index 0000000000000..7eb3e440decb1 --- /dev/null +++ b/clang/test/Refactor/Rename/TemplatedClassFunction.cpp @@ -0,0 +1,19 @@ +template +class A { +public: + void foo() /* Test 1 */ {} // CHECK: rename [[@LINE]]:8 -> [[@LINE]]:11 +}; + +int main(int argc, char **argv) { + A a; + a.foo(); /* Test 2 */ // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 + return 0; +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:4:8 -new-name=bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:9:5 -new-name=bar %s | FileCheck %s +// +// Currently unsupported test. +// XFAIL: * diff --git a/clang/test/Refactor/Rename/TransparentTypedef.m b/clang/test/Refactor/Rename/TransparentTypedef.m new file mode 100644 index 0000000000000..5717bb8636cd7 --- /dev/null +++ b/clang/test/Refactor/Rename/TransparentTypedef.m @@ -0,0 +1,40 @@ +#define NS_ENUM(_name, _type) enum _name:_type _name; enum _name : _type +// CHECK1: Renaming 1 symbols +// CHECK1-NEXT: 'c:@E@AnotherEnum' +typedef NS_ENUM(AnotherEnum, int) { // CHECK1: rename [[@LINE]]:17 -> [[@LINE]]:28 + AnotherEnumFirst = 0, +}; +AnotherEnum anotherT; // CHECK1: rename [[@LINE]]:1 -> [[@LINE]]:12 +enum AnotherEnum anotherE; // CHECK1: rename [[@LINE]]:6 -> [[@LINE]]:17 + +// RUN: clang-refactor-test rename-initiate -at=%s:4:17 -at=%s:7:1 -at=%s:8:6 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK1 %s + +#define TRANSPARENT(_name) struct _name _name; struct _name +#define OPAQUE(_name) struct _name *_name; struct _name + +// CHECK2: Renaming 1 symbols +// CHECK2-NEXT: 'c:@S@AStruct' +typedef TRANSPARENT(AStruct) { // CHECK2: rename [[@LINE]]:21 -> [[@LINE]]:28 + int x; +}; + +AStruct aStructT; // CHECK2: rename [[@LINE]]:1 -> [[@LINE]]:8 +struct AStruct aStructS; // CHECK2: rename [[@LINE]]:8 -> [[@LINE]]:15 + +// RUN: clang-refactor-test rename-initiate -at=%s:17:21 -at=%s:21:1 -at=%s:22:8 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK2 %s + +// CHECK3: Renaming 1 symbols +// CHECK3-NEXT: 'c:{{.*}}TransparentTypedef.m@T@Separate' +// CHECK4: Renaming 1 symbols +// CHECK4-NEXT: 'c:@S@Separate' + +typedef OPAQUE(Separate) { // CHECK3: rename [[@LINE]]:16 -> [[@LINE]]:24 + int x; // CHECK4: rename [[@LINE-1]]:16 -> [[@LINE-1]]:24 +}; + + +Separate separateT; // CHECK3: rename [[@LINE]]:1 -> [[@LINE]]:9 +struct Separate separateE; // CHECK4: rename [[@LINE]]:8 -> [[@LINE]]:16 + +// RUN: clang-refactor-test rename-initiate -at=%s:31:16 -at=%s:36:1 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:37:8 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK4 %s diff --git a/clang/test/Refactor/Rename/TypedefTag.cpp b/clang/test/Refactor/Rename/TypedefTag.cpp new file mode 100644 index 0000000000000..390350c1969ef --- /dev/null +++ b/clang/test/Refactor/Rename/TypedefTag.cpp @@ -0,0 +1,21 @@ +struct S1 { }; // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:10 + +typedef struct S2 { } S3; // CHECK2: rename [[@LINE]]:16 -> [[@LINE]]:18 +// CHECK3: rename [[@LINE-1]]:23 -> [[@LINE-1]]:25 + +void func(struct S1, // CHECK1-NEXT: rename [[@LINE]]:18 -> [[@LINE]]:20 + struct S2, // CHECK2-NEXT: rename [[@LINE]]:18 -> [[@LINE]]:20 + S3) { // CHECK3-NEXT: rename [[@LINE]]:11 -> [[@LINE]]:13 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:1:8 -at=%s:6:18 -new-name=Bar %s | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:3:16 -at=%s:7:18 -new-name=Bar %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:3:23 -at=%s:8:11 -new-name=Bar %s | FileCheck --check-prefix=CHECK3 %s + +// RUN: clang-refactor-test rename-initiate -at=%s:1:8 -at=%s:6:18 -new-name=Bar %s -x c | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor-test rename-initiate -at=%s:3:16 -at=%s:7:18 -new-name=Bar %s -x c | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-initiate -at=%s:3:23 -at=%s:8:11 -new-name=Bar %s -x c | FileCheck --check-prefix=CHECK3 %s + +// RUN: not clang-refactor-test rename-initiate -at=%s:3:9 -at=%s:6:11 -at=%s:7:11 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=ERROR %s +// RUN: not clang-refactor-test rename-initiate -at=%s:3:9 -at=%s:6:11 -at=%s:7:11 -new-name=Bar %s -x c 2>&1 | FileCheck --check-prefix=ERROR %s +// ERROR: could not rename symbol at the given location diff --git a/clang/test/Refactor/Rename/USRForSymbols.m b/clang/test/Refactor/Rename/USRForSymbols.m new file mode 100644 index 0000000000000..1f1efe89209ed --- /dev/null +++ b/clang/test/Refactor/Rename/USRForSymbols.m @@ -0,0 +1,16 @@ +@interface I1 + +@property int p1; + +@end + +@implementation I1 + +@end + +// CHECK: 3 symbols +// CHECK: 'c:objc(cs)I1(py)p1' +// CHECK: 'c:objc(cs)I1(im)p1' +// CHECK: 'c:objc(cs)I1(im)setP1:' + +// RUN: clang-refactor-test rename-initiate -at=%s:3:15 -new-name=foo -dump-symbols %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/UserDefinedConversion.cpp b/clang/test/Refactor/Rename/UserDefinedConversion.cpp new file mode 100644 index 0000000000000..24614bf7440d3 --- /dev/null +++ b/clang/test/Refactor/Rename/UserDefinedConversion.cpp @@ -0,0 +1,23 @@ +class Foo { /* Test 1 */ // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:10 +public: + Foo() {} // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 +}; + +class Baz { +public: + operator Foo() /* Test 2 */ const { // CHECK: rename [[@LINE]]:12 -> [[@LINE]]:15 + Foo foo; // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 + return foo; + } +}; + +int main() { + Baz boo; + Foo foo = static_cast(boo); // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:6 + return 0; // CHECK: rename [[@LINE-1]]:25 -> [[@LINE-1]]:28 +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:1:7 -new-name=Bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:8:12 -new-name=Bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/UsingDecl.cpp b/clang/test/Refactor/Rename/UsingDecl.cpp new file mode 100644 index 0000000000000..99eb1f44b1fd2 --- /dev/null +++ b/clang/test/Refactor/Rename/UsingDecl.cpp @@ -0,0 +1,46 @@ + +namespace ns { // CHECK4: rename [[@LINE]]:11 -> [[@LINE]]:13 + +struct Struct { }; // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:14 + +void func(); // CHECK2: rename [[@LINE]]:6 -> [[@LINE]]:10 + +void overload1(int x); +void overload1(double y); // CHECK3: rename [[@LINE]]:6 -> [[@LINE]]:15 + +} + +using ns::Struct; // CHECK1: rename [[@LINE]]:11 -> [[@LINE]]:17 + +// RUN: clang-refactor-test rename-initiate -at=%s:13:11 -new-name=x %s | FileCheck --check-prefix=CHECK1 %s + +using ns::func; // CHECK2: rename [[@LINE]]:11 -> [[@LINE]]:15 + +// RUN: clang-refactor-test rename-initiate -at=%s:17:11 -new-name=x %s | FileCheck --check-prefix=CHECK2 %s + +using ns::overload1; // CHECK3: rename [[@LINE]]:11 -> [[@LINE]]:20 + +// RUN: clang-refactor-test rename-initiate -at=%s:21:11 -new-name=x %s | FileCheck --check-prefix=CHECK3 %s + +using namespace ns; // CHECK4: rename [[@LINE]]:17 -> [[@LINE]]:19 + +// RUN: clang-refactor-test rename-initiate -at=%s:25:17 -new-name=x %s | FileCheck --check-prefix=CHECK4 %s + +struct UsingConstructor { + template + UsingConstructor(T, typename T::Q); +}; +struct UsingConstructorHere : UsingConstructor { +using UsingConstructor::UsingConstructor; // CHECK5: rename [[@LINE]]:7 -> [[@LINE]]:23 +// CHECK5: rename [[@LINE-1]]:25 -> [[@LINE-1]]:41 +}; +// RUN: clang-refactor-test rename-initiate -at=%s:34:25 -new-name=x %s -std=c++14 | FileCheck --check-prefix=CHECK5 %s + +template +struct S : T { + using T::T; // CHECK6: rename local [[@LINE]]:12 -> [[@LINE]]:13 +// RUN: clang-refactor-test rename-initiate -at=%s:41:12 -new-name=x %s -std=c++14 | FileCheck --check-prefix=CHECK6 %s + using typename T::Foo; +// RUN: not clang-refactor-test rename-initiate -at=%s:43:12 -new-name=x %s -std=c++14 2>&1 | FileCheck --check-prefix=CHECK-UNRESOLVED %s +// CHECK-UNRESOLVED: error: could not rename symbol at the given location +}; diff --git a/clang/test/Refactor/Rename/Variable.cpp b/clang/test/Refactor/Rename/Variable.cpp new file mode 100644 index 0000000000000..7d5c41e13461e --- /dev/null +++ b/clang/test/Refactor/Rename/Variable.cpp @@ -0,0 +1,29 @@ +namespace A { +int Foo; /* Test 1 */ // CHECK: rename [[@LINE]]:5 -> [[@LINE]]:8 +} +int Foo; +int Qux = Foo; +int Baz = A::Foo; /* Test 2 */ // CHECK-NEXT: rename [[@LINE]]:14 -> [[@LINE]]:17 +void fun() { + struct { + int Foo; + } b = {100}; + int Foo = 100; + Baz = Foo; + { + extern int Foo; + Baz = Foo; + Foo = A::Foo /* Test 3 */ + Baz; // CHECK-NEXT: rename [[@LINE]]:14 -> [[@LINE]]:17 + A::Foo /* Test 4 */ = b.Foo; // CHECK-NEXT: rename [[@LINE]]:8 -> [[@LINE]]:11 + } + Foo = b.Foo; // CHECK-NOT: rename [[@LINE]] +} + +// Test 1. +// RUN: clang-refactor-test rename-initiate -at=%s:2:5 -new-name=Bar %s | FileCheck %s +// Test 2. +// RUN: clang-refactor-test rename-initiate -at=%s:6:14 -new-name=Bar %s | FileCheck %s +// Test 3. +// RUN: clang-refactor-test rename-initiate -at=%s:16:14 -new-name=Bar %s | FileCheck %s +// Test 4. +// RUN: clang-refactor-test rename-initiate -at=%s:17:8 -new-name=Bar %s | FileCheck %s diff --git a/clang/test/Refactor/Rename/VariableMacro.cpp b/clang/test/Refactor/Rename/VariableMacro.cpp new file mode 100644 index 0000000000000..312d459323910 --- /dev/null +++ b/clang/test/Refactor/Rename/VariableMacro.cpp @@ -0,0 +1,69 @@ +#define Baz Foo // CHECK1-NOT: rename local [[@LINE]] +// CHECK1-NOT: macro [[@LINE-1]] + +void foo(int value) {} + +void macro() { + int Foo; // CHECK1: rename local [[@LINE]]:7 -> [[@LINE]]:10 + Foo = 42; // CHECK1-NEXT: rename local [[@LINE]]:3 -> [[@LINE]]:6 + Baz -= 0; // CHECK1-NEXT: macro [[@LINE]]:3 -> [[@LINE]]:3 + foo(Foo); // CHECK1-NEXT: rename local [[@LINE]]:7 -> [[@LINE]]:10 + foo(Baz); // CHECK1-NEXT: macro [[@LINE]]:7 -> [[@LINE]]:7 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:7:7 -at=%s:8:3 -at=%s:10:7 -new-name=Bar %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: not clang-refactor-test rename-initiate -at=%s:1:13 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s +// RUN: not clang-refactor-test rename-initiate -at=%s:9:3 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s +// CHECK-ERROR: could not rename symbol at the given location + +#define M var +#define MM M +void macro2() { + int M = 2; // CHECK2: macro [[@LINE]]:7 -> [[@LINE]]:7 + (void)var; // CHECK2-NEXT: rename local [[@LINE]]:9 -> [[@LINE]]:12 + (void)M; // CHECK2-NEXT: macro [[@LINE]]:9 -> [[@LINE]]:9 + (void)MM; // CHECK2-NEXT: macro [[@LINE]]:9 -> [[@LINE]]:9 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:24:9 -new-name=Bar %s | FileCheck --check-prefix=CHECK2 %s + +// RUN: not clang-refactor-test rename-initiate -at=%s:20:11 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s +// RUN: not clang-refactor-test rename-initiate -at=%s:21:12 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s +// RUN: not clang-refactor-test rename-initiate -at=%s:23:7 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s +// RUN: not clang-refactor-test rename-initiate -at=%s:25:9 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s + +#define BAR(x) x +#define FOO(x) BAR(x) +int FOO(global) = 2; // CHECK3: rename [[@LINE]]:9 -> [[@LINE]]:15 +void macro3() { + (void)global; // CHECK3-NEXT: rename [[@LINE]]:9 -> [[@LINE]]:15 + BAR(global) = 0; // CHECK3-NEXT: rename [[@LINE]]:7 -> [[@LINE]]:13 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:40:9 -at=%s:41:7 -new-name=Bar %s | FileCheck --check-prefix=CHECK3 %s + + + +#define CONCAT(x, y) x##_##y +int CONCAT(a, b) = 2; // CHECK4: macro [[@LINE]]:5 -> [[@LINE]]:5 +void macro3() { + (void)a_b; // CHECK4-NEXT: rename [[@LINE]]:9 -> [[@LINE]]:12 + CONCAT(a, b) = 0; // CHECK4-NEXT: macro [[@LINE]]:3 -> [[@LINE]]:3 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:51:9 -new-name=Bar %s | FileCheck --check-prefix=CHECK4 %s + +void macroInFunc() { + #define VARNAME var + int VARNAME; +} + +// RUN: not clang-refactor-test rename-initiate -at=%s:58:19 -new-name=Bar %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s + +void localVarArg() { + int var; // CHECK5: rename local [[@LINE]]:7 -> [[@LINE]]:10 + BAR(var) = 0; // CHECK5-NEXT: rename local [[@LINE]]:7 -> [[@LINE]]:10 +} + +// RUN: clang-refactor-test rename-initiate -at=%s:65:7 -at=%s:66:7 -new-name=Bar %s | FileCheck --check-prefix=CHECK5 %s diff --git a/clang/test/Refactor/Rename/invalid-indexed-name.m b/clang/test/Refactor/Rename/invalid-indexed-name.m new file mode 100644 index 0000000000000..b682bb3fba9ca --- /dev/null +++ b/clang/test/Refactor/Rename/invalid-indexed-name.m @@ -0,0 +1,37 @@ +// XFAIL: * +// TODO: Remove if unused +// Note: the run lines follow their respective tests, since line/column +// matter in this test + +int variable = 0; // CHECK1: rename [[@LINE]]:5 -> [[@LINE]]:13 + +// RUN: clang-refactor-test rename-indexed-file -name=variable -new-name=class -indexed-file=%s -indexed-at=4:5 %s | FileCheck --check-prefix=CHECK1 %s +// RUN: not clang-refactor-test rename-indexed-file -name=variable -new-name=class -indexed-file=%s -indexed-at=4:5 %s -x objective-c++ 2>&1 | FileCheck --check-prefix=CHECK-ERR %s +// CHECK-ERR: error: invalid new name + +// RUN: not clang-refactor-test rename-indexed-file -name=variable -new-name=int -indexed-file=%s -indexed-at=4:5 %s 2>&1 | FileCheck --check-prefix=CHECK-ERR %s + +@interface I + +- (void)some:(int)x selector:(int)y; // CHECK2: rename [[@LINE]]:9 -> [[@LINE]]:13, [[@LINE]]:21 -> [[@LINE]]:29 + +@end + +// RUN: clang-refactor-test rename-indexed-file -name=some:selector -new-name=struct:void -indexed-file=%s -indexed-at=14:9 -indexed-symbol-kind=objc-im %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test rename-indexed-file -name=some:selector: -new-name=struct:void -indexed-file=%s -indexed-at=14:9 -indexed-symbol-kind=objc-cm %s | FileCheck --check-prefix=CHECK2 %s + +// RUN: not clang-refactor-test rename-indexed-file -name=some:selector -new-name=struct:void -indexed-file=%s -indexed-at=14:9 %s 2>&1 | FileCheck --check-prefix=CHECK-ERR-FAIL %s +// CHECK-ERR-FAIL: failed to perform indexed file rename +// RUN: not clang-refactor-test rename-indexed-file -name=some:selector -new-name=hello:123 -indexed-file=%s -indexed-at=14:9 -indexed-symbol-kind=objc-im %s 2>&1 | FileCheck --check-prefix=CHECK-ERR %s +// RUN: not clang-refactor-test rename-indexed-file -name=some:selector -new-name=+:test -indexed-file=%s -indexed-at=14:9 -indexed-symbol-kind=objc-im %s 2>&1 | FileCheck --check-prefix=CHECK-ERR %s + +// RUN: not clang-refactor-test rename-indexed-file -name=some:selector -new-name=justOnePiece -indexed-file=%s -indexed-at=14:9 -indexed-symbol-kind=objc-im %s 2>&1 | FileCheck --check-prefix=CHECK-ERR2 %s +// CHECK-ERR2: error: the number of strings in the new name 'justOnePiece' doesn't match the the number of strings in the old name + +@interface I1 + +- (void)singlePiece; + +@end + +// RUN: not clang-refactor-test rename-indexed-file -name=singlePiece -new-name=struct -indexed-file=%s -indexed-at=31:9 %s 2>&1 | FileCheck --check-prefix=CHECK-ERR %s diff --git a/clang/test/Refactor/Rename/invalid-name.cpp b/clang/test/Refactor/Rename/invalid-name.cpp new file mode 100644 index 0000000000000..703f8187d1293 --- /dev/null +++ b/clang/test/Refactor/Rename/invalid-name.cpp @@ -0,0 +1,13 @@ +// XFAIL: * +// TODO: Remove if unused +// Note: the run lines follow their respective tests, since line/column +// matter in this test + +int variable = 0; + +// RUN: not clang-refactor-test rename-initiate -at=%s:4:5 -new-name=class %s 2>&1 | FileCheck %s +// RUN: not clang-refactor-test rename-initiate -at=%s:4:5 -new-name=- %s 2>&1 | FileCheck %s +// RUN: not clang-refactor-test rename-initiate -at=%s:4:5 -new-name=var+ %s 2>&1 | FileCheck %s +// RUN: not clang-refactor-test rename-initiate -at=%s:4:5 -new-name="var " %s 2>&1 | FileCheck %s + +// CHECK: error: invalid new name diff --git a/clang/test/Refactor/Rename/invalid-name.m b/clang/test/Refactor/Rename/invalid-name.m new file mode 100644 index 0000000000000..65014be0ab0e1 --- /dev/null +++ b/clang/test/Refactor/Rename/invalid-name.m @@ -0,0 +1,23 @@ +// XFAIL: * +// TODO: Remove if unused +// Note: the run lines follow their respective tests, since line/column +// matter in this test + +int variable = 0; // CHECK1: rename [[@LINE]]:5 -> [[@LINE]]:13 + +// RUN: clang-refactor-test rename-initiate -at=%s:4:5 -new-name=class %s | FileCheck --check-prefix=CHECK1 %s +// RUN: not clang-refactor-test rename-initiate -at=%s:4:5 -new-name=class %s -x objective-c++ 2>&1 | FileCheck --check-prefix=CHECK-ERR %s +// CHECK-ERR: error: invalid new name + +// RUN: not clang-refactor-test rename-initiate -at=%s:4:5 -new-name=int %s 2>&1 | FileCheck --check-prefix=CHECK-ERR %s + +@interface I + +- (void)some:(int)x selector:(int)y; // CHECK2: rename [[@LINE]]:9 -> [[@LINE]]:13, [[@LINE]]:21 -> [[@LINE]]:29 + +@end + +// RUN: clang-refactor-test rename-initiate -at=%s:14:9 -new-name=struct:void %s | FileCheck --check-prefix=CHECK2 %s + +// RUN: not clang-refactor-test rename-initiate -at=%s:14:9 -new-name=hello:123 %s 2>&1 | FileCheck --check-prefix=CHECK-ERR %s +// RUN: not clang-refactor-test rename-initiate -at=%s:14:9 -new-name=+:test %s 2>&1 | FileCheck --check-prefix=CHECK-ERR %s diff --git a/clang/test/Refactor/Rename/rename-indexed-file.cpp b/clang/test/Refactor/Rename/rename-indexed-file.cpp new file mode 100644 index 0000000000000..4e64fdf511a5f --- /dev/null +++ b/clang/test/Refactor/Rename/rename-indexed-file.cpp @@ -0,0 +1,123 @@ +// Note: the run lines follow their respective tests, since line/column +// matter in this test + +class Test { // CHECK1: rename [[@LINE]]:7 -> [[@LINE]]:11 +public: + Test() { } // CHECK1: rename [[@LINE]]:3 -> [[@LINE]]:7 + ~Test() { } // CHECK1: rename [[@LINE]]:4 -> [[@LINE]]:8 + + void doSomething() { return; } + void otherFile(); +}; + +void foo() { + Test test; // CHECK1: rename [[@LINE]]:3 -> [[@LINE]]:7 + (test).doSomething(); +} + +Test notIndexed; // CHECK1-NOT: rename [[@LINE]] + +// RUN: clang-refactor-test rename-indexed-file -name=Test -new-name=Foo -indexed-file=%s -indexed-at=4:7 -indexed-at=6:3 -indexed-at=7:4 -indexed-at=14:3 %s | FileCheck --check-prefix=CHECK1 %s + +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=Test -new-name=Foo -indexed-file=%S/Inputs/rename-indexed-file.cpp -indexed-at=1:6 -indexed-at=2:3 -indexed-at=3:6 %s | FileCheck --check-prefix=CHECK2 %s +// CHECK2: rename 1:6 -> 1:10 +// CHECK2: rename 2:3 -> 2:7 +// CHECK2: rename 3:6 -> 3:10 + +// A valid location with an non-identifier token shouldn't produce an occurence +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=Test -new-name=Foo -indexed-file=%s -indexed-at=15:3 %s | FileCheck --check-prefix=CHECK3 %s + +// A invalid location shouldn't produce an occurence +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=Test -new-name=Foo -indexed-file=%s -indexed-at=999:1 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=Test -new-name=Foo -indexed-file=%s -indexed-at=0:1 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=Test -new-name=Foo -indexed-file=%s -indexed-at=1:0 %s | FileCheck --check-prefix=CHECK3 %s + + +// CHECK3: no replacements found +// CHECK3-NOT: rename + +// RUN: not clang-refactor-test rename-indexed-file -no-textual-matches -name=Test -new-name=Foo %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR1 %s + +// CHECK-ERROR1: for the -indexed-file option: must be specified at least once! + +// It should be possible to have the filename as one of the compilation arguments +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -ignore-filename-for-initiation-tu -name=Test -new-name=Foo -indexed-file=%s -indexed-at=4:7 -indexed-at=6:3 -indexed-at=7:4 -indexed-at=14:3 %s -c %s -Wall | FileCheck --check-prefix=CHECK1 %s + +// -gmodules should be stripped to avoid -fmodule-format=obj in CC1 arguments: +// RUN: clang-refactor-test rename-indexed-file -no-textual-matches -name=Test -new-name=Foo -indexed-file=%s -indexed-at=4:7 -indexed-at=6:3 -indexed-at=7:4 -indexed-at=14:3 %s -fmodules -gmodules | FileCheck --check-prefix=CHECK1 %s + +// These texual matches should be reported as comment occurrences: +// CHECK4-INIT: rename [[@LINE-46]]:7 -> [[@LINE-46]]:11 +// Test +/* Test 2 Test */ +/** Test+1 +// Test +**/ +/// Hello Test World +//! \c Test. + +// CHECK4: comment [[@LINE-8]]:4 -> [[@LINE-8]]:8 +// CHECK4-NEXT: comment [[@LINE-8]]:4 -> [[@LINE-8]]:8 +// CHECK4-NEXT: comment [[@LINE-9]]:11 -> [[@LINE-9]]:15 +// CHECK4-NEXT: documentation [[@LINE-9]]:5 -> [[@LINE-9]]:9 +// CHECK4-NEXT: documentation [[@LINE-9]]:4 -> [[@LINE-9]]:8 +// CHECK4-NEXT: documentation [[@LINE-8]]:11 -> [[@LINE-8]]:15 +// CHECK4-NEXT: documentation [[@LINE-8]]:8 -> [[@LINE-8]]:12 + +// "Test" +// 'Test' +// CHECK4-NEXT: comment [[@LINE-2]]:5 -> [[@LINE-2]]:9 +// CHECK4-NEXT: comment [[@LINE-2]]:5 -> [[@LINE-2]]:9 + +// CHECK4-NEXT: comment [[@LINE+1]]:55 +// RUN: clang-refactor-test rename-indexed-file -name=Test -new-name=Foo -indexed-file=%s -indexed-at=4:7 %s | FileCheck --check-prefixes=CHECK4-INIT,CHECK4 %s +// We should find textual occurrences even without indexed occurrences: +// CHECK4-NEXT: comment [[@LINE+1]]:55 +// RUN: clang-refactor-test rename-indexed-file -name=Test -new-name=Foo -indexed-file=%s %s | FileCheck --check-prefix=CHECK4 %s + +// These ones shouldn't: +// Test2 test Testable +/// _Test +/// ATest_ +const char *test = "Test"; +void Test20() { } + +// CHECK4-NOT: comment +// CHECK4-NOT: documentation + + +class MyInclude { // CHECK5: rename [[@LINE]]:7 -> [[@LINE]]:16 +}; + + /*comment*/ #include "MyInclude.h" +#include +#import +// CHECK5-NEXT: filename [[@LINE-3]]:24 -> [[@LINE-3]]:33 +// CHECK5-NEXT: filename [[@LINE-3]]:17 -> [[@LINE-3]]:26 +// CHECK5-NEXT: filename [[@LINE-3]]:26 -> [[@LINE-3]]:35 + +// CHECK5-NOT: filename +#include "My Include.h" +"MyInclude.h" + +// RUN: clang-refactor-test rename-indexed-file -name=MyInclude -new-name=Foo -indexed-file=%s -indexed-at=89:7 -indexed-at=include:92:1 -indexed-at=include:93:1 -indexed-at=include:94:1 -indexed-at=include:100:1 %s | FileCheck --check-prefix=CHECK5 %s + +#define MACRO variable + +void macroOccurrence() { + variable; + MACRO; + 22; + MACRO; +} +// CHECK-MACRO: rename [[@LINE-5]]:3 -> [[@LINE-5]]:11 +// CHECK-MACRO-NEXT: macro [[@LINE-5]]:3 -> [[@LINE-5]]:3 +// CHECK-MACRO-NOT: macro + +// RUN: clang-refactor-test rename-indexed-file -name=variable -new-name=foo -indexed-file=%s -indexed-at=108:3 -indexed-at=109:3 -indexed-at=110:3 -indexed-at=111:2 %s | FileCheck --check-prefix=CHECK-MACRO %s + +struct MyType { // CHECK-MACRO-PREFIX: rename [[@LINE]]:8 -> [[@LINE]]:14 +}; +MyType MyTypePrefix; // CHECK-MACRO-PREFIX: macro [[@LINE]]:8 -> [[@LINE]]:8 + +// RUN: clang-refactor-test rename-indexed-file -name=MyType -new-name=x -indexed-file=%s -indexed-at=119:8 -indexed-at=121:8 %s | FileCheck --check-prefix=CHECK-MACRO-PREFIX %s diff --git a/clang/test/Refactor/Rename/rename-initiate-usr.cpp b/clang/test/Refactor/Rename/rename-initiate-usr.cpp new file mode 100644 index 0000000000000..2f1546522edba --- /dev/null +++ b/clang/test/Refactor/Rename/rename-initiate-usr.cpp @@ -0,0 +1,20 @@ +// Note: the run lines follow their respective tests, since line/column +// matter in this test + +class Test { // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:11 +public: + Test() { } // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:7 + ~Test() { } // CHECK: rename [[@LINE]]:4 -> [[@LINE]]:8 +}; + +void foo() { + Test test; // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:7 +} + +// RUN: clang-refactor-test rename-initiate-usr -usr="c:@S@Test" -new-name=Foo %s | FileCheck %s + +// RUN: not clang-refactor-test rename-initiate-usr -usr="c:@S@Foo" -new-name=Foo %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR1 %s +// CHECK-ERROR1: error: could not rename symbol with the given USR + +// RUN: not clang-refactor-test rename-initiate-usr -new-name=Foo %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR2 %s +// CHECK-ERROR2: for the -usr option: must be specified at least once diff --git a/clang/test/Refactor/Rename/rename-initiate.cpp b/clang/test/Refactor/Rename/rename-initiate.cpp new file mode 100644 index 0000000000000..30c918cc834e7 --- /dev/null +++ b/clang/test/Refactor/Rename/rename-initiate.cpp @@ -0,0 +1,27 @@ +// Note: the run lines follow their respective tests, since line/column +// matter in this test + +class Test { // CHECK: rename [[@LINE]]:7 -> [[@LINE]]:11 +public: + Test() { } // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:7 + ~Test() { } // CHECK: rename [[@LINE]]:4 -> [[@LINE]]:8 + void doSomething() { + return; + } +}; + +void foo() { + Test test; // CHECK: rename [[@LINE]]:3 -> [[@LINE]]:7 + test.doSomething(); +} + +// RUN: clang-refactor-test rename-initiate -at=%s:4:7 -new-name=Foo %s | FileCheck %s +// RUN: clang-refactor-test rename-initiate -at=%s:4:8 -new-name=Foo %s | FileCheck %s +// RUN: clang-refactor-test rename-initiate -at=%s:4:9 -new-name=Foo %s | FileCheck %s +// RUN: clang-refactor-test rename-initiate -at=%s:4:10 -new-name=Foo %s | FileCheck %s + +// RUN: not clang-refactor-test rename-initiate -at=%s:1:10 -new-name=Foo %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR1 %s +// CHECK-ERROR1: error: could not rename symbol at the given location + +// RUN: not clang-refactor-test rename-initiate -at=%s -new-name=Foo %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR2 %s +// CHECK-ERROR2: error: The -at option must use the format diff --git a/clang/test/Refactor/list-refactoring-actions.cpp b/clang/test/Refactor/list-refactoring-actions.cpp new file mode 100644 index 0000000000000..573a076bf1449 --- /dev/null +++ b/clang/test/Refactor/list-refactoring-actions.cpp @@ -0,0 +1,39 @@ +int +renamable = 0; + +// RUN: clang-refactor-test list-actions -at=%s:2:1 %s | FileCheck --check-prefix=CHECK-RENAME %s + +// CHECK-RENAME: Found {{[0-9]*}} actions: +// CHECK-RENAME-NEXT: Rename + +// RUN: not clang-refactor-test list-actions -at=%s:2:13 %s 2>&1 | FileCheck --check-prefix=CHECK-NONE %s + +// CHECK-NONE: No refactoring actions are available at the given location +// CHECK-NONE-NOT: Rename + +// RUN: not clang-refactor-test list-actions -at=%s %s 2>&1 | FileCheck --check-prefix=CHECK-ERR %s +// CHECK-ERR: error: The -at option must use the format + +void localVsGlobalRename(int renamable) { } + +// RUN: clang-refactor-test list-actions -dump-raw-action-type -at=%s:17:30 %s | FileCheck --check-prefix=CHECK-LOCAL-RENAME %s + +// CHECK-LOCAL-RENAME: Found {{[0-9]*}} actions: +// CHECK-LOCAL-RENAME-NEXT: Rename(1) + +namespace nullDeclNamespace { + +template class C> class NullNode {}; + +struct AfterNull { }; +// RUN: clang-refactor-test list-actions -at=%s:28:8 %s | FileCheck --check-prefix=CHECK-RENAME %s + +} + +#define MACRO(X) (void)X; +void macroArg() { + int variable = 0; + MACRO(variable); +} +// RUN: not clang-refactor-test list-actions -at=%s:26:9 -selected=%s:36:9-36:16 %s 2>&1 | FileCheck --check-prefix=CHECK-MACRO-ARG %s +// CHECK-MACRO-ARG: No refactoring actions are available at the given location diff --git a/clang/test/Sema/enum-attr.c b/clang/test/Sema/enum-attr.c index 933d8ccdcd89c..bf0dcb0bfe18f 100644 --- a/clang/test/Sema/enum-attr.c +++ b/clang/test/Sema/enum-attr.c @@ -44,7 +44,7 @@ void test() { enum Enum t0 = 100; // expected-warning{{integer constant not in range of enumerated type}} t0 = 1; - switch (t0) { // expected-warning{{enumeration value 'A1' not handled in switch}} + switch (t0) { // expected-warning{{enumeration value 'A1' not handled in switch}} expected-note {{add missing switch cases}} case A0: break; case 16: break; // expected-warning{{case value not in enumerated type}} } @@ -58,7 +58,7 @@ void test() { enum EnumClosed t1 = 100; // expected-warning{{integer constant not in range of enumerated type}} t1 = 1; - switch (t1) { // expected-warning{{enumeration value 'B1' not handled in switch}} + switch (t1) { // expected-warning{{enumeration value 'B1' not handled in switch}} expected-note {{add missing switch cases}} case B0: break; case 16: break; // expected-warning{{case value not in enumerated type}} } @@ -72,7 +72,7 @@ void test() { enum EnumOpen t2 = 100; t2 = 1; - switch (t2) { // expected-warning{{enumeration value 'C1' not handled in switch}} + switch (t2) { // expected-warning{{enumeration value 'C1' not handled in switch}} expected-note {{add missing switch cases}} case C0: break; case 16: break; } @@ -86,7 +86,7 @@ void test() { enum EnumFlag t3 = 5; // expected-warning{{integer constant not in range of enumerated type}} t3 = 9; - switch (t3) { // expected-warning{{enumeration value 'D1' not handled in switch}} + switch (t3) { // expected-warning{{enumeration value 'D1' not handled in switch}} expected-note {{add missing switch cases}} case D0: break; case 9: break; case 16: break; // expected-warning{{case value not in enumerated type}} @@ -101,7 +101,7 @@ void test() { enum EnumFlagClosed t4 = 5; // expected-warning{{integer constant not in range of enumerated type}} t4 = 9; - switch (t4) { // expected-warning{{enumeration value 'E1' not handled in switch}} + switch (t4) { // expected-warning{{enumeration value 'E1' not handled in switch}} expected-note {{add missing switch cases}} case E0: break; case 9: break; case 16: break; // expected-warning{{case value not in enumerated type}} @@ -116,7 +116,7 @@ void test() { enum EnumFlagOpen t5 = 5; t5 = 9; - switch (t5) { // expected-warning{{enumeration value 'F1' not handled in switch}} + switch (t5) { // expected-warning{{enumeration value 'F1' not handled in switch}} expected-note {{add missing switch cases}} case F0: break; case 9: break; case 16: break; diff --git a/clang/test/Sema/statements.c b/clang/test/Sema/statements.c index dbb4d56ee1d17..a17e8405b4947 100644 --- a/clang/test/Sema/statements.c +++ b/clang/test/Sema/statements.c @@ -56,7 +56,7 @@ int test12(enum Numbers num) { enum x { a, b, c, d, e, f, g }; void foo(enum x X) { - switch (X) { // expected-warning {{enumeration value 'g' not handled in switch}} + switch (X) { // expected-warning {{enumeration value 'g' not handled in switch}} expected-note {{add missing switch cases}} case a: case b: case c: @@ -66,7 +66,7 @@ void foo(enum x X) { break; } - switch (X) { // expected-warning {{enumeration values 'f' and 'g' not handled in switch}} + switch (X) { // expected-warning {{enumeration values 'f' and 'g' not handled in switch}} expected-note {{add missing switch cases}} case a: case b: case c: @@ -75,7 +75,7 @@ void foo(enum x X) { break; } - switch (X) { // expected-warning {{enumeration values 'e', 'f', and 'g' not handled in switch}} + switch (X) { // expected-warning {{enumeration values 'e', 'f', and 'g' not handled in switch}} expected-note {{add missing switch cases}} case a: case b: case c: @@ -83,7 +83,7 @@ void foo(enum x X) { break; } - switch (X) { // expected-warning {{5 enumeration values not handled in switch: 'c', 'd', 'e'...}} + switch (X) { // expected-warning {{5 enumeration values not handled in switch: 'c', 'd', 'e'...}} expected-note {{add missing switch cases}} case a: case b: break; diff --git a/clang/test/Sema/switch.c b/clang/test/Sema/switch.c index 7aa695d7cb919..5580209876ba3 100644 --- a/clang/test/Sema/switch.c +++ b/clang/test/Sema/switch.c @@ -97,7 +97,7 @@ void test7() { A = 1, B } a; - switch(a) { //expected-warning{{enumeration value 'B' not handled in switch}} + switch(a) { //expected-warning{{enumeration value 'B' not handled in switch}} expected-note {{add missing switch cases}} case A: break; } @@ -155,7 +155,7 @@ void test8() { case C: break; } - switch(a) { //expected-warning{{enumeration value 'B' not handled in switch}} + switch(a) { //expected-warning{{enumeration value 'B' not handled in switch}} expected-note {{add missing switch cases}} case A: break; } @@ -202,13 +202,13 @@ void test11() { B, C } a; - switch(a) { //expected-warning{{enumeration value 'A' not handled in switch}} + switch(a) { //expected-warning{{enumeration value 'A' not handled in switch}} expected-note {{add missing switch cases}} case B: case C: break; } - switch(a) { //expected-warning{{enumeration value 'A' not explicitly handled in switch}} + switch(a) { //expected-warning{{enumeration value 'A' not explicitly handled in switch}} expected-note {{add missing switch cases}} case B: case C: break; @@ -238,7 +238,7 @@ typedef enum { } my_type_t; int test13(my_type_t t) { - switch(t) { // expected-warning{{enumeration value 'val3' not handled in switch}} + switch(t) { // expected-warning{{enumeration value 'val3' not handled in switch}} expected-note {{add missing switch cases}} case val1: return 1; case val2: diff --git a/clang/test/SemaCXX/array-bounds.cpp b/clang/test/SemaCXX/array-bounds.cpp index 8ae92e7613010..92e8dd4e13613 100644 --- a/clang/test/SemaCXX/array-bounds.cpp +++ b/clang/test/SemaCXX/array-bounds.cpp @@ -164,7 +164,7 @@ enum enumB { enumB_X, enumB_Y, enumB_Z }; static enum enumB myVal = enumB_X; void test_nested_switch() { switch (enumA_E) { // expected-warning {{no case matching constant}} - switch (myVal) { // expected-warning {{enumeration values 'enumB_X' and 'enumB_Z' not handled in switch}} + switch (myVal) { // expected-warning {{enumeration values 'enumB_X' and 'enumB_Z' not handled in switch}} expected-note {{add missing switch cases}} case enumB_Y: ; } } diff --git a/clang/test/SemaCXX/enum-attr.cpp b/clang/test/SemaCXX/enum-attr.cpp index 7726aff4830a3..92027ae5bd238 100644 --- a/clang/test/SemaCXX/enum-attr.cpp +++ b/clang/test/SemaCXX/enum-attr.cpp @@ -27,7 +27,7 @@ enum __attribute__((flag_enum,enum_extensibility(open))) EnumFlagOpen { void test() { enum Enum t0; - switch (t0) { // expected-warning{{enumeration value 'A1' not handled in switch}} + switch (t0) { // expected-warning{{enumeration value 'A1' not handled in switch}} expected-note {{add missing switch cases}} case A0: break; case 16: break; // expected-warning{{case value not in enumerated type}} } @@ -40,7 +40,7 @@ void test() { enum EnumClosed t1; - switch (t1) { // expected-warning{{enumeration value 'B1' not handled in switch}} + switch (t1) { // expected-warning{{enumeration value 'B1' not handled in switch}} expected-note {{add missing switch cases}} case B0: break; case 16: break; // expected-warning{{case value not in enumerated type}} } @@ -53,7 +53,7 @@ void test() { enum EnumOpen t2; - switch (t2) { // expected-warning{{enumeration value 'C1' not handled in switch}} + switch (t2) { // expected-warning{{enumeration value 'C1' not handled in switch}} expected-note {{add missing switch cases}} case C0: break; case 16: break; } @@ -66,7 +66,7 @@ void test() { enum EnumFlag t3; - switch (t3) { // expected-warning{{enumeration value 'D1' not handled in switch}} + switch (t3) { // expected-warning{{enumeration value 'D1' not handled in switch}} expected-note {{add missing switch cases}} case D0: break; case 9: break; case 16: break; // expected-warning{{case value not in enumerated type}} @@ -80,7 +80,7 @@ void test() { enum EnumFlagClosed t4; - switch (t4) { // expected-warning{{enumeration value 'E1' not handled in switch}} + switch (t4) { // expected-warning{{enumeration value 'E1' not handled in switch}} expected-note {{add missing switch cases}} case E0: break; case 9: break; case 16: break; // expected-warning{{case value not in enumerated type}} @@ -94,7 +94,7 @@ void test() { enum EnumFlagOpen t5; - switch (t5) { // expected-warning{{enumeration value 'F1' not handled in switch}} + switch (t5) { // expected-warning{{enumeration value 'F1' not handled in switch}} expected-note {{add missing switch cases}} case F0: break; case 9: break; case 16: break; diff --git a/clang/test/SemaCXX/scope-check.cpp b/clang/test/SemaCXX/scope-check.cpp index 9e00332985276..256f01b8af2a8 100644 --- a/clang/test/SemaCXX/scope-check.cpp +++ b/clang/test/SemaCXX/scope-check.cpp @@ -193,7 +193,7 @@ namespace PR10462 { bool recurse() { MyEnum K; - switch (K) { // expected-warning {{enumeration value 'something_invalid' not handled in switch}} + switch (K) { // expected-warning {{enumeration value 'something_invalid' not handled in switch}} expected-note {{add missing switch cases}} case something_valid: case what_am_i_thinking: // expected-error {{use of undeclared identifier}} int *X = 0; diff --git a/clang/test/SemaCXX/typo-correction.cpp b/clang/test/SemaCXX/typo-correction.cpp index 2d78f06c5d331..910e2c1186fc8 100644 --- a/clang/test/SemaCXX/typo-correction.cpp +++ b/clang/test/SemaCXX/typo-correction.cpp @@ -411,7 +411,7 @@ class Figure { void testAccess() { Figure obj; - switch (obj.type()) { // expected-warning {{enumeration values 'SQUARE', 'TRIANGLE', and 'CIRCLE' not handled in switch}} + switch (obj.type()) { // expected-warning {{enumeration values 'SQUARE', 'TRIANGLE', and 'CIRCLE' not handled in switch}} expected-note {{add missing switch cases}} case SQUARE: // expected-error-re {{use of undeclared identifier 'SQUARE'{{$}}}} case TRIANGLE: // expected-error-re {{use of undeclared identifier 'TRIANGLE'{{$}}}} case CIRCE: // expected-error-re {{use of undeclared identifier 'CIRCE'{{$}}}} diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt index b0c97f0f1e4ca..ec3307b1415ca 100644 --- a/clang/tools/CMakeLists.txt +++ b/clang/tools/CMakeLists.txt @@ -30,3 +30,6 @@ add_llvm_external_project(clang-tools-extra extra) # libclang may require clang-tidy in clang-tools-extra. add_clang_subdirectory(libclang) + +add_clang_subdirectory(clang-refactor-test) + diff --git a/clang/tools/c-index-test/c-index-test.c b/clang/tools/c-index-test/c-index-test.c index d25ae117a68aa..f3684407bf83c 100644 --- a/clang/tools/c-index-test/c-index-test.c +++ b/clang/tools/c-index-test/c-index-test.c @@ -110,6 +110,10 @@ static void describeLibclangFailure(enum CXErrorCode Err) { case CXError_ASTReadError: fprintf(stderr, "Failure: AST deserialization error occurred\n"); return; + + default: + fprintf(stderr, "Failure (other)\n"); + return; } } diff --git a/clang/tools/clang-refactor-test/CMakeLists.txt b/clang/tools/clang-refactor-test/CMakeLists.txt new file mode 100644 index 0000000000000..f742327ea3989 --- /dev/null +++ b/clang/tools/clang-refactor-test/CMakeLists.txt @@ -0,0 +1,23 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_executable(clang-refactor-test + ClangRefactorTest.cpp + ) + +if (LLVM_BUILD_STATIC) + target_link_libraries(clang-refactor-test + libclang_static + ) +else() + target_link_libraries(clang-refactor-test + libclang + clangBasic + clangFrontend + clangRewrite + clangTooling + clangToolingCore + clangToolingRefactor + ) +endif() diff --git a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp new file mode 100644 index 0000000000000..5f220e40adc2d --- /dev/null +++ b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp @@ -0,0 +1,1371 @@ +//===--- ClangRefactorTest.cpp - ------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements a clang-refactor-test tool that is used to test the +// refactoring library in Clang. +// +//===----------------------------------------------------------------------===// + +#include "clang-c/Refactor.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Frontend/CommandLineSourceLoc.h" +#include "clang/Tooling/Refactor/SymbolName.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace clang; + +namespace opts { + +static cl::OptionCategory + ClangRefactorTestOptions("clang-refactor-test common options"); + +cl::SubCommand RenameInitiateSubcommand( + "rename-initiate", "Initiate renaming in an initial translation unit"); + +cl::SubCommand RenameInitiateUSRSubcommand( + "rename-initiate-usr", + "Initiate renaming in an translation unit on a specific declaration"); + +cl::SubCommand RenameIndexedFileSubcommand( + "rename-indexed-file", + "Initiate renaming and find occurrences in an indexed file"); + +cl::SubCommand ListRefactoringActionsSubcommand("list-actions", + "Print the list of the " + "refactoring actions that can " + "be performed at the specified " + "location"); + +cl::SubCommand InitiateActionSubcommand("initiate", + "Initiate a refactoring action"); + +cl::SubCommand + PerformActionSubcommand("perform", + "Initiate and perform a refactoring action"); + +const cl::desc + AtOptionDescription("The location at which the refactoring should be " + "initiated (::)"); + +const cl::desc InRangeOptionDescription( + "The location(s) at which the refactoring should be " + "initiated (::-)"); + +const cl::desc SelectedRangeOptionDescription( + "The selected source range in which the refactoring should be " + "initiated (::-:)"); + +static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); + +namespace rename { +static cl::list AtLocation("at", AtOptionDescription, cl::Required, + cl::cat(ClangRefactorTestOptions), + cl::sub(RenameInitiateSubcommand), + cl::OneOrMore); + +static cl::opt + USR("usr", cl::desc("The USR of the declaration that should be renamed"), + cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateUSRSubcommand), + cl::Required); + +static cl::opt + NewName("new-name", cl::desc("The new name to change the symbol to."), + cl::Required, cl::cat(ClangRefactorTestOptions), + cl::sub(RenameInitiateSubcommand), + cl::sub(RenameInitiateUSRSubcommand)); + +static cl::list + IndexedNames("name", cl::desc("The names of the renamed symbols"), + cl::Required, cl::OneOrMore, cl::cat(ClangRefactorTestOptions), + cl::sub(RenameIndexedFileSubcommand)); + +static cl::list IndexedNewNames( + "new-name", cl::desc("The new name to change the symbol to."), cl::Required, + cl::OneOrMore, cl::cat(ClangRefactorTestOptions), + cl::sub(RenameIndexedFileSubcommand)); + +static cl::opt + IndexedSymbolKind("indexed-symbol-kind", + cl::desc("The kind of the indexed symbol."), cl::Optional, + cl::cat(ClangRefactorTestOptions), + cl::sub(RenameIndexedFileSubcommand)); + +static cl::opt + IndexedFileName("indexed-file", cl::desc("The name of the indexed file"), + cl::Required, cl::cat(ClangRefactorTestOptions), + cl::sub(RenameIndexedFileSubcommand)); + +static cl::list + IndexedLocations("indexed-at", + cl::desc("The location of an indexed occurrence " + "([|:]:)"), + cl::ZeroOrMore, cl::cat(ClangRefactorTestOptions), + cl::sub(RenameIndexedFileSubcommand)); + +static cl::opt AvoidTextual( + "no-textual-matches", cl::desc("Avoid searching for textual matches"), + cl::cat(ClangRefactorTestOptions), cl::sub(RenameIndexedFileSubcommand)); + +static cl::opt DumpSymbols( + "dump-symbols", cl::desc("Dump the information about the renamed symbols"), + cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateSubcommand), + cl::sub(RenameInitiateUSRSubcommand)); +} + +namespace listActions { +cl::opt AtLocation("at", AtOptionDescription, cl::Required, + cl::cat(ClangRefactorTestOptions), + cl::sub(ListRefactoringActionsSubcommand)); + +cl::opt SelectedRange("selected", SelectedRangeOptionDescription, + cl::cat(ClangRefactorTestOptions), + cl::sub(ListRefactoringActionsSubcommand)); + +cl::opt DumpRawActionType( + "dump-raw-action-type", + cl::desc("Prints the action type integer value for each listed action"), + cl::cat(ClangRefactorTestOptions), + cl::sub(ListRefactoringActionsSubcommand)); +} + +namespace initiateAndPerform { +cl::list InLocationRanges("in", cl::ZeroOrMore, + InRangeOptionDescription, + cl::cat(ClangRefactorTestOptions), + cl::sub(InitiateActionSubcommand)); + +cl::list AtLocations("at", cl::ZeroOrMore, AtOptionDescription, + cl::cat(ClangRefactorTestOptions), + cl::sub(InitiateActionSubcommand), + cl::sub(PerformActionSubcommand)); + +cl::list SelectedRanges("selected", cl::ZeroOrMore, + SelectedRangeOptionDescription, + cl::cat(ClangRefactorTestOptions), + cl::sub(InitiateActionSubcommand), + cl::sub(PerformActionSubcommand)); + +cl::opt ActionName("action", cl::Required, + cl::desc("The name of the refactoring action"), + cl::cat(ClangRefactorTestOptions), + cl::sub(InitiateActionSubcommand), + cl::sub(PerformActionSubcommand)); + +cl::opt LocationAgnostic( + "location-agnostic", + cl::desc( + "Ignore the location of initiation when verifying result consistency"), + cl::cat(ClangRefactorTestOptions), cl::sub(InitiateActionSubcommand)); + +cl::opt CandidateIndex( + "candidate", + cl::desc( + "The index of the refactoring candidate which should be performed"), + cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand)); + +cl::opt ContinuationFile( + "continuation-file", + cl::desc("The source file in which the continuation should run"), + cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand)); + +cl::opt QueryResults( + "query-results", cl::desc("The indexer query results that should be passed " + "into the continuation"), + cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand)); + +cl::opt EmitAssociatedInfo( + "emit-associated", cl::desc("Dump additional associated information"), + cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand)); +} + +cl::opt Apply( + "apply", + cl::desc( + "Apply the changes and print the modified file to standard output"), + cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand), + cl::sub(RenameInitiateSubcommand), cl::sub(RenameIndexedFileSubcommand)); + +cl::opt + Diff("diff", + cl::desc("Display the replaced text in red when -apply is specified"), + cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand), + cl::sub(RenameInitiateSubcommand), + cl::sub(RenameIndexedFileSubcommand)); + +cl::opt Context("context", cl::desc("How many lines of context should be " + "displayed when -apply is specified"), + cl::cat(ClangRefactorTestOptions), + cl::sub(PerformActionSubcommand), + cl::sub(RenameInitiateSubcommand), + cl::sub(RenameIndexedFileSubcommand)); + +static cl::opt FileName( + cl::Positional, cl::desc(""), cl::Required, + cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateSubcommand), + cl::sub(RenameInitiateUSRSubcommand), cl::sub(RenameIndexedFileSubcommand), + cl::sub(ListRefactoringActionsSubcommand), + cl::sub(InitiateActionSubcommand), cl::sub(PerformActionSubcommand)); + +static cl::opt IgnoreFilenameForInitiationTU( + "ignore-filename-for-initiation-tu", cl::Optional, + cl::cat(ClangRefactorTestOptions), cl::sub(RenameIndexedFileSubcommand)); + +static cl::list CompilerArguments( + cl::ConsumeAfter, cl::desc(""), + cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateSubcommand), + cl::sub(RenameInitiateUSRSubcommand), cl::sub(RenameIndexedFileSubcommand), + cl::sub(ListRefactoringActionsSubcommand), + cl::sub(InitiateActionSubcommand), cl::sub(PerformActionSubcommand)); + +static cl::opt ImplementationTU( + "implementation-tu", cl::desc("The name of the implementation TU"), + cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateSubcommand)); +} + +static const char *renameOccurrenceKindString(CXSymbolOccurrenceKind Kind, + bool IsLocal, + bool IsMacroExpansion) { + switch (Kind) { + case CXSymbolOccurrence_MatchingSymbol: + return IsMacroExpansion ? "macro" : IsLocal ? "rename local" : "rename"; + case CXSymbolOccurrence_MatchingSelector: + assert(!IsLocal && "Objective-C selector renames must be global"); + return IsMacroExpansion ? "selector in macro" : "selector"; + case CXSymbolOccurrence_MatchingImplicitProperty: + assert(!IsLocal); + return IsMacroExpansion ? "implicit-property in macro" + : "implicit-property"; + case CXSymbolOccurrence_MatchingCommentString: + return "comment"; + case CXSymbolOccurrence_MatchingDocCommentString: + return "documentation"; + case CXSymbolOccurrence_MatchingFilename: + return "filename"; + case CXSymbolOccurrence_ExtractedDeclaration: + return "extracted-decl"; + case CXSymbolOccurrence_ExtractedDeclaration_Reference: + return "extracted-decl-ref"; + } +} + +static int apply(ArrayRef Replacements, + StringRef Filename) { + // Assume that the replacements are sorted. + auto Result = MemoryBuffer::getFile(Filename); + if (!Result) { + errs() << "Failed to open " << Filename << "\n"; + return 1; + } + + raw_ostream &OS = outs(); + + int Context = opts::Context; + + MemoryBuffer &Buffer = **Result; + std::vector>> + Lines; + for (auto I = line_iterator(Buffer, /*SkipBlanks=*/false), + E = line_iterator(); + I != E; ++I) + Lines.push_back( + std::make_pair(*I, std::vector())); + unsigned FlushedLine = 1; + auto FlushUntil = [&](unsigned Line) { + // Adjust the first flushed line if needed when printing in context mode. + if (FlushedLine == 1 && Context) + FlushedLine = std::max(int(Line) - Context, 1); + for (; FlushedLine < Line; ++FlushedLine) { + const auto &Line = Lines[FlushedLine - 1]; + if (Line.second.empty()) { + OS << Line.first << "\n"; + continue; + } + + unsigned I = 0; + for (const CXRefactoringReplacement &Replacement : Line.second) { + OS << Line.first.substr(I, Replacement.Range.Begin.Column - 1 - I); + if (opts::Diff) { + OS.changeColor(raw_ostream::RED, false, true); + OS << Line.first.substr(Replacement.Range.Begin.Column - 1, + Replacement.Range.End.Column - 1 - + (Replacement.Range.Begin.Column - 1)); + } + OS.changeColor(raw_ostream::GREEN); + OS << clang_getCString(Replacement.ReplacementString); + OS.resetColor(); + I = Replacement.Range.End.Column - 1; + } + OS << Line.first.substr(I); + if (I < Line.first.size() || opts::Diff) + OS << "\n"; + } + }; + + int EndLineMax = 0; + for (const CXRefactoringReplacement &Replacement : Replacements) { + EndLineMax = std::max(int(Replacement.Range.End.Line), EndLineMax); + unsigned StartingLine = Replacement.Range.Begin.Line; + FlushUntil(StartingLine); + if (Replacement.Range.End.Line == StartingLine) { + Lines[StartingLine - 1].second.push_back(Replacement); + continue; + } + // Multi-line replacements have to be split + for (unsigned I = StartingLine; I <= Replacement.Range.End.Line; ++I) { + CXRefactoringReplacement NewReplacement; + if (I == Replacement.Range.End.Line) + NewReplacement.ReplacementString = Replacement.ReplacementString; + else + // FIXME: This is a hack to workaround the fact that the API doesn't + // provide a way to create a null string. This should be fixed when + // upstreaming. + NewReplacement.ReplacementString = {0, 0}; + NewReplacement.Range.Begin.Line = I; + NewReplacement.Range.Begin.Column = + I == StartingLine ? Replacement.Range.Begin.Column : 1; + NewReplacement.Range.End.Line = I; + NewReplacement.Range.End.Column = I == Replacement.Range.End.Line + ? Replacement.Range.End.Column + : Lines[I - 1].first.size() + 1; + NewReplacement.AssociatedData = nullptr; + Lines[I - 1].second.push_back(NewReplacement); + } + } + FlushUntil(Context ? std::min(int(Lines.size()), EndLineMax + Context) + 1 + : Lines.size() + 2); + // Print out a dividor when printing in the context mode. + if (Context) { + for (int I = 0; I < 80; ++I) + OS << '-'; + OS << "\n"; + } + return 0; +} + +/// Converts the given renamed \p Occurrence into a string value that represents +/// this occurrence. +static std::string +occurrenceToString(const CXSymbolOccurrence &Occurrence, bool IsLocal, + const tooling::SymbolName &NewName, + const tooling::SymbolName &ExpectedReplacementStrings, + StringRef Filename) { + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << renameOccurrenceKindString(Occurrence.Kind, IsLocal, + Occurrence.IsMacroExpansion) + << ' '; + if (!Filename.empty()) + OS << '"' << Filename << "\" "; + + bool FirstRange = true; + for (unsigned J = 0; J != Occurrence.NumNamePieces; ++J) { + if (!FirstRange) // TODO + OS << ", "; + + // Print the replacement string if it doesn't match the expected string. + if (NewName[J] != ExpectedReplacementStrings[J]) + OS << '"' << NewName[J] << "\" "; + + CXFileRange Range = Occurrence.NamePieces[J]; + OS << Range.Begin.Line << ":" << Range.Begin.Column << " -> " + << Range.End.Line << ":" << Range.End.Column; + FirstRange = false; + } + return OS.str(); +} + +static CXCursorKind +renameIndexedOccurrenceKindStringToKind(StringRef Str, CXCursorKind Default) { + return llvm::StringSwitch(Str) + .Case("objc-im", CXCursor_ObjCInstanceMethodDecl) + .Case("objc-cm", CXCursor_ObjCClassMethodDecl) + .Case("objc-message", CXCursor_ObjCMessageExpr) + .Case("include", CXCursor_InclusionDirective) + .Default(Default); +} + +/// Parses the string passed as the -indexed-at argument. +std::pair +parseIndexedOccurrence(StringRef IndexedOccurrence, + CXCursorKind DefaultCursorKind) { + StringRef LineColumnLoc = IndexedOccurrence; + CXCursorKind Kind = DefaultCursorKind; + unsigned SymbolIndex = 0; + if (LineColumnLoc.count(':') > 1) { + std::pair Split = LineColumnLoc.split(':'); + // The first value is either the kind or the symbol index. + if (Split.first.getAsInteger(10, SymbolIndex)) { + if (Split.second.count(':') > 1) { + std::pair SecondSplit = Split.second.split(':'); + if (SecondSplit.first.getAsInteger(10, SymbolIndex)) + assert(false && "expected symbol index"); + Split.second = SecondSplit.second; + } + Kind = renameIndexedOccurrenceKindStringToKind(Split.first, Kind); + } + LineColumnLoc = Split.second; + } + auto Loc = std::string("-:") + LineColumnLoc.str(); + auto Location = ParsedSourceLocation::FromString(Loc); + return std::make_pair( + CXRenamedIndexedSymbolLocation{{Location.Line, Location.Column}, Kind}, + SymbolIndex); +} + +/// Compare the produced occurrences to the expected occurrences that were +/// gathered at the first location. Return true if the occurrences are +/// different. +static bool compareOccurrences(ArrayRef ExpectedReplacements, + CXSymbolOccurrencesResult Occurrences, + bool IsLocal, + const tooling::SymbolName &NewSymbolName, + bool PrintFilenames) { + unsigned NumFiles = clang_SymbolOccurrences_getNumFiles(Occurrences); + size_t ExpectedReplacementIndex = 0; + for (unsigned FileIndex = 0; FileIndex < NumFiles; ++FileIndex) { + CXSymbolOccurrencesInFile FileResult; + clang_SymbolOccurrences_getOccurrencesForFile(Occurrences, FileIndex, + &FileResult); + StringRef Filename = + PrintFilenames ? clang_getCString(FileResult.Filename) : ""; + + for (unsigned I = 0; I != FileResult.NumOccurrences; ++I) { + std::string Replacement = + occurrenceToString(FileResult.Occurrences[I], IsLocal, NewSymbolName, + NewSymbolName, Filename); + if (ExpectedReplacementIndex >= ExpectedReplacements.size() || + Replacement != ExpectedReplacements[ExpectedReplacementIndex]) + return true; + ++ExpectedReplacementIndex; + } + } + // Verify that all of the expected replacements were checked. + return ExpectedReplacementIndex != ExpectedReplacements.size(); +} + +struct ImplementationTUWrapper { + CXTranslationUnit TU = nullptr; + + ImplementationTUWrapper() {} + ~ImplementationTUWrapper() { clang_disposeTranslationUnit(TU); } + + ImplementationTUWrapper(const ImplementationTUWrapper &) = delete; + ImplementationTUWrapper &operator=(const ImplementationTUWrapper &) = delete; + + bool load(CXRefactoringAction Action, CXIndex CIdx, + ArrayRef Args); +}; + +bool ImplementationTUWrapper::load(CXRefactoringAction Action, CXIndex CIdx, + ArrayRef Args) { + if (!clang_RefactoringAction_requiresImplementationTU(Action)) + return false; + CXString USR = + clang_RefactoringAction_getUSRThatRequiresImplementationTU(Action); + outs() << "Implementation TU USR: '" << clang_getCString(USR) << "'\n"; + clang_disposeString(USR); + if (!TU) { + CXErrorCode Err = clang_parseTranslationUnit2( + CIdx, opts::ImplementationTU.c_str(), Args.data(), Args.size(), 0, 0, + CXTranslationUnit_KeepGoing, &TU); + if (Err != CXError_Success) { + errs() << "error: failed to load implementation TU '" + << opts::ImplementationTU << "'\n"; + return true; + } + } + CXErrorCode Err = clang_RefactoringAction_addImplementationTU(Action, TU); + if (Err != CXError_Success) { + errs() << "error: failed to add implementation TU '" + << opts::ImplementationTU << "'\n"; + return true; + } + return false; +} + +static bool reportNewNameError(CXErrorCode Err) { + std::string NewName = opts::RenameIndexedFileSubcommand + ? opts::rename::IndexedNewNames[0] + : opts::rename::NewName; + if (Err == CXError_RefactoringNameSizeMismatch) + errs() << "error: the number of strings in the new name '" << NewName + << "' doesn't match the the number of strings in the old name\n"; + else if (Err == CXError_RefactoringNameInvalid) + errs() << "error: invalid new name '" << NewName << "'\n"; + else + return true; + return false; +} + +int rename(CXTranslationUnit TU, CXIndex CIdx, ArrayRef Args) { + assert(!opts::RenameIndexedFileSubcommand); + // Contains the renamed source replacements for the first location. It is + // compared to replacements from follow-up renames to ensure that all renames + // give the same result. + std::vector ExpectedReplacements; + // Should we print out the filenames. False by default, but true when multiple + // files are modified. + bool PrintFilenames = false; + ImplementationTUWrapper ImplementationTU; + + auto RenameAt = [&](const ParsedSourceLocation &Location, + const std::string &USR) -> int { + CXRefactoringAction RenamingAction; + CXErrorCode Err; + CXDiagnosticSet Diags = nullptr; + if (USR.empty()) { + CXSourceLocation Loc = + clang_getLocation(TU, clang_getFile(TU, Location.FileName.c_str()), + Location.Line, Location.Column); + Err = clang_Refactoring_initiateAction( + TU, Loc, clang_getNullRange(), CXRefactor_Rename, + /*Options=*/nullptr, &RenamingAction, &Diags); + } else { + Err = clang_Refactoring_initiateActionOnDecl( + TU, USR.c_str(), CXRefactor_Rename, /*Options=*/nullptr, + &RenamingAction, nullptr); + } + if (Err != CXError_Success) { + errs() << "error: could not rename symbol " + << (USR.empty() ? "at the given location\n" + : "with the given USR\n"); + if (USR.empty()) { + unsigned NumDiags = clang_getNumDiagnosticsInSet(Diags); + for (unsigned DiagID = 0; DiagID < NumDiags; ++DiagID) { + CXDiagnostic Diag = clang_getDiagnosticInSet(Diags, DiagID); + CXString Spelling = clang_getDiagnosticSpelling(Diag); + errs() << clang_getCString(Spelling) << "\n"; + clang_disposeString(Spelling); + } + } + clang_disposeDiagnosticSet(Diags); + return 1; + } + clang_disposeDiagnosticSet(Diags); + + if (ImplementationTU.load(RenamingAction, CIdx, Args)) + return 1; + + Err = clang_Refactoring_initiateRenamingOperation(RenamingAction); + if (Err != CXError_Success) { + errs() << "error: failed to initiate the renaming operation!\n"; + return 1; + } + + bool IsLocal = clang_RefactoringAction_getInitiatedActionType( + RenamingAction) == CXRefactor_Rename_Local; + + unsigned NumSymbols = clang_RenamingOperation_getNumSymbols(RenamingAction); + if (opts::rename::DumpSymbols) { + outs() << "Renaming " << NumSymbols << " symbols\n"; + for (unsigned I = 0; I < NumSymbols; ++I) { + CXString USR = + clang_RenamingOperation_getUSRForSymbol(RenamingAction, I); + outs() << "'" << clang_getCString(USR) << "'\n"; + clang_disposeString(USR); + } + } + + CXSymbolOccurrencesResult Occurrences; + Occurrences = clang_Refactoring_findSymbolOccurrencesInInitiationTU( + RenamingAction, Args.data(), Args.size(), 0, 0); + + clang_RefactoringAction_dispose(RenamingAction); + + // FIXME: This is a hack + LangOptions LangOpts; + LangOpts.ObjC1 = true; + tooling::SymbolName NewSymbolName(opts::rename::NewName, LangOpts); + + if (ExpectedReplacements.empty()) { + if (opts::Apply) { + // FIXME: support --apply. + } + + unsigned NumFiles = clang_SymbolOccurrences_getNumFiles(Occurrences); + if (NumFiles > 1) + PrintFilenames = true; + // Convert the occurrences to strings + for (unsigned FileIndex = 0; FileIndex < NumFiles; ++FileIndex) { + CXSymbolOccurrencesInFile FileResult; + clang_SymbolOccurrences_getOccurrencesForFile(Occurrences, FileIndex, + &FileResult); + StringRef Filename = + PrintFilenames ? clang_getCString(FileResult.Filename) : ""; + for (unsigned I = 0; I != FileResult.NumOccurrences; ++I) + ExpectedReplacements.push_back( + occurrenceToString(FileResult.Occurrences[I], IsLocal, + NewSymbolName, NewSymbolName, Filename)); + } + clang_SymbolOccurrences_dispose(Occurrences); + return 0; + } + // Compare the produced occurrences to the expected occurrences that were + // gathered at the first location. + bool AreOccurrencesDifferent = + compareOccurrences(ExpectedReplacements, Occurrences, IsLocal, + NewSymbolName, PrintFilenames); + clang_SymbolOccurrences_dispose(Occurrences); + if (!AreOccurrencesDifferent) + return 0; + errs() << "error: occurrences for a rename at " << Location.FileName << ":" + << Location.Line << ":" << Location.Column + << " differ to occurrences from the rename at the first location!\n"; + return 1; + }; + + std::vector ParsedLocations; + for (const auto &I : enumerate(opts::rename::AtLocation)) { + auto Location = ParsedSourceLocation::FromString(I.value()); + if (Location.FileName.empty()) { + errs() + << "error: The -at option must use the format\n"; + return 1; + } + ParsedLocations.push_back(Location); + } + + if (opts::RenameInitiateUSRSubcommand) { + if (RenameAt(ParsedSourceLocation(), opts::rename::USR)) + return 1; + } else { + assert(!ParsedLocations.empty() && "No -at locations"); + + for (const auto &Location : ParsedLocations) { + if (RenameAt(Location, "")) + return 1; + } + } + + // Print the produced renamed replacements + if (opts::Apply) + return 0; + for (const auto &Replacement : ExpectedReplacements) + outs() << Replacement << "\n"; + if (ExpectedReplacements.empty()) + outs() << "no replacements found\n"; + return 0; +} + +int renameIndexedFile(CXIndex CIdx, ArrayRef Args) { + assert(opts::RenameIndexedFileSubcommand); + + // Compute the number of symbols. + unsigned NumSymbols = opts::rename::IndexedNames.size(); + + // Get the occurrences of a symbol. + CXCursorKind DefaultCursorKind = renameIndexedOccurrenceKindStringToKind( + opts::rename::IndexedSymbolKind, CXCursor_NotImplemented); + std::vector> IndexedOccurrences( + NumSymbols, std::vector()); + for (const auto &IndexedOccurrence : opts::rename::IndexedLocations) { + auto Occurrence = + parseIndexedOccurrence(IndexedOccurrence, DefaultCursorKind); + unsigned SymbolIndex = Occurrence.second; + assert(SymbolIndex < IndexedOccurrences.size() && "Invalid symbol index"); + IndexedOccurrences[SymbolIndex].push_back(CXIndexedSymbolLocation{ + Occurrence.first.Location, Occurrence.first.CursorKind}); + } + + // Create the indexed symbols. + std::vector IndexedSymbols; + for (const auto &I : llvm::enumerate(IndexedOccurrences)) { + const auto &Occurrences = I.value(); + const char *Name = + opts::rename::IndexedNames[opts::rename::IndexedNames.size() < 2 + ? 0 + : I.index()] + .c_str(); + IndexedSymbols.push_back({Occurrences.data(), (unsigned)Occurrences.size(), + DefaultCursorKind, Name}); + } + + CXRefactoringOptionSet Options = nullptr; + if (opts::rename::AvoidTextual) { + Options = clang_RefactoringOptionSet_create(); + clang_RefactoringOptionSet_add(Options, + CXRefactorOption_AvoidTextualMatches); + } + + CXSymbolOccurrencesResult Occurrences; + CXErrorCode Err = clang_Refactoring_findSymbolOccurrencesInIndexedFile( + IndexedSymbols.data(), IndexedSymbols.size(), CIdx, + opts::rename::IndexedFileName.c_str(), Args.data(), Args.size(), 0, 0, + Options, &Occurrences); + if (Err != CXError_Success) { + if (reportNewNameError(Err)) + errs() << "error: failed to perform indexed file rename\n"; + return 1; + } + + if (Options) + clang_RefactoringOptionSet_dispose(Options); + + // Should we print out the filenames. False by default, but true when multiple + // files are modified. + bool PrintFilenames = false; + unsigned NumFiles = clang_SymbolOccurrences_getNumFiles(Occurrences); + if (NumFiles > 1) + PrintFilenames = true; + + LangOptions LangOpts; + LangOpts.ObjC1 = true; + tooling::SymbolName ExpectedReplacementStrings( + opts::rename::IndexedNewNames[0], LangOpts); + + // Print the occurrences. + bool HasReplacements = false; + for (unsigned FileIndex = 0; FileIndex < NumFiles; ++FileIndex) { + CXSymbolOccurrencesInFile FileResult; + clang_SymbolOccurrences_getOccurrencesForFile(Occurrences, FileIndex, + &FileResult); + StringRef Filename = + PrintFilenames ? clang_getCString(FileResult.Filename) : ""; + HasReplacements = FileResult.NumOccurrences; + for (unsigned I = 0; I != FileResult.NumOccurrences; ++I) { + unsigned SymbolIndex = FileResult.Occurrences[I].SymbolIndex; + const char *NewName = + opts::rename::IndexedNewNames[opts::rename::IndexedNewNames.size() < 2 + ? 0 + : SymbolIndex] + .c_str(); + LangOptions LangOpts; + LangOpts.ObjC1 = true; + tooling::SymbolName NewSymbolName(NewName, LangOpts); + + outs() << occurrenceToString(FileResult.Occurrences[I], /*IsLocal*/ false, + NewSymbolName, ExpectedReplacementStrings, + Filename) + << "\n"; + } + } + if (!HasReplacements) + outs() << "no replacements found\n"; + clang_SymbolOccurrences_dispose(Occurrences); + return 0; +} + +/// Returns the last column number of a line in a file. +static unsigned lastColumnForFile(StringRef Filename, unsigned LineNo) { + auto Buf = llvm::MemoryBuffer::getFile(Filename); + if (!Buf) + return 0; + unsigned LineCount = 1; + for (llvm::line_iterator Lines(**Buf, /*SkipBlanks=*/false); + !Lines.is_at_end(); ++Lines, ++LineCount) { + if (LineNo == LineCount) + return Lines->size() + 1; + } + return 0; +} + +struct ParsedSourceLineRange : ParsedSourceLocation { + unsigned MaxColumn; + + ParsedSourceLineRange() {} + ParsedSourceLineRange(const ParsedSourceLocation &Loc) + : ParsedSourceLocation(Loc), MaxColumn(Loc.Column) {} + + static Optional FromString(StringRef Str) { + std::pair RangeSplit = Str.rsplit('-'); + auto PSL = ParsedSourceLocation::FromString(RangeSplit.first); + ParsedSourceLineRange Result; + Result.FileName = std::move(PSL.FileName); + Result.Line = PSL.Line; + Result.Column = PSL.Column; + if (Result.FileName.empty()) + return None; + if (RangeSplit.second == "end") + Result.MaxColumn = lastColumnForFile(Result.FileName, Result.Line); + else if (RangeSplit.second.getAsInteger(10, Result.MaxColumn)) + return None; + if (Result.MaxColumn < Result.Column) + return None; + return Result; + } +}; + +struct ParsedSourceRange { + ParsedSourceLocation Begin, End; + + ParsedSourceRange(const ParsedSourceLocation &Begin, + const ParsedSourceLocation &End) + : Begin(Begin), End(End) {} + + static Optional FromString(StringRef Str) { + std::pair RangeSplit = Str.rsplit('-'); + auto Begin = ParsedSourceLocation::FromString(RangeSplit.first); + if (Begin.FileName.empty()) + return None; + std::string EndString = Begin.FileName + ":" + RangeSplit.second.str(); + auto End = ParsedSourceLocation::FromString(EndString); + if (End.FileName.empty()) + return None; + return ParsedSourceRange(Begin, End); + } +}; + +int listRefactoringActions(CXTranslationUnit TU) { + auto Location = + ParsedSourceLocation::FromString(opts::listActions::AtLocation); + if (Location.FileName.empty()) { + errs() << "error: The -at option must use the format\n"; + return 1; + } + CXSourceRange Range; + if (!opts::listActions::SelectedRange.empty()) { + auto SelectionRange = + ParsedSourceRange::FromString(opts::listActions::SelectedRange); + if (!SelectionRange) { + errs() << "error: The -selected option must use the " + " format\n"; + return 1; + } + auto Begin = SelectionRange.getValue().Begin; + auto End = SelectionRange.getValue().End; + CXFile File = clang_getFile(TU, Begin.FileName.c_str()); + Range = + clang_getRange(clang_getLocation(TU, File, Begin.Line, Begin.Column), + clang_getLocation(TU, File, End.Line, End.Column)); + } else + Range = clang_getNullRange(); + CXSourceLocation Loc = + clang_getLocation(TU, clang_getFile(TU, Location.FileName.c_str()), + Location.Line, Location.Column); + CXRefactoringActionSet ActionSet; + CXRefactoringActionSetWithDiagnostics FailedActionSet; + CXErrorCode Err = + clang_Refactoring_findActionsWithInitiationFailureDiagnosicsAt( + TU, Loc, Range, /*Options=*/nullptr, &ActionSet, &FailedActionSet); + if (FailedActionSet.NumActions) { + errs() << "Failed to initiate " << FailedActionSet.NumActions + << " actions because:\n"; + for (unsigned I = 0; I < FailedActionSet.NumActions; ++I) { + errs() << clang_getCString(clang_RefactoringActionType_getName( + FailedActionSet.Actions[I].Action)) + << ":"; + CXDiagnosticSet Diags = FailedActionSet.Actions[I].Diagnostics; + unsigned NumDiags = clang_getNumDiagnosticsInSet(Diags); + for (unsigned DiagID = 0; DiagID < NumDiags; ++DiagID) { + CXDiagnostic Diag = clang_getDiagnosticInSet(Diags, DiagID); + CXString Spelling = clang_getDiagnosticSpelling(Diag); + errs() << ' ' << clang_getCString(Spelling); + clang_disposeString(Spelling); + } + errs() << "\n"; + } + } + if (Err == CXError_RefactoringActionUnavailable) + errs() << "No refactoring actions are available at the given location\n"; + if (Err != CXError_Success) + return 1; + // Print the list of refactoring actions. + outs() << "Found " << ActionSet.NumActions << " actions:\n"; + for (unsigned I = 0; I < ActionSet.NumActions; ++I) { + outs() << clang_getCString( + clang_RefactoringActionType_getName(ActionSet.Actions[I])); + if (opts::listActions::DumpRawActionType) + outs() << "(" << ActionSet.Actions[I] << ")"; + outs() << "\n"; + } + clang_RefactoringActionSet_dispose(&ActionSet); + clang_RefactoringActionSetWithDiagnostics_dispose(&FailedActionSet); + return 0; +} + +static std::string locationToString(CXSourceLocation Loc) { + unsigned Line, Column; + clang_getFileLocation(Loc, nullptr, &Line, &Column, nullptr); + std::string S; + llvm::raw_string_ostream OS(S); + OS << Line << ':' << Column; + return OS.str(); +} + +static std::string rangeToString(CXSourceRange Range) { + return locationToString(clang_getRangeStart(Range)) + " -> " + + locationToString(clang_getRangeEnd(Range)); +} + +static std::string +refactoringCandidatesToString(CXRefactoringCandidateSet Candidates) { + std::string Results = "with multiple candidates:"; + for (unsigned I = 0; I < Candidates.NumCandidates; ++I) { + Results += "\n"; + Results += clang_getCString(Candidates.Candidates[I].Description); + } + return Results; +} + +static void printEscaped(StringRef Str, raw_ostream &OS) { + size_t Pos = Str.find('\n'); + OS << Str.substr(0, Pos); + if (Pos == StringRef::npos) + return; + OS << "\\n"; + printEscaped(Str.substr(Pos + 1), OS); +} + +bool printRefactoringReplacements( + CXRefactoringResult Result, CXRefactoringContinuation Continuation, + CXRefactoringContinuation CurrentContinuation) { + CXRefactoringReplacements Replacements = + clang_RefactoringResult_getSourceReplacements(Result); + if (Replacements.NumFileReplacementSets == 0) { + if (CurrentContinuation) + return false; + errs() << "error: no replacements produced!\n"; + return true; + } + // Print out the produced results. + for (unsigned FileIndex = 0; FileIndex < Replacements.NumFileReplacementSets; + ++FileIndex) { + const CXRefactoringFileReplacementSet &FileSet = + Replacements.FileReplacementSets[FileIndex]; + if (opts::Apply) { + apply(llvm::makeArrayRef(FileSet.Replacements, FileSet.NumReplacements), + clang_getCString(FileSet.Filename)); + continue; + } + for (unsigned I = 0; I < FileSet.NumReplacements; ++I) { + const CXRefactoringReplacement &Replacement = FileSet.Replacements[I]; + + if (Continuation) { + // Always print the filenames in with continuations. + outs() << '"' << clang_getCString(FileSet.Filename) << "\" "; + } + outs() << '"'; + printEscaped(clang_getCString(Replacement.ReplacementString), outs()); + outs() << "\" "; + CXFileRange Range = Replacement.Range; + outs() << Range.Begin.Line << ":" << Range.Begin.Column << " -> " + << Range.End.Line << ":" << Range.End.Column; + if (opts::initiateAndPerform::EmitAssociatedInfo) { + CXRefactoringReplacementAssociatedSymbolOccurrences Info = + clang_RefactoringReplacement_getAssociatedSymbolOccurrences( + Replacement); + for (const CXSymbolOccurrence &SymbolOccurrence : + llvm::makeArrayRef(Info.AssociatedSymbolOccurrences, + Info.NumAssociatedSymbolOccurrences)) { + outs() << " [Symbol " << renameOccurrenceKindString( + SymbolOccurrence.Kind, /*IsLocal*/ false, + SymbolOccurrence.IsMacroExpansion) + << ' ' << SymbolOccurrence.SymbolIndex; + for (const auto &Piece : + llvm::makeArrayRef(SymbolOccurrence.NamePieces, + SymbolOccurrence.NumNamePieces)) { + outs() << ' ' << Piece.Begin.Line << ":" << Piece.Begin.Column + << " -> " << Piece.End.Line << ":" << Piece.End.Column; + } + outs() << ']'; + } + } + outs() << "\n"; + } + } + return false; +} + +/// Returns the last column number of a line in a file. +static std::string queryResultsForFile(StringRef Filename, StringRef Name, + StringRef FileSubstitution) { + auto Buf = llvm::MemoryBuffer::getFile(Filename); + if (!Buf) + return ""; + StringRef Buffer = (*Buf)->getBuffer(); + std::string Label = Name.str() + ":"; + size_t I = Buffer.find(Label); + if (I == StringRef::npos) + return ""; + I = I + Label.size(); + auto Result = Buffer.substr(I, Buffer.find('\n', I) - I); + std::string Sub1 = llvm::Regex("%s").sub(FileSubstitution, Result); + return llvm::Regex("%S").sub(llvm::sys::path::parent_path(FileSubstitution), + Sub1); +} + +static Optional> +findSelectionLocInSource(StringRef Buffer, StringRef Label) { + size_t I = Buffer.find(Label); + if (I == StringRef::npos) + return None; + I = I + Label.size(); + auto LocParts = + Buffer.substr(I, Buffer.find_first_of("\n/", I) - I).trim().split(":"); + unsigned CurrentLine = Buffer.take_front(I).count('\n') + 1; + if (LocParts.second.empty()) + return None; + StringRef LineString = LocParts.first; + unsigned Line, Column; + enum ExprKind { Literal, Add, Sub }; + ExprKind Expr = LineString.startswith("+") + ? Add + : LineString.startswith("-") ? Sub : Literal; + if (LineString.drop_front(Expr != Literal ? 1 : 0).getAsInteger(10, Line)) + return None; + if (Expr == Add) + Line += CurrentLine; + else if (Expr == Sub) + Line = CurrentLine - Line; + if (LocParts.second.getAsInteger(10, Column)) + return None; + return std::make_pair(Line, Column); +} + +static Optional selectionLocForFile(StringRef Filename, + StringRef Name) { + auto Buf = llvm::MemoryBuffer::getFile(Filename); + if (!Buf) + return None; + + StringRef Buffer = (*Buf)->getBuffer(); + std::string Label = Name.str() + ":"; + auto Start = findSelectionLocInSource(Buffer, Label); + if (!Start) + return None; + // Create the resulting source location. + // FIXME: Parse can be avoided. + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << Filename << ":" << Start->first << ":" << Start->second; + return ParsedSourceLocation::FromString(OS.str()); +} + +static Optional selectionRangeForFile(StringRef Filename, + StringRef Name) { + auto Buf = llvm::MemoryBuffer::getFile(Filename); + if (!Buf) + return None; + + StringRef Buffer = (*Buf)->getBuffer(); + std::string BeginLabel = Name.str() + "-begin:"; + std::string EndLabel = Name.str() + "-end:"; + auto Start = findSelectionLocInSource(Buffer, BeginLabel); + auto End = findSelectionLocInSource(Buffer, EndLabel); + if (!Start || !End) + return None; + // Create the resulting source range. + // FIXME: Parse can be avoided. + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << Filename << ":" << Start->first << ":" << Start->second << "-" + << End->first << ":" << End->second; + return ParsedSourceRange::FromString(OS.str()); +} + +bool performOperation(CXRefactoringAction Action, ArrayRef Args, + CXIndex CIdx) { + if (opts::initiateAndPerform::CandidateIndex.getNumOccurrences()) { + if (clang_RefactoringAction_selectRefactoringCandidate( + Action, opts::initiateAndPerform::CandidateIndex)) { + errs() << "error: failed to select the refactoring candidate!\n"; + return true; + } + } + CXRefactoringOptionSet Options = nullptr; + CXString FailureReason; + CXRefactoringResult Result = clang_Refactoring_performOperation( + Action, Args.data(), Args.size(), nullptr, 0, Options, &FailureReason); + if (!Result) { + errs() << "error: failed to perform the refactoring operation"; + if (const char *Reason = clang_getCString(FailureReason)) + errs() << " (" << Reason << ')'; + errs() << "!\n"; + clang_disposeString(FailureReason); + return true; + } + CXRefactoringContinuation Continuation = + clang_RefactoringResult_getContinuation(Result); + bool AreReplacementsInvalid = + printRefactoringReplacements(Result, Continuation, Continuation); + clang_RefactoringResult_dispose(Result); + if (AreReplacementsInvalid) { + clang_RefactoringContinuation_dispose(Continuation); + return true; + } + if (!Continuation) + return false; + assert(clang_RefactoringContinuation_getNumIndexerQueries(Continuation) != + 0 && + "Missing indexer queries?"); + std::string QueryResults = queryResultsForFile( + opts::FileName, opts::initiateAndPerform::QueryResults, + /*FileSubstitution=*/opts::initiateAndPerform::ContinuationFile); + clang_RefactoringContinuation_loadSerializedIndexerQueryResults( + Continuation, /*Source=*/QueryResults.c_str()); + clang_RefactoringContinuation_finalizeEvaluationInInitationTU(Continuation); + // Load the continuation TU. + CXTranslationUnit ContinuationTU; + CXErrorCode Err = clang_parseTranslationUnit2( + CIdx, opts::initiateAndPerform::ContinuationFile.c_str(), Args.data(), + Args.size(), 0, 0, CXTranslationUnit_KeepGoing, &ContinuationTU); + if (Err != CXError_Success) { + errs() << "error: failed to load '" + << opts::initiateAndPerform::ContinuationFile.c_str() << "'\n"; + clang_RefactoringContinuation_dispose(Continuation); + return true; + } + Result = clang_RefactoringContinuation_continueOperationInTU( + Continuation, ContinuationTU, &FailureReason); + if (!Result) { + errs() << "error: failed to perform the refactoring continuation"; + if (const char *Reason = clang_getCString(FailureReason)) + errs() << " (" << Reason << ')'; + errs() << "!\n"; + clang_disposeString(FailureReason); + clang_disposeTranslationUnit(ContinuationTU); + clang_RefactoringContinuation_dispose(Continuation); + return true; + } + // FIXME: Continuations can be chained in the future. + AreReplacementsInvalid = + printRefactoringReplacements(Result, Continuation, nullptr); + clang_RefactoringResult_dispose(Result); + clang_disposeTranslationUnit(ContinuationTU); + clang_RefactoringContinuation_dispose(Continuation); + return AreReplacementsInvalid; +} + +int initiateAndPerformAction(CXTranslationUnit TU, ArrayRef Args, + CXIndex CIdx) { + std::vector Ranges; + std::vector SelectionRanges; + for (const auto &Range : opts::initiateAndPerform::InLocationRanges) { + auto ParsedLineRange = ParsedSourceLineRange::FromString(Range); + if (!ParsedLineRange) { + errs() + << "error: The -in option must use the " + "format\n"; + return 1; + } + Ranges.push_back(ParsedLineRange.getValue()); + } + for (const auto &Range : opts::initiateAndPerform::AtLocations) { + if (!StringRef(Range).contains(':')) { + auto ParsedLocation = selectionLocForFile(opts::FileName, Range); + if (!ParsedLocation) { + errs() << "error: The -at option must use the " + "format\n"; + return 1; + } + Ranges.push_back(*ParsedLocation); + continue; + } + // TODO: Remove old location in arguments in favour of new testing + // locations. + auto ParsedLocation = ParsedSourceLocation::FromString(Range); + if (ParsedLocation.FileName.empty()) { + errs() << "error: The -at option must use the " + "format\n"; + return 1; + } + Ranges.push_back(ParsedLocation); + } + for (const auto &Range : opts::initiateAndPerform::SelectedRanges) { + auto ParsedRange = StringRef(Range).contains(':') + ? ParsedSourceRange::FromString(Range) + : selectionRangeForFile(opts::FileName, Range); + if (!ParsedRange) { + errs() << "error: The -selected option must use the " + " format or refer to the name of " + "the selection specifier in the source\n"; + return 1; + } + SelectionRanges.push_back(ParsedRange.getValue()); + } + if (Ranges.empty() && SelectionRanges.empty()) { + errs() << "error: -in or -at options must be specified at least once!"; + return 1; + } + if (!Ranges.empty() && !SelectionRanges.empty()) { + errs() << "error: -in or -at options can't be used with -selected!"; + return 1; + } + + auto ActionTypeOrNone = StringSwitch>( + opts::initiateAndPerform::ActionName) +#define REFACTORING_OPERATION_ACTION(Name, Spelling, Command) \ + .Case(Command, CXRefactor_##Name) +#define REFACTORING_OPERATION_SUB_ACTION(Name, Parent, Spelling, Command) \ + .Case(Command, CXRefactor_##Parent##_##Name) +#include "clang/Tooling/Refactor/RefactoringActions.def" + .Default(None); + if (!ActionTypeOrNone) { + errs() << "error: invalid action '" << opts::initiateAndPerform::ActionName + << "'\n"; + return 1; + } + CXRefactoringActionType ActionType = *ActionTypeOrNone; + + Optional Initiated; + Optional InitiationFailureReason; + Optional LocationCandidateInformation; + auto InitiateAndPerform = + [&](const ParsedSourceLocation &Location, unsigned Column, + Optional SelectionRange = None) -> bool { + CXSourceLocation Loc = + clang_getLocation(TU, clang_getFile(TU, Location.FileName.c_str()), + Location.Line, Column); + CXSourceRange Range; + if (SelectionRange) { + auto Begin = SelectionRange.getValue().Begin; + auto End = SelectionRange.getValue().End; + CXFile File = clang_getFile(TU, Begin.FileName.c_str()); + Range = + clang_getRange(clang_getLocation(TU, File, Begin.Line, Begin.Column), + clang_getLocation(TU, File, End.Line, End.Column)); + } else + Range = clang_getNullRange(); + CXRefactoringAction Action; + CXString FailureReason; + CXErrorCode Err = clang_Refactoring_initiateActionAt( + TU, Loc, Range, ActionType, /*Options=*/nullptr, &Action, + &FailureReason); + std::string ReasonString; + if (const char *Reason = clang_getCString(FailureReason)) + ReasonString = Reason; + clang_disposeString(FailureReason); + if (InitiationFailureReason.hasValue() && + InitiationFailureReason.getValue() != ReasonString) { + errs() << "error: inconsistent results in a single action range!\n"; + return true; + } + InitiationFailureReason = std::move(ReasonString); + if (Err == CXError_RefactoringActionUnavailable) { + if (Initiated.hasValue() && Initiated.getValue()) { + errs() << "error: inconsistent results in a single action range!\n"; + return true; + } + Initiated = false; + } else if (Err != CXError_Success) + return true; + else if (Initiated.hasValue() && !Initiated.getValue()) { + errs() << "error: inconsistent results in a single action range!\n"; + return true; + } else + Initiated = true; + + CXRefactoringCandidateSet Candidates; + if (clang_RefactoringAction_getRefactoringCandidates(Action, &Candidates) == + CXError_Success && + Candidates.NumCandidates > 1) { + std::string CandidateString = refactoringCandidatesToString(Candidates); + if (LocationCandidateInformation) { + if (*LocationCandidateInformation != CandidateString) { + errs() << "error: inconsistent results in a single action range!\n"; + return true; + } + } else + LocationCandidateInformation = CandidateString; + } else if (opts::InitiateActionSubcommand && + !opts::initiateAndPerform::LocationAgnostic) { + CXSourceRange Range = + clang_RefactoringAction_getSourceRangeOfInterest(Action); + std::string LocationString = + std::string("at ") + + (!clang_Range_isNull(Range) + ? SelectionRange ? rangeToString(Range) + : locationToString(clang_getRangeStart(Range)) + : ""); + if (!LocationCandidateInformation.hasValue()) + LocationCandidateInformation = LocationString; + else if (LocationCandidateInformation.getValue() != LocationString) { + errs() << "error: inconsistent results in a single action range!\n"; + return true; + } + } + + if (!*Initiated) + return false; + + bool Failed = opts::PerformActionSubcommand + ? performOperation(Action, Args, CIdx) + : false; + clang_RefactoringAction_dispose(Action); + return Failed; + }; + + // Iterate over all of the possible locations and perform the initiation + // at each range. + for (const ParsedSourceLineRange &LineRange : Ranges) { + for (unsigned Column = LineRange.Column; Column <= LineRange.MaxColumn; + ++Column) { + if (InitiateAndPerform(LineRange, Column)) + return 1; + } + } + + for (const ParsedSourceRange &SelectionRange : SelectionRanges) { + if (InitiateAndPerform(SelectionRange.Begin, SelectionRange.Begin.Column, + SelectionRange)) + return 1; + } + + if (!Initiated.getValue()) { + errs() << "Failed to initiate the refactoring action"; + if (InitiationFailureReason.hasValue() && + !InitiationFailureReason.getValue().empty()) + errs() << " (" << InitiationFailureReason.getValue() << ')'; + errs() << "!\n"; + return 1; + } + if (opts::InitiateActionSubcommand) { + outs() << "Initiated the '" << opts::initiateAndPerform::ActionName + << "' action"; + if (!opts::initiateAndPerform::LocationAgnostic) + outs() << ' ' << LocationCandidateInformation.getValue(); + outs() << "\n"; + } + return 0; +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(opts::ClangRefactorTestOptions); + + cl::ParseCommandLineOptions(argc, argv, "Clang refactoring test tool\n"); + cl::PrintOptionValues(); + + CXIndex CIdx = clang_createIndex(0, 0); + + std::vector Args; + for (const auto &Arg : opts::CompilerArguments) { + Args.push_back(Arg.c_str()); + } + CXTranslationUnit TU; + CXErrorCode Err = clang_parseTranslationUnit2( + CIdx, + opts::IgnoreFilenameForInitiationTU ? nullptr : opts::FileName.c_str(), + Args.data(), Args.size(), 0, 0, CXTranslationUnit_KeepGoing, &TU); + if (Err != CXError_Success) { + errs() << "error: failed to load '" << opts::FileName << "'\n"; + return 1; + } + + if (opts::RenameInitiateSubcommand || opts::RenameInitiateUSRSubcommand) + return rename(TU, CIdx, Args); + else if (opts::RenameIndexedFileSubcommand) + return renameIndexedFile(CIdx, Args); + else if (opts::ListRefactoringActionsSubcommand) + return listRefactoringActions(TU); + else if (opts::InitiateActionSubcommand || opts::PerformActionSubcommand) + return initiateAndPerformAction(TU, Args, CIdx); + + clang_disposeTranslationUnit(TU); + clang_disposeIndex(CIdx); + + return 0; +} diff --git a/clang/tools/diagtool/DiagnosticNames.cpp b/clang/tools/diagtool/DiagnosticNames.cpp index a08da89577f1b..1f894898b3dea 100644 --- a/clang/tools/diagtool/DiagnosticNames.cpp +++ b/clang/tools/diagtool/DiagnosticNames.cpp @@ -41,6 +41,7 @@ static const DiagnosticRecord BuiltinDiagnosticsByID[] = { #include "clang/Basic/DiagnosticCommentKinds.inc" #include "clang/Basic/DiagnosticSemaKinds.inc" #include "clang/Basic/DiagnosticAnalysisKinds.inc" +#include "clang/Basic/DiagnosticRefactoringKinds.inc" #undef DIAG }; diff --git a/clang/tools/libclang/CIndexDiagnostic.cpp b/clang/tools/libclang/CIndexDiagnostic.cpp index de223d3043a31..4cf1d18e5b3a0 100644 --- a/clang/tools/libclang/CIndexDiagnostic.cpp +++ b/clang/tools/libclang/CIndexDiagnostic.cpp @@ -152,7 +152,20 @@ class CXDiagnosticRenderer : public DiagnosticNoteRenderer { CXDiagnosticSetImpl *CurrentSet; CXDiagnosticSetImpl *MainSet; -}; +}; + +class CXStoredDiagnosticSet : public CXDiagnosticSetImpl { + llvm::SmallVector Diags; + +public: + CXStoredDiagnosticSet(ArrayRef Diags, + const LangOptions &LangOpts) + : CXDiagnosticSetImpl(/*isManaged=*/true), + Diags(Diags.begin(), Diags.end()) { + for (const auto &Diag : this->Diags) + appendDiagnostic(llvm::make_unique(Diag, LangOpts)); + } +}; } CXDiagnosticSetImpl *cxdiag::lazyCreateDiags(CXTranslationUnit TU, @@ -202,6 +215,11 @@ CXDiagnosticSetImpl *cxdiag::lazyCreateDiags(CXTranslationUnit TU, return static_cast(TU->Diagnostics); } +CXDiagnosticSetImpl *cxdiag::createStoredDiags(ArrayRef Diags, + const LangOptions &LangOpts) { + return new CXStoredDiagnosticSet(Diags, LangOpts); +} + //----------------------------------------------------------------------------- // C Interface Routines //----------------------------------------------------------------------------- diff --git a/clang/tools/libclang/CIndexDiagnostic.h b/clang/tools/libclang/CIndexDiagnostic.h index 9f406987ebf1a..468f9bcde6227 100644 --- a/clang/tools/libclang/CIndexDiagnostic.h +++ b/clang/tools/libclang/CIndexDiagnostic.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_TOOLS_LIBCLANG_CINDEXDIAGNOSTIC_H #include "clang-c/Index.h" +#include "clang/Basic/LLVM.h" #include #include #include @@ -158,6 +159,9 @@ struct CXStoredDiagnostic : public CXDiagnosticImpl { namespace cxdiag { CXDiagnosticSetImpl *lazyCreateDiags(CXTranslationUnit TU, bool checkIfChanged = false); + +CXDiagnosticSetImpl *createStoredDiags(ArrayRef Diags, + const LangOptions &LangOpts); } // end namespace cxdiag } // end namespace clang diff --git a/clang/tools/libclang/CMakeLists.txt b/clang/tools/libclang/CMakeLists.txt index 5cad9dc3b3c67..dbad06aecfece 100644 --- a/clang/tools/libclang/CMakeLists.txt +++ b/clang/tools/libclang/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES CIndexInclusionStack.cpp CIndexUSRs.cpp CIndexer.cpp + CRefactor.cpp CXComment.cpp CXCursor.cpp CXIndexDataConsumer.cpp diff --git a/clang/tools/libclang/CRefactor.cpp b/clang/tools/libclang/CRefactor.cpp new file mode 100644 index 0000000000000..7331c3a53abb0 --- /dev/null +++ b/clang/tools/libclang/CRefactor.cpp @@ -0,0 +1,1917 @@ +//===- CRefactor.cpp - Refactoring API hooks ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the Clang-C refactoring library. +// +//===----------------------------------------------------------------------===// + +#include "CIndexDiagnostic.h" +#include "CIndexer.h" +#include "CLog.h" +#include "CXCursor.h" +#include "CXSourceLocation.h" +#include "CXString.h" +#include "CXTranslationUnit.h" +#include "clang-c/Refactor.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/Basic/DiagnosticCategories.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/Utils.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Tooling/Refactor/IndexerQuery.h" +#include "clang/Tooling/Refactor/RefactoringActionFinder.h" +#include "clang/Tooling/Refactor/RefactoringActions.h" +#include "clang/Tooling/Refactor/RefactoringOperation.h" +#include "clang/Tooling/Refactor/RefactoringOptions.h" +#include "clang/Tooling/Refactor/RenameIndexedFile.h" +#include "clang/Tooling/Refactor/RenamingOperation.h" +#include "clang/Tooling/Refactor/SymbolOccurrenceFinder.h" +#include "clang/Tooling/Refactor/USRFinder.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringMap.h" +#include +#include + +using namespace clang; +using namespace clang::tooling; + +static RefactoringActionType +translateRefactoringActionType(CXRefactoringActionType Action) { + switch (Action) { +#define REFACTORING_ACTION(Name, Spelling) \ + case CXRefactor_##Name: \ + return RefactoringActionType::Name; +#include "clang/Tooling/Refactor/RefactoringActions.def" + } +} + +static CXRefactoringActionType +translateRefactoringActionType(RefactoringActionType Action) { + switch (Action) { +#define REFACTORING_ACTION(Name, Spelling) \ + case RefactoringActionType::Name: \ + return CXRefactor_##Name; +#include "clang/Tooling/Refactor/RefactoringActions.def" + } +} + +static CXSymbolOccurrenceKind +translateOccurrenceKind(rename::SymbolOccurrence::OccurrenceKind Kind) { + switch (Kind) { + case rename::SymbolOccurrence::MatchingSymbol: + return CXSymbolOccurrence_MatchingSymbol; + case rename::SymbolOccurrence::MatchingSelector: + return CXSymbolOccurrence_MatchingSelector; + case rename::SymbolOccurrence::MatchingImplicitProperty: + return CXSymbolOccurrence_MatchingImplicitProperty; + case rename::SymbolOccurrence::MatchingComment: + return CXSymbolOccurrence_MatchingCommentString; + case rename::SymbolOccurrence::MatchingDocComment: + return CXSymbolOccurrence_MatchingDocCommentString; + case rename::SymbolOccurrence::MatchingFilename: + return CXSymbolOccurrence_MatchingFilename; + } +} + +namespace { + +// TODO: Remove +class RenamingResult { + struct RenamedNameString { + CXString NewString; + unsigned OldLength; + }; + typedef SmallVector SymbolNameInfo; + std::vector NameInfo; + + /// The set of files that have to be modified. + llvm::SmallVector Filenames; + llvm::SpecificBumpPtrAllocator Replacements; + std::vector> Occurrences; + + void addOccurrence(const rename::SymbolOccurrence &RenamedOccurrence, + const SourceManager &SM, const LangOptions &LangOpts) { + CXRefactoringReplacement_Old *OccurrenceReplacements = + Replacements.Allocate(RenamedOccurrence.Locations.size()); + + unsigned I = 0; + const auto &SymbolNameInfo = NameInfo[RenamedOccurrence.SymbolIndex]; + if (!RenamedOccurrence.IsMacroExpansion && + RenamedOccurrence.Kind != rename::SymbolOccurrence::MatchingComment && + RenamedOccurrence.Kind != rename::SymbolOccurrence::MatchingDocComment) + assert(RenamedOccurrence.Locations.size() == SymbolNameInfo.size()); + for (const auto &Location : RenamedOccurrence.Locations) { + CXSourceRange Range = cxloc::translateSourceRange( + SM, LangOpts, + CharSourceRange::getCharRange(RenamedOccurrence.getLocationRange( + Location, SymbolNameInfo[I].OldLength))); + CXFileLocation Begin, End; + clang_getFileLocation(clang_getRangeStart(Range), nullptr, &Begin.Line, + &Begin.Column, nullptr); + clang_getFileLocation(clang_getRangeEnd(Range), nullptr, &End.Line, + &End.Column, nullptr); + + OccurrenceReplacements[I] = CXRefactoringReplacement_Old{ + {Begin, End}, + RenamedOccurrence.IsMacroExpansion ? cxstring::createNull() + : SymbolNameInfo[I].NewString}; + ++I; + } + + Occurrences.back().push_back(CXRenamedSymbolOccurrence{ + OccurrenceReplacements, I, + translateOccurrenceKind(RenamedOccurrence.Kind), + RenamedOccurrence.IsMacroExpansion}); + } + +public: + RenamingResult(ArrayRef NewNames, + ArrayRef Symbols) { + assert(NewNames.size() == Symbols.size()); + for (size_t I = 0, E = NewNames.size(); I != E; ++I) { + const auto &NewName = NewNames[I]; + const auto &OldName = Symbols[I].Name; + + assert(NewName.size() == OldName.size()); + SymbolNameInfo Info; + for (size_t I = 0, E = NewName.size(); I != E; ++I) + Info.push_back(RenamedNameString{cxstring::createDup(NewName[I]), + (unsigned)OldName[I].size()}); + NameInfo.push_back(std::move(Info)); + } + } + + // FIXME: Don't duplicate code, Use just one constructor. + RenamingResult(ArrayRef NewNames, ArrayRef OldNames) { + assert(NewNames.size() == OldNames.size()); + for (size_t I = 0, E = NewNames.size(); I != E; ++I) { + const auto &NewName = NewNames[I]; + const auto &OldName = OldNames[I]; + + assert(NewName.size() == OldName.size()); + SymbolNameInfo Info; + for (size_t I = 0, E = NewName.size(); I != E; ++I) + Info.push_back(RenamedNameString{cxstring::createDup(NewName[I]), + (unsigned)OldName[I].size()}); + NameInfo.push_back(std::move(Info)); + } + } + + ~RenamingResult() { + for (const auto &SymbolInfo : NameInfo) + for (const auto &NameString : SymbolInfo) + clang_disposeString(NameString.NewString); + for (const auto &Filename : Filenames) + clang_disposeString(Filename); + } + + void + handleTUResults(CXTranslationUnit TU, + llvm::MutableArrayRef Results) { + ASTUnit *Unit = cxtu::getASTUnit(TU); + assert(Unit && "Invalid TU"); + auto &Ctx = Unit->getASTContext(); + + // Find the set of files that have to be modified and gather the indices of + // the occurrences for each file. + const SourceManager &SM = Ctx.getSourceManager(); + typedef std::set OccurrenceSet; + llvm::StringMap FilenamesToSymbolOccurrences; + for (auto &Occurrence : Results) { + const std::pair DecomposedLocation = + SM.getDecomposedLoc(Occurrence.Locations[0]); + const FileEntry *Entry = SM.getFileEntryForID(DecomposedLocation.first); + assert(Entry && "Invalid file entry"); + auto &FileOccurrences = + FilenamesToSymbolOccurrences + .try_emplace(Entry->getName(), OccurrenceSet()) + .first->getValue(); + FileOccurrences.insert(std::move(Occurrence)); + } + + // Create the filenames + for (const auto &FilenameCount : FilenamesToSymbolOccurrences) + Filenames.push_back(cxstring::createDup(FilenameCount.getKey())); + + unsigned FileIndex = 0; + for (const auto &RenamedOccurrences : FilenamesToSymbolOccurrences) { + assert(clang_getCString(Filenames[FileIndex]) == + RenamedOccurrences.getKey() && + "Unstable iteration order"); + Occurrences.push_back(std::vector()); + for (const auto &Occurrence : RenamedOccurrences.getValue()) + addOccurrence(Occurrence, SM, Ctx.getLangOpts()); + ++FileIndex; + } + } + + void addMainFilename(const SourceManager &SM) { + assert(Filenames.empty() && "Main filename should be added only once"); + Filenames.push_back(cxstring::createDup( + SM.getFileEntryForID(SM.getMainFileID())->getName())); + Occurrences.push_back(std::vector()); + } + + void + handleSingleFileTUResults(const ASTContext &Ctx, + ArrayRef Occurrences) { + addMainFilename(Ctx.getSourceManager()); + for (const auto &Occurrence : Occurrences) + addOccurrence(Occurrence, Ctx.getSourceManager(), Ctx.getLangOpts()); + } + + void handleIndexedFileOccurrence(const rename::SymbolOccurrence &Occurrence, + const SourceManager &SM, + const LangOptions &LangOpts) { + if (Filenames.empty()) { + addMainFilename(SM); + } + addOccurrence(Occurrence, SM, LangOpts); + } + + ArrayRef getOccurrences(unsigned FileIndex) const { + return Occurrences[FileIndex]; + } + + ArrayRef getFilenames() const { return Filenames; } +}; + +class SymbolOccurrencesResult { + struct SymbolNamePiece { + unsigned OldLength; + }; + typedef SmallVector SymbolNameInfo; + std::vector NameInfo; + + /// The set of files that have to be modified. + llvm::SmallVector Filenames; + llvm::SpecificBumpPtrAllocator Ranges; + std::vector> SymbolOccurrences; + + void addOccurrence(const rename::SymbolOccurrence &RenamedOccurrence, + const SourceManager &SM, const LangOptions &LangOpts) { + CXFileRange *OccurrenceRanges = + Ranges.Allocate(RenamedOccurrence.Locations.size()); + + unsigned I = 0; + const auto &SymbolNameInfo = NameInfo[RenamedOccurrence.SymbolIndex]; + if (!RenamedOccurrence.IsMacroExpansion && + RenamedOccurrence.Kind != rename::SymbolOccurrence::MatchingComment && + RenamedOccurrence.Kind != rename::SymbolOccurrence::MatchingDocComment) + assert(RenamedOccurrence.Locations.size() == SymbolNameInfo.size()); + for (const auto &Location : RenamedOccurrence.Locations) { + CXSourceRange Range = cxloc::translateSourceRange( + SM, LangOpts, + CharSourceRange::getCharRange(RenamedOccurrence.getLocationRange( + Location, SymbolNameInfo[I].OldLength))); + CXFileLocation Begin, End; + clang_getFileLocation(clang_getRangeStart(Range), nullptr, &Begin.Line, + &Begin.Column, nullptr); + clang_getFileLocation(clang_getRangeEnd(Range), nullptr, &End.Line, + &End.Column, nullptr); + OccurrenceRanges[I] = CXFileRange{Begin, End}; + ++I; + } + + SymbolOccurrences.back().push_back(CXSymbolOccurrence{ + OccurrenceRanges, /*NumNamePieces=*/I, + translateOccurrenceKind(RenamedOccurrence.Kind), + RenamedOccurrence.IsMacroExpansion, RenamedOccurrence.SymbolIndex}); + } + +public: + SymbolOccurrencesResult(ArrayRef Symbols) { + for (const auto &Symbol : Symbols) { + const SymbolName &Name = Symbol.Name; + SymbolNameInfo Info; + for (size_t I = 0, E = Name.size(); I != E; ++I) + Info.push_back(SymbolNamePiece{(unsigned)Name[I].size()}); + NameInfo.push_back(std::move(Info)); + } + } + + SymbolOccurrencesResult(ArrayRef Names) { + for (const SymbolName &Name : Names) { + SymbolNameInfo Info; + for (size_t I = 0, E = Name.size(); I != E; ++I) + Info.push_back(SymbolNamePiece{(unsigned)Name[I].size()}); + NameInfo.push_back(std::move(Info)); + } + } + + ~SymbolOccurrencesResult() { + for (const auto &Filename : Filenames) + clang_disposeString(Filename); + } + + void + handleTUResults(CXTranslationUnit TU, + llvm::MutableArrayRef Results) { + ASTUnit *Unit = cxtu::getASTUnit(TU); + assert(Unit && "Invalid TU"); + auto &Ctx = Unit->getASTContext(); + + // Find the set of files that have to be modified and gather the indices of + // the occurrences for each file. + const SourceManager &SM = Ctx.getSourceManager(); + typedef std::set OccurrenceSet; + llvm::StringMap FilenamesToSymbolOccurrences; + for (auto &Occurrence : Results) { + const std::pair DecomposedLocation = + SM.getDecomposedLoc(Occurrence.Locations[0]); + const FileEntry *Entry = SM.getFileEntryForID(DecomposedLocation.first); + assert(Entry && "Invalid file entry"); + auto &FileOccurrences = + FilenamesToSymbolOccurrences + .try_emplace(Entry->getName(), OccurrenceSet()) + .first->getValue(); + FileOccurrences.insert(std::move(Occurrence)); + } + + // Create the filenames + for (const auto &FilenameCount : FilenamesToSymbolOccurrences) + Filenames.push_back(cxstring::createDup(FilenameCount.getKey())); + + unsigned FileIndex = 0; + for (const auto &RenamedOccurrences : FilenamesToSymbolOccurrences) { + assert(clang_getCString(Filenames[FileIndex]) == + RenamedOccurrences.getKey() && + "Unstable iteration order"); + SymbolOccurrences.push_back(std::vector()); + for (const auto &Occurrence : RenamedOccurrences.getValue()) + addOccurrence(Occurrence, SM, Ctx.getLangOpts()); + ++FileIndex; + } + } + + void addMainFilename(const SourceManager &SM) { + assert(Filenames.empty() && "Main filename should be added only once"); + Filenames.push_back(cxstring::createDup( + SM.getFileEntryForID(SM.getMainFileID())->getName())); + SymbolOccurrences.push_back(std::vector()); + } + + void handleIndexedFileOccurrence(const rename::SymbolOccurrence &Occurrence, + const SourceManager &SM, + const LangOptions &LangOpts) { + if (Filenames.empty()) { + addMainFilename(SM); + } + addOccurrence(Occurrence, SM, LangOpts); + } + + ArrayRef getOccurrences(unsigned FileIndex) const { + return SymbolOccurrences[FileIndex]; + } + + ArrayRef getFilenames() const { return Filenames; } +}; + +class RenamingAction { +public: + LangOptions LangOpts; + IdentifierTable IDs; + // TODO: Remove + SmallVector NewNames; + SymbolOperation Operation; + + RenamingAction(const LangOptions &LangOpts, SymbolOperation Operation) + : LangOpts(LangOpts), IDs(LangOpts), Operation(std::move(Operation)) {} + + /// \brief Sets the new renaming name and returns CXError_Success on success. + // TODO: Remove + CXErrorCode setNewName(StringRef Name) { + SymbolName NewSymbolName(Name, LangOpts); + if (NewSymbolName.size() != Operation.symbols()[0].Name.size()) + return CXError_RefactoringNameSizeMismatch; + if (!rename::isNewNameValid(NewSymbolName, Operation, IDs, LangOpts)) + return CXError_RefactoringNameInvalid; + rename::determineNewNames(std::move(NewSymbolName), Operation, NewNames, + LangOpts); + return CXError_Success; + } + + // TODO: Remove + CXString usrForSymbolAt(unsigned Index) { + llvm::SmallVector Buff; + if (index::generateUSRForDecl(Operation.symbols()[Index].FoundDecl, Buff)) + return cxstring::createNull(); + return cxstring::createDup(StringRef(Buff.begin(), Buff.size())); + } + + // TODO: Remove + CXString getUSRThatRequiresImplementationTU() { + llvm::SmallVector Buff; + if (!Operation.requiresImplementationTU() || + index::generateUSRForDecl(Operation.declThatRequiresImplementationTU(), + Buff)) + return cxstring::createNull(); + return cxstring::createDup(StringRef(Buff.begin(), Buff.size())); + } + + // TODO: Remove + RenamingResult *handlePrimaryTU(CXTranslationUnit TU, ASTUnit &Unit) { + // Perform the renaming. + if (NewNames.empty()) + return nullptr; + + const ASTContext &Context = Unit.getASTContext(); + auto Occurrences = rename::findSymbolOccurrences( + Operation, Context.getTranslationUnitDecl()); + auto *Result = new RenamingResult(NewNames, Operation.symbols()); + Result->handleTUResults(TU, Occurrences); + return Result; + } + + SymbolOccurrencesResult *findSymbolsInInitiationTU(CXTranslationUnit TU, + ASTUnit &Unit) { + const ASTContext &Context = Unit.getASTContext(); + auto Occurrences = rename::findSymbolOccurrences( + Operation, Context.getTranslationUnitDecl()); + auto *Result = new SymbolOccurrencesResult(Operation.symbols()); + Result->handleTUResults(TU, Occurrences); + return Result; + } +}; + +static bool isObjCSelectorKind(CXCursorKind Kind) { + return Kind == CXCursor_ObjCInstanceMethodDecl || + Kind == CXCursor_ObjCClassMethodDecl || + Kind == CXCursor_ObjCMessageExpr; +} + +// TODO: Remove +static bool isObjCSelector(const CXRenamedIndexedSymbol &Symbol) { + if (isObjCSelectorKind(Symbol.CursorKind)) + return true; + for (const auto &Occurrence : llvm::makeArrayRef( + Symbol.IndexedLocations, Symbol.IndexedLocationCount)) { + if (isObjCSelectorKind(Occurrence.CursorKind)) + return true; + } + return false; +} + +static bool isObjCSelector(const CXIndexedSymbol &Symbol) { + if (isObjCSelectorKind(Symbol.CursorKind)) + return true; + for (const auto &Occurrence : llvm::makeArrayRef( + Symbol.IndexedLocations, Symbol.IndexedLocationCount)) { + if (isObjCSelectorKind(Occurrence.CursorKind)) + return true; + } + return false; +} + +// New names are initialized and verified after the LangOptions are created. +CXErrorCode computeNewNames(ArrayRef Symbols, + ArrayRef SymbolNames, + const LangOptions &LangOpts, + SmallVectorImpl &NewNames) { + IdentifierTable IDs(LangOpts); + for (const auto &Symbol : Symbols) { + SymbolName NewSymbolName(Symbol.NewName, LangOpts); + if (NewSymbolName.size() != SymbolNames[0].size()) + return CXError_RefactoringNameSizeMismatch; + if (!rename::isNewNameValid(NewSymbolName, isObjCSelector(Symbol), IDs, + LangOpts)) + return CXError_RefactoringNameInvalid; + NewNames.push_back(std::move(NewSymbolName)); + } + return CXError_Success; +} + +static rename::IndexedOccurrence::OccurrenceKind +translateIndexedOccurrenceKind(CXCursorKind Kind) { + switch (Kind) { + case CXCursor_ObjCMessageExpr: + return rename::IndexedOccurrence::IndexedObjCMessageSend; + case CXCursor_InclusionDirective: + return rename::IndexedOccurrence::InclusionDirective; + default: + return rename::IndexedOccurrence::IndexedSymbol; + } +} + +// TODO: Remove +CXErrorCode performIndexedFileRename( + ArrayRef Symbols, StringRef Filename, + ArrayRef Arguments, CXIndex CIdx, + MutableArrayRef UnsavedFiles, + const RefactoringOptionSet *Options, CXRenamingResult &Result) { + Result = nullptr; + + // Adjust the given command line arguments to ensure that any positional + // arguments in them are stripped. + std::vector ClangToolArguments; + ClangToolArguments.push_back("--"); + for (const auto &Arg : Arguments) { + // Remove the '-gmodules' option, as the -fmodules-format=obj isn't + // supported without the linked object reader. + if (StringRef(Arg) == "-gmodules") + continue; + ClangToolArguments.push_back(Arg); + } + int Argc = ClangToolArguments.size(); + std::string ErrorMessage; + std::unique_ptr Compilations = + FixedCompilationDatabase::loadFromCommandLine( + Argc, ClangToolArguments.data(), ErrorMessage); + if (!Compilations) { + llvm::errs() << "CRefactor: Failed to load command line: " << ErrorMessage + << "\n"; + return CXError_Failure; + } + + // Translate the symbols. + llvm::SmallVector IndexedSymbols; + for (const auto &Symbol : Symbols) { + + // Parse the symbol name. + bool IsObjCSelector = false; + // Selectors have to be parsed. + if (isObjCSelector(Symbol)) + IsObjCSelector = true; + // Ensure that we don't get selectors with incorrect symbol kind. + else if (StringRef(Symbol.Name).contains(':')) + return CXError_InvalidArguments; + + std::vector IndexedOccurrences; + for (const auto &Loc : llvm::makeArrayRef(Symbol.IndexedLocations, + Symbol.IndexedLocationCount)) { + rename::IndexedOccurrence Result; + Result.Line = Loc.Location.Line; + Result.Column = Loc.Location.Column; + Result.Kind = translateIndexedOccurrenceKind(Loc.CursorKind); + IndexedOccurrences.push_back(Result); + } + + IndexedSymbols.emplace_back(SymbolName(Symbol.Name, IsObjCSelector), + IndexedOccurrences, + /*IsObjCSelector=*/IsObjCSelector); + } + + class ToolRunner final : public FrontendActionFactory, + public rename::IndexedFileOccurrenceConsumer { + ArrayRef Symbols; + ArrayRef IndexedSymbols; + const RefactoringOptionSet *Options; + + public: + RenamingResult *Result; + CXErrorCode Err; + + ToolRunner(ArrayRef Symbols, + ArrayRef IndexedSymbols, + const RefactoringOptionSet *Options) + : Symbols(Symbols), IndexedSymbols(IndexedSymbols), Options(Options), + Result(nullptr), Err(CXError_Success) {} + + clang::FrontendAction *create() override { + return new rename::IndexedFileOccurrenceProducer(IndexedSymbols, *this, + Options); + } + + void handleOccurrence(const rename::SymbolOccurrence &Occurrence, + SourceManager &SM, + const LangOptions &LangOpts) override { + if (Err != CXError_Success) + return; + if (!Result) { + SmallVector SymbolNames; + for (const auto &Symbol : IndexedSymbols) + SymbolNames.push_back(Symbol.Name); + SmallVector NewNames; + Err = computeNewNames(Symbols, SymbolNames, LangOpts, NewNames); + if (Err != CXError_Success) + return; + Result = new RenamingResult(NewNames, SymbolNames); + } + Result->handleIndexedFileOccurrence(Occurrence, SM, LangOpts); + } + }; + + auto Runner = llvm::make_unique(Symbols, IndexedSymbols, Options); + + // Run a clang tool on the input file. + std::string Name = Filename.str(); + ClangTool Tool(*Compilations, Name); + Tool.run(Runner.get()); + if (Runner->Err != CXError_Success) + return Runner->Err; + Result = Runner->Result; + return CXError_Success; +} + +CXErrorCode performIndexedSymbolSearch( + ArrayRef Symbols, StringRef Filename, + ArrayRef Arguments, CXIndex CIdx, + MutableArrayRef UnsavedFiles, + const RefactoringOptionSet *Options, CXSymbolOccurrencesResult &Result) { + Result = nullptr; + + // Adjust the given command line arguments to ensure that any positional + // arguments in them are stripped. + std::vector ClangToolArguments; + ClangToolArguments.push_back("--"); + for (const auto &Arg : Arguments) { + // Remove the '-gmodules' option, as the -fmodules-format=obj isn't + // supported without the linked object reader. + if (StringRef(Arg) == "-gmodules") + continue; + ClangToolArguments.push_back(Arg); + } + int Argc = ClangToolArguments.size(); + std::string ErrorMessage; + std::unique_ptr Compilations = + FixedCompilationDatabase::loadFromCommandLine( + Argc, ClangToolArguments.data(), ErrorMessage); + if (!Compilations) { + llvm::errs() << "CRefactor: Failed to load command line: " << ErrorMessage + << "\n"; + return CXError_Failure; + } + + // Translate the symbols. + llvm::SmallVector IndexedSymbols; + for (const auto &Symbol : Symbols) { + + // Parse the symbol name. + bool IsObjCSelector = false; + // Selectors have to be parsed. + if (isObjCSelector(Symbol)) + IsObjCSelector = true; + // Ensure that we don't get selectors with incorrect symbol kind. + else if (StringRef(Symbol.Name).contains(':')) + return CXError_InvalidArguments; + + std::vector IndexedOccurrences; + for (const auto &Loc : llvm::makeArrayRef(Symbol.IndexedLocations, + Symbol.IndexedLocationCount)) { + rename::IndexedOccurrence Result; + Result.Line = Loc.Location.Line; + Result.Column = Loc.Location.Column; + Result.Kind = translateIndexedOccurrenceKind(Loc.CursorKind); + IndexedOccurrences.push_back(Result); + } + + IndexedSymbols.emplace_back(SymbolName(Symbol.Name, IsObjCSelector), + IndexedOccurrences, + /*IsObjCSelector=*/IsObjCSelector); + } + + class ToolRunner final : public FrontendActionFactory, + public rename::IndexedFileOccurrenceConsumer { + ArrayRef IndexedSymbols; + const RefactoringOptionSet *Options; + + public: + SymbolOccurrencesResult *Result; + + ToolRunner(ArrayRef IndexedSymbols, + const RefactoringOptionSet *Options) + : IndexedSymbols(IndexedSymbols), Options(Options), Result(nullptr) {} + + clang::FrontendAction *create() override { + return new rename::IndexedFileOccurrenceProducer(IndexedSymbols, *this, + Options); + } + + void handleOccurrence(const rename::SymbolOccurrence &Occurrence, + SourceManager &SM, + const LangOptions &LangOpts) override { + if (!Result) { + SmallVector SymbolNames; + for (const auto &Symbol : IndexedSymbols) + SymbolNames.push_back(Symbol.Name); + Result = new SymbolOccurrencesResult(SymbolNames); + } + Result->handleIndexedFileOccurrence(Occurrence, SM, LangOpts); + } + }; + + auto Runner = llvm::make_unique(IndexedSymbols, Options); + + // Run a clang tool on the input file. + std::string Name = Filename.str(); + ClangTool Tool(*Compilations, Name); + for (const CXUnsavedFile &File : UnsavedFiles) + Tool.mapVirtualFile(File.Filename, StringRef(File.Contents, File.Length)); + if (Tool.run(Runner.get())) + return CXError_Failure; + Result = Runner->Result; + return CXError_Success; +} + +class RefactoringAction { + std::unique_ptr Operation; + std::unique_ptr Rename; + + SmallVector RefactoringCandidates; + CXRefactoringCandidateSet CandidateSet = {nullptr, 0}; + bool HasCandidateSet = false; + +public: + CXRefactoringActionType Type; + unsigned SelectedCandidate = 0; + CXTranslationUnit InitiationTU; + // TODO: Remove (no longer needed due to continuations). + CXTranslationUnit ImplementationTU; + + RefactoringAction(std::unique_ptr Operation, + CXRefactoringActionType Type, + CXTranslationUnit InitiationTU) + : Operation(std::move(Operation)), Type(Type), InitiationTU(InitiationTU), + ImplementationTU(nullptr) {} + + RefactoringAction(std::unique_ptr Rename, + CXTranslationUnit InitiationTU) + : Rename(std::move(Rename)), + Type(this->Rename->Operation.isLocal() ? CXRefactor_Rename_Local + : CXRefactor_Rename), + InitiationTU(InitiationTU), ImplementationTU(nullptr) {} + + ~RefactoringAction() { + for (const auto &Candidate : RefactoringCandidates) + clang_disposeString(Candidate.Description); + } + + RefactoringOperation *getOperation() const { return Operation.get(); } + + RenamingAction *getRenamingAction() const { return Rename.get(); } + + CXRefactoringCandidateSet getRefactoringCandidates() { + if (HasCandidateSet) + return CandidateSet; + HasCandidateSet = true; + RefactoringOperation *Operation = getOperation(); + if (!Operation) + return CandidateSet; + auto Candidates = Operation->getRefactoringCandidates(); + if (Candidates.empty()) + return CandidateSet; + for (const auto &Candidate : Candidates) + RefactoringCandidates.push_back({cxstring::createDup(Candidate)}); + CandidateSet = {RefactoringCandidates.data(), + (unsigned)RefactoringCandidates.size()}; + return CandidateSet; + } + + CXErrorCode selectCandidate(unsigned Index) { + RefactoringOperation *Operation = getOperation(); + if (!Operation) + return CXError_InvalidArguments; + if (Index != 0 && Index >= getRefactoringCandidates().NumCandidates) + return CXError_InvalidArguments; + SelectedCandidate = Index; + return CXError_Success; + } +}; + +static bool operator==(const CXFileLocation &LHS, const CXFileLocation &RHS) { + return LHS.Line == RHS.Line && LHS.Column == RHS.Column; +} + +static CXFileRange translateOffsetToRelativeRange(unsigned Offset, + unsigned Size, + StringRef Source) { + assert(Source.drop_front(Offset).take_front(Size).count('\n') == 0 && + "Newlines in translated range?"); + StringRef Prefix = Source.take_front(Offset); + unsigned StartLines = Prefix.count('\n') + 1; + if (StartLines > 1) + Offset -= Prefix.rfind('\n') + 1; + return CXFileRange{{StartLines, Offset + 1}, {StartLines, Offset + 1 + Size}}; +} + +class RefactoringResultWrapper { +public: + CXRefactoringReplacements_Old Replacements; // TODO: Remove. + CXRefactoringReplacements SourceReplacements; + std::unique_ptr Continuation; + llvm::BumpPtrAllocator Allocator; + CXTranslationUnit TU; + + struct AssociatedReplacementInfo { + CXSymbolOccurrence *AssociatedSymbolOccurrences; + unsigned NumAssociatedSymbolOccurrences; + }; + + ~RefactoringResultWrapper() { + // TODO: Remove. + for (unsigned I = 0; I < Replacements.NumFileReplacementSets; ++I) { + const CXRefactoringFileReplacementSet_Old &FileSet = + Replacements.FileReplacementSets[I]; + clang_disposeString(FileSet.Filename); + for (unsigned J = 0; J < FileSet.NumReplacements; ++J) + clang_disposeString(FileSet.Replacements[J].ReplacementString); + delete[] FileSet.Replacements; + } + delete[] Replacements.FileReplacementSets; + + for (unsigned I = 0; I < SourceReplacements.NumFileReplacementSets; ++I) { + const CXRefactoringFileReplacementSet &FileSet = + SourceReplacements.FileReplacementSets[I]; + clang_disposeString(FileSet.Filename); + for (unsigned J = 0; J < FileSet.NumReplacements; ++J) + clang_disposeString(FileSet.Replacements[J].ReplacementString); + } + } + + RefactoringResultWrapper( + ArrayRef Replacements, + ArrayRef> + AssociatedSymbols, + std::unique_ptr Continuation, + ASTContext &Context, CXTranslationUnit TU) + : Continuation(std::move(Continuation)), TU(TU) { + SourceManager &SM = Context.getSourceManager(); + + if (Replacements.empty()) { + assert(AssociatedSymbols.empty() && "Symbols without replacements??"); + // TODO: Remove begin + this->Replacements.NumFileReplacementSets = 0; + this->Replacements.FileReplacementSets = nullptr; + // Remove end + this->SourceReplacements.NumFileReplacementSets = 0; + this->SourceReplacements.FileReplacementSets = nullptr; + return; + } + llvm::SmallDenseMap + AssociatedSymbolToIndex; + for (const auto &Symbol : llvm::enumerate(AssociatedSymbols)) + AssociatedSymbolToIndex[Symbol.value().get()] = Symbol.index(); + + // Find the set of files that have to be modified and gather the indices of + // the occurrences for each file. + llvm::DenseMap> + FilesToReplacements; + for (const auto &Replacement : llvm::enumerate(Replacements)) { + SourceLocation Loc = Replacement.value().Range.getBegin(); + const std::pair DecomposedLocation = + SM.getDecomposedLoc(Loc); + const FileEntry *Entry = SM.getFileEntryForID(DecomposedLocation.first); + FilesToReplacements.try_emplace(Entry, std::vector()) + .first->second.push_back(Replacement.index()); + } + + // TODO: Remove + unsigned NumFiles = FilesToReplacements.size(); + auto *FileReplacementSets = + new CXRefactoringFileReplacementSet_Old[NumFiles]; + + unsigned FileIndex = 0; + for (const auto &Entry : FilesToReplacements) { + CXRefactoringFileReplacementSet_Old &FileSet = + FileReplacementSets[FileIndex]; + ++FileIndex; + ArrayRef ReplacementIndices = Entry.second; + FileSet.Filename = cxstring::createDup(Entry.first->getName()); + FileSet.NumReplacements = ReplacementIndices.size(); + auto *FileReplacements = + new CXRefactoringReplacement_Old[ReplacementIndices.size()]; + FileSet.Replacements = FileReplacements; + + unsigned NumRemoved = 0; + for (unsigned I = 0; I < FileSet.NumReplacements; ++I) { + const RefactoringReplacement &RefReplacement = + Replacements[ReplacementIndices[I]]; + CXSourceRange Range = cxloc::translateSourceRange( + SM, Context.getLangOpts(), + CharSourceRange::getCharRange(RefReplacement.Range.getBegin(), + RefReplacement.Range.getEnd())); + CXFileLocation Begin, End; + clang_getFileLocation(clang_getRangeStart(Range), nullptr, &Begin.Line, + &Begin.Column, nullptr); + clang_getFileLocation(clang_getRangeEnd(Range), nullptr, &End.Line, + &End.Column, nullptr); + + if (I && FileReplacements[I - NumRemoved - 1].Range.End == Begin) { + // Merge the previous and the current replacement. + FileReplacements[I - NumRemoved - 1].Range.End = End; + std::string Replacement = + std::string(clang_getCString( + FileReplacements[I - NumRemoved - 1].ReplacementString)) + + RefReplacement.ReplacementString; + FileReplacements[I - NumRemoved - 1].ReplacementString = + cxstring::createDup(Replacement); + NumRemoved++; + continue; + } + + CXRefactoringReplacement_Old &Replacement = + FileReplacements[I - NumRemoved]; + Replacement.ReplacementString = + cxstring::createDup(RefReplacement.ReplacementString); + Replacement.Range.Begin = Begin; + Replacement.Range.End = End; + } + FileSet.NumReplacements -= NumRemoved; + } + + this->Replacements.FileReplacementSets = FileReplacementSets; + this->Replacements.NumFileReplacementSets = NumFiles; + + // TODO: Outdent. + { + unsigned NumFiles = FilesToReplacements.size(); + auto *FileReplacementSets = + Allocator.Allocate(NumFiles); + SourceReplacements.FileReplacementSets = FileReplacementSets; + SourceReplacements.NumFileReplacementSets = NumFiles; + unsigned FileIndex = 0; + for (const auto &Entry : FilesToReplacements) { + CXRefactoringFileReplacementSet &FileSet = + FileReplacementSets[FileIndex]; + ++FileIndex; + ArrayRef ReplacementIndices = Entry.second; + FileSet.Filename = cxstring::createDup(Entry.first->getName()); + FileSet.NumReplacements = ReplacementIndices.size(); + auto *FileReplacements = Allocator.Allocate( + ReplacementIndices.size()); + FileSet.Replacements = FileReplacements; + + unsigned NumRemoved = 0; + for (unsigned I = 0; I < FileSet.NumReplacements; ++I) { + const RefactoringReplacement &RefReplacement = + Replacements[ReplacementIndices[I]]; + CXSourceRange Range = cxloc::translateSourceRange( + SM, Context.getLangOpts(), + CharSourceRange::getCharRange(RefReplacement.Range.getBegin(), + RefReplacement.Range.getEnd())); + CXFileLocation Begin, End; + clang_getFileLocation(clang_getRangeStart(Range), nullptr, + &Begin.Line, &Begin.Column, nullptr); + clang_getFileLocation(clang_getRangeEnd(Range), nullptr, &End.Line, + &End.Column, nullptr); + + if (I && FileReplacements[I - NumRemoved - 1].Range.End == Begin) { + // Merge the previous and the current replacement. + FileReplacements[I - NumRemoved - 1].Range.End = End; + std::string Replacement = + std::string(clang_getCString( + FileReplacements[I - NumRemoved - 1].ReplacementString)) + + RefReplacement.ReplacementString; + FileReplacements[I - NumRemoved - 1].ReplacementString = + cxstring::createDup(Replacement); + NumRemoved++; + continue; + } + + CXRefactoringReplacement &Replacement = + FileReplacements[I - NumRemoved]; + Replacement.ReplacementString = + cxstring::createDup(RefReplacement.ReplacementString); + Replacement.Range.Begin = Begin; + Replacement.Range.End = End; + unsigned NumAssociatedSymbols = RefReplacement.SymbolLocations.size(); + if (!NumAssociatedSymbols) { + Replacement.AssociatedData = nullptr; + continue; + } + AssociatedReplacementInfo *AssociatedData = + Allocator.Allocate(); + Replacement.AssociatedData = AssociatedData; + AssociatedData->AssociatedSymbolOccurrences = + Allocator.Allocate(NumAssociatedSymbols); + AssociatedData->NumAssociatedSymbolOccurrences = NumAssociatedSymbols; + unsigned SymbolIndex = 0; + for (const auto &AssociatedSymbol : RefReplacement.SymbolLocations) { + unsigned Index = AssociatedSymbolToIndex[AssociatedSymbol.first]; + const RefactoringReplacement::AssociatedSymbolLocation &Loc = + AssociatedSymbol.second; + CXFileRange *NamePieces = + Allocator.Allocate(Loc.Offsets.size()); + assert(AssociatedSymbol.first->getName().size() == + Loc.Offsets.size() && + "mismatching symbol name and offsets"); + for (const auto &Offset : llvm::enumerate(Loc.Offsets)) { + StringRef NamePiece = + AssociatedSymbol.first->getName()[Offset.index()]; + NamePieces[Offset.index()] = translateOffsetToRelativeRange( + Offset.value(), NamePiece.size(), + RefReplacement.ReplacementString); + } + AssociatedData->AssociatedSymbolOccurrences[SymbolIndex] = + CXSymbolOccurrence{ + NamePieces, (unsigned)Loc.Offsets.size(), + Loc.IsDeclaration + ? CXSymbolOccurrence_ExtractedDeclaration + : CXSymbolOccurrence_ExtractedDeclaration_Reference, + /*IsMacroExpansion=*/0, Index}; + ++SymbolIndex; + } + } + FileSet.NumReplacements -= NumRemoved; + } + } + } +}; + +class RefactoringContinuationWrapper { +public: + std::unique_ptr Continuation; + struct QueryWrapper { + indexer::IndexerQuery *Query; + CXTranslationUnit TU; + std::vector> DeclResults; + unsigned ConsumedResults = 0; + + QueryWrapper(indexer::IndexerQuery *Query, CXTranslationUnit TU) + : Query(Query), TU(TU) {} + }; + SmallVector Queries; + bool IsInitiationTUAbandoned = false; + + RefactoringContinuationWrapper( + std::unique_ptr Continuation, + CXTranslationUnit TU) + : Continuation(std::move(Continuation)) { + Queries.emplace_back(this->Continuation->getASTUnitIndexerQuery(), TU); + assert(Queries.back().Query && "Invalid ast query"); + std::vector AdditionalQueries = + this->Continuation->getAdditionalIndexerQueries(); + for (indexer::IndexerQuery *IQ : AdditionalQueries) + Queries.emplace_back(IQ, TU); + } +}; + +class RefactoringDiagnosticConsumer : public DiagnosticConsumer { + const ASTContext &Context; + DiagnosticConsumer *PreviousClient; + std::unique_ptr PreviousClientPtr; + llvm::SmallVector RenameDiagnostics; + +public: + RefactoringDiagnosticConsumer(ASTContext &Context) : Context(Context) { + PreviousClient = Context.getDiagnostics().getClient(); + PreviousClientPtr = Context.getDiagnostics().takeClient(); + Context.getDiagnostics().setClient(this, /*ShouldOwnClient=*/false); + } + + ~RefactoringDiagnosticConsumer() { + if (PreviousClientPtr) + Context.getDiagnostics().setClient(PreviousClientPtr.release()); + else + Context.getDiagnostics().setClient(PreviousClient, + /*ShouldOwnClient=*/false); + } + + void HandleDiagnostic(DiagnosticsEngine::Level Level, + const Diagnostic &Info) override { + if (DiagnosticIDs::getCategoryNumberForDiag(Info.getID()) == + diag::DiagCat_Rename_Issue) + RenameDiagnostics.push_back(StoredDiagnostic(Level, Info)); + else + assert(false && "Unhandled refactoring category"); + } + + CXDiagnosticSetImpl *createDiags() const { + if (RenameDiagnostics.empty()) + return nullptr; + return cxdiag::createStoredDiags(RenameDiagnostics, Context.getLangOpts()); + } + + CXRefactoringActionSetWithDiagnostics createActionSet() const { + if (RenameDiagnostics.empty()) + return {nullptr, 0}; + CXRefactoringActionWithDiagnostics *Actions = + new CXRefactoringActionWithDiagnostics[1]; + Actions[0].Action = CXRefactor_Rename; + Actions[0].Diagnostics = + cxdiag::createStoredDiags(RenameDiagnostics, Context.getLangOpts()); + return {Actions, 1}; + } +}; + +} // end anonymous namespace + +template +static T withRenamingAction(CXRefactoringAction Action, T DefaultValue, + llvm::function_ref Callback) { + if (!Action) + return DefaultValue; + RenamingAction *Rename = + static_cast(Action)->getRenamingAction(); + if (!Rename) + return DefaultValue; + return Callback(*Rename); +} + +static enum CXIndexerQueryKind +translateDeclPredicate(const indexer::DeclPredicate &Predicate) { + indexer::DeclEntity Entity; + if (Predicate == Entity.isDefined().Predicate) + return CXIndexerQuery_Decl_IsDefined; + return CXIndexerQuery_Unknown; +} + +extern "C" { + +CXString +clang_RefactoringActionType_getName(enum CXRefactoringActionType Action) { + return cxstring::createRef( + getRefactoringActionTypeName(translateRefactoringActionType(Action))); +} + +void clang_RefactoringActionSet_dispose(CXRefactoringActionSet *Set) { + if (Set && Set->Actions) + delete[] Set->Actions; +} + +void clang_RefactoringActionSetWithDiagnostics_dispose( + CXRefactoringActionSetWithDiagnostics *Set) { + if (Set && Set->Actions) { + for (auto &S : llvm::makeArrayRef(Set->Actions, Set->NumActions)) + clang_disposeDiagnosticSet(S.Diagnostics); + delete[] Set->Actions; + } +} + +CXRefactoringOptionSet clang_RefactoringOptionSet_create() { + return new RefactoringOptionSet; +} + +CXRefactoringOptionSet +clang_RefactoringOptionSet_createFromString(const char *String) { + RefactoringOptionSet *Result = new RefactoringOptionSet; + auto Options = RefactoringOptionSet::parse(String); + if (Options) { + *Result = std::move(*Options); + return Result; + } + llvm::handleAllErrors(Options.takeError(), + [](const llvm::StringError &Error) {}); + return clang_RefactoringOptionSet_create(); +} + +void clang_RefactoringOptionSet_add(CXRefactoringOptionSet Set, + enum CXRefactoringOption Option) { + if (!Set) + return; + switch (Option) { + case CXRefactorOption_AvoidTextualMatches: + static_cast(Set)->add( + option::AvoidTextualMatches::getTrue()); + break; + } +} + +CXString clang_RefactoringOptionSet_toString(CXRefactoringOptionSet Set) { + if (!Set) + return cxstring::createNull(); + std::string Result; + llvm::raw_string_ostream OS(Result); + static_cast(Set)->print(OS); + return cxstring::createDup(OS.str()); +} + +void clang_RefactoringOptionSet_dispose(CXRefactoringOptionSet Set) { + if (Set) + delete static_cast(Set); +} + +enum CXErrorCode +clang_Refactoring_findActionsAt(CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, + CXRefactoringOptionSet Options, + CXRefactoringActionSet *OutSet) { + return clang_Refactoring_findActionsWithInitiationFailureDiagnosicsAt( + TU, Location, SelectionRange, Options, OutSet, /*OutFailureSet=*/nullptr); +} + +enum CXErrorCode clang_Refactoring_findActionsWithInitiationFailureDiagnosicsAt( + CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, CXRefactoringOptionSet Options, + CXRefactoringActionSet *OutSet, + CXRefactoringActionSetWithDiagnostics *OutFailureSet) { + LOG_FUNC_SECTION { *Log << TU << ' '; } + if (OutFailureSet) { + OutFailureSet->Actions = nullptr; + OutFailureSet->NumActions = 0; + } + + if (!OutSet) + return CXError_InvalidArguments; + + OutSet->Actions = nullptr; + OutSet->NumActions = 0; + + if (cxtu::isNotUsableTU(TU)) { + LOG_BAD_TU(TU); + return CXError_InvalidArguments; + } + + ASTUnit *CXXUnit = cxtu::getASTUnit(TU); + if (!CXXUnit) + return CXError_InvalidArguments; + + SourceLocation Loc = cxloc::translateSourceLocation(Location); + if (Loc.isInvalid()) + return CXError_InvalidArguments; + + ASTUnit::ConcurrencyCheck Check(*CXXUnit); + (void)Options; // FIXME: handle options + ASTContext &Context = CXXUnit->getASTContext(); + RefactoringDiagnosticConsumer DiagConsumer(Context); + RefactoringActionSet ActionSet = findActionSetAt( + Loc, cxloc::translateCXSourceRange(SelectionRange), Context); + if (OutFailureSet) + *OutFailureSet = DiagConsumer.createActionSet(); + if (ActionSet.Actions.empty()) + return CXError_RefactoringActionUnavailable; + + CXRefactoringActionType *Actions = + new CXRefactoringActionType[ActionSet.Actions.size()]; + OutSet->Actions = Actions; + OutSet->NumActions = ActionSet.Actions.size(); + for (const auto &Action : llvm::enumerate(ActionSet.Actions)) + Actions[Action.index()] = translateRefactoringActionType(Action.value()); + return CXError_Success; +} + +void clang_RefactoringAction_dispose(CXRefactoringAction Action) { + if (Action) + delete static_cast(Action); +} + +CXSourceRange +clang_RefactoringAction_getSourceRangeOfInterest(CXRefactoringAction Action) { + if (Action) { + RefactoringOperation *Operation = + static_cast(Action)->getOperation(); + if (Operation) { + ASTUnit *CXXUnit = cxtu::getASTUnit( + static_cast(Action)->InitiationTU); + if (const Stmt *S = Operation->getTransformedStmt()) { + SourceRange Range = S->getSourceRange(); + if (const Stmt *Last = Operation->getLastTransformedStmt()) + Range.setEnd(Last->getLocEnd()); + return cxloc::translateSourceRange(CXXUnit->getASTContext(), Range); + } else if (const Decl *D = Operation->getTransformedDecl()) { + SourceRange Range = D->getSourceRange(); + if (const Decl *Last = Operation->getLastTransformedDecl()) + Range.setEnd(Last->getLocEnd()); + return cxloc::translateSourceRange(CXXUnit->getASTContext(), Range); + } + } + } + return clang_getNullRange(); +} + +int clang_RefactoringAction_requiresImplementationTU( + CXRefactoringAction Action) { + return withRenamingAction(Action, 0, [](RenamingAction &Action) { + return Action.Operation.requiresImplementationTU(); + }); +} + +CXString clang_RefactoringAction_getUSRThatRequiresImplementationTU( + CXRefactoringAction Action) { + return withRenamingAction( + Action, cxstring::createNull(), [](RenamingAction &Action) { + return Action.getUSRThatRequiresImplementationTU(); + }); +} + +enum CXErrorCode +clang_RefactoringAction_addImplementationTU(CXRefactoringAction Action, + CXTranslationUnit TU) { + if (!Action || !TU) + return CXError_InvalidArguments; + // Prohibit multiple additions of implementation TU. + if (static_cast(Action)->ImplementationTU) + return CXError_Failure; + static_cast(Action)->ImplementationTU = TU; + return CXError_Success; +} + +enum CXErrorCode clang_RefactoringAction_getRefactoringCandidates( + CXRefactoringAction Action, + CXRefactoringCandidateSet *OutRefactoringCandidateSet) { + if (!Action || !OutRefactoringCandidateSet) + return CXError_InvalidArguments; + *OutRefactoringCandidateSet = + static_cast(Action)->getRefactoringCandidates(); + return CXError_Success; +} + +enum CXErrorCode +clang_RefactoringAction_selectRefactoringCandidate(CXRefactoringAction Action, + unsigned Index) { + if (!Action) + return CXError_InvalidArguments; + return static_cast(Action)->selectCandidate(Index); +} + +// TODO: Remove. +enum CXErrorCode clang_Refactoring_initiateActionAt( + CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, enum CXRefactoringActionType ActionType, + CXRefactoringOptionSet Options, CXRefactoringAction *OutAction, + CXString *OutFailureReason) { + CXDiagnosticSet Diags; + CXErrorCode Result = clang_Refactoring_initiateAction( + TU, Location, SelectionRange, ActionType, Options, OutAction, &Diags); + if (OutFailureReason && Diags && clang_getNumDiagnosticsInSet(Diags) == 1) { + CXString Spelling = + clang_getDiagnosticSpelling(clang_getDiagnosticInSet(Diags, 0)); + *OutFailureReason = cxstring::createDup(clang_getCString(Spelling)); + clang_disposeString(Spelling); + } else if (OutFailureReason) + *OutFailureReason = cxstring::createEmpty(); + clang_disposeDiagnosticSet(Diags); + return Result; +} + +enum CXErrorCode clang_Refactoring_initiateAction( + CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, enum CXRefactoringActionType ActionType, + CXRefactoringOptionSet Options, CXRefactoringAction *OutAction, + CXDiagnosticSet *OutDiagnostics) { + if (!OutAction) + return CXError_InvalidArguments; + *OutAction = nullptr; + if (OutDiagnostics) + *OutDiagnostics = nullptr; + + if (cxtu::isNotUsableTU(TU)) { + LOG_BAD_TU(TU); + return CXError_InvalidArguments; + } + + ASTUnit *CXXUnit = cxtu::getASTUnit(TU); + if (!CXXUnit) + return CXError_InvalidArguments; + + SourceLocation Loc = cxloc::translateSourceLocation(Location); + if (Loc.isInvalid()) + return CXError_InvalidArguments; + + ASTUnit::ConcurrencyCheck Check(*CXXUnit); + + (void)Options; // FIXME: handle options + ASTContext &Context = CXXUnit->getASTContext(); + RefactoringDiagnosticConsumer DiagConsumer(Context); + auto Operation = initiateRefactoringOperationAt( + Loc, cxloc::translateCXSourceRange(SelectionRange), Context, + translateRefactoringActionType(ActionType)); + if (!Operation.Initiated) { + if (OutDiagnostics) { + if (!Operation.FailureReason.empty()) { + // TODO: Remove when other actions migrate to diagnostics. + StoredDiagnostic Diag(DiagnosticsEngine::Error, /*ID=*/0, + Operation.FailureReason); + *OutDiagnostics = + cxdiag::createStoredDiags(Diag, Context.getLangOpts()); + } else + *OutDiagnostics = DiagConsumer.createDiags(); + } + return CXError_RefactoringActionUnavailable; + } + if (Operation.RefactoringOp) + *OutAction = new RefactoringAction(std::move(Operation.RefactoringOp), + ActionType, TU); + else + *OutAction = new RefactoringAction( + llvm::make_unique(CXXUnit->getLangOpts(), + std::move(*Operation.SymbolOp)), + TU); + return CXError_Success; +} + +enum CXErrorCode clang_Refactoring_initiateActionOnDecl( + CXTranslationUnit TU, const char *DeclUSR, + enum CXRefactoringActionType ActionType, CXRefactoringOptionSet Options, + CXRefactoringAction *OutAction, CXString *OutFailureReason) { + if (!OutAction) + return CXError_InvalidArguments; + *OutAction = nullptr; + if (OutFailureReason) + *OutFailureReason = cxstring::createNull(); + + if (cxtu::isNotUsableTU(TU)) { + LOG_BAD_TU(TU); + return CXError_InvalidArguments; + } + + ASTUnit *CXXUnit = cxtu::getASTUnit(TU); + if (!CXXUnit) + return CXError_InvalidArguments; + + ASTUnit::ConcurrencyCheck Check(*CXXUnit); + + (void)Options; // FIXME: handle options + auto Operation = initiateRefactoringOperationOnDecl( + DeclUSR, CXXUnit->getASTContext(), + translateRefactoringActionType(ActionType)); + if (!Operation.Initiated) + return CXError_RefactoringActionUnavailable; + // FIXME: Don't dupe with above + if (Operation.RefactoringOp) + *OutAction = new RefactoringAction(std::move(Operation.RefactoringOp), + ActionType, TU); + else + *OutAction = new RefactoringAction( + llvm::make_unique(CXXUnit->getLangOpts(), + std::move(*Operation.SymbolOp)), + TU); + return CXError_Success; +} + +enum CXErrorCode +clang_Refactoring_initiateRenamingOperation(CXRefactoringAction Action) { + if (!Action) + return CXError_InvalidArguments; + RefactoringAction *RefAction = static_cast(Action); + RenamingAction *Rename = RefAction->getRenamingAction(); + if (!Rename) + return CXError_InvalidArguments; + // TODO + return CXError_Success; +} + +CINDEX_LINKAGE +enum CXErrorCode clang_Refactoring_findRenamedCursor( + CXTranslationUnit TU, CXSourceLocation Location, + CXSourceRange SelectionRange, CXCursor *OutCursor) { + if (!OutCursor) + return CXError_InvalidArguments; + + if (cxtu::isNotUsableTU(TU)) { + LOG_BAD_TU(TU); + return CXError_InvalidArguments; + } + + ASTUnit *CXXUnit = cxtu::getASTUnit(TU); + if (!CXXUnit) + return CXError_InvalidArguments; + SourceLocation Loc = cxloc::translateSourceLocation(Location); + if (Loc.isInvalid()) + return CXError_InvalidArguments; + + const NamedDecl *ND = rename::getNamedDeclAt(CXXUnit->getASTContext(), Loc); + if (!ND) { + *OutCursor = cxcursor::MakeCXCursorInvalid(CXCursor_NoDeclFound, TU); + return CXError_RefactoringActionUnavailable; + } + + *OutCursor = cxcursor::MakeCXCursor(ND, TU); + return CXError_Success; +} + +enum CXErrorCode clang_RenamingOperation_setNewName(CXRefactoringAction Action, + const char *NewName) { + return withRenamingAction( + Action, CXError_InvalidArguments, + [=](RenamingAction &Action) -> CXErrorCode { + if (!NewName) + return CXError_InvalidArguments; + StringRef Name = NewName; + if (Name.empty()) + return CXError_InvalidArguments; + return Action.setNewName(Name); + }); +} + +enum CXRefactoringActionType +clang_RefactoringAction_getInitiatedActionType(CXRefactoringAction Action) { + return static_cast(Action)->Type; +} + +unsigned clang_RenamingOperation_getNumSymbols(CXRefactoringAction Action) { + return withRenamingAction(Action, 0, [](RenamingAction &Action) { + return Action.Operation.symbols().size(); + }); +} + +CXString clang_RenamingOperation_getUSRForSymbol(CXRefactoringAction Action, + unsigned Index) { + return withRenamingAction( + Action, cxstring::createNull(), + [=](RenamingAction &Action) { return Action.usrForSymbolAt(Index); }); +} + +CXRenamingResult clang_Refactoring_findRenamedOccurrencesInPrimaryTUs( + CXRefactoringAction Action, const char *const *CommandLineArgs, + int NumCommandLineArgs, CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles) { + if (!Action) + return nullptr; + RefactoringAction *RefAction = static_cast(Action); + RenamingAction *Rename = RefAction->getRenamingAction(); + if (!Rename) + return nullptr; + + // TODO: Handle implementation TU + if (cxtu::isNotUsableTU(RefAction->InitiationTU)) { + LOG_BAD_TU(RefAction->InitiationTU); + return nullptr; + } + + ASTUnit *CXXUnit = cxtu::getASTUnit(RefAction->InitiationTU); + if (!CXXUnit) + return nullptr; + + ASTUnit::ConcurrencyCheck Check(*CXXUnit); + + return Rename->handlePrimaryTU(RefAction->InitiationTU, *CXXUnit); +} + +CXSymbolOccurrencesResult clang_Refactoring_findSymbolOccurrencesInInitiationTU( + CXRefactoringAction Action, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles) { + if (!Action) + return nullptr; + RefactoringAction *RefAction = static_cast(Action); + RenamingAction *Rename = RefAction->getRenamingAction(); + if (!Rename) + return nullptr; + + if (cxtu::isNotUsableTU(RefAction->InitiationTU)) { + LOG_BAD_TU(RefAction->InitiationTU); + return nullptr; + } + + ASTUnit *CXXUnit = cxtu::getASTUnit(RefAction->InitiationTU); + if (!CXXUnit) + return nullptr; + + ASTUnit::ConcurrencyCheck Check(*CXXUnit); + + return Rename->findSymbolsInInitiationTU(RefAction->InitiationTU, *CXXUnit); +} + +CXErrorCode clang_Refactoring_findRenamedOccurrencesInIndexedFile( + const CXRenamedIndexedSymbol *Symbols, unsigned NumSymbols, CXIndex CIdx, + const char *Filename, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles, CXRefactoringOptionSet Options, + CXRenamingResult *OutResult) { + if (!OutResult) + return CXError_InvalidArguments; + if (!Symbols || !NumSymbols || !Filename) + return CXError_InvalidArguments; + return performIndexedFileRename( + llvm::makeArrayRef(Symbols, NumSymbols), StringRef(Filename), + llvm::makeArrayRef(CommandLineArgs, NumCommandLineArgs), CIdx, + MutableArrayRef(UnsavedFiles, NumUnsavedFiles), + Options ? static_cast(Options) : nullptr, + *OutResult); +} + +CXErrorCode clang_Refactoring_findSymbolOccurrencesInIndexedFile( + const CXIndexedSymbol *Symbols, unsigned NumSymbols, CXIndex CIdx, + const char *Filename, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles, CXRefactoringOptionSet Options, + CXSymbolOccurrencesResult *OutResult) { + if (!OutResult) + return CXError_InvalidArguments; + if (!Symbols || !NumSymbols || !Filename) + return CXError_InvalidArguments; + return performIndexedSymbolSearch( + llvm::makeArrayRef(Symbols, NumSymbols), StringRef(Filename), + llvm::makeArrayRef(CommandLineArgs, NumCommandLineArgs), CIdx, + MutableArrayRef(UnsavedFiles, NumUnsavedFiles), + Options ? static_cast(Options) : nullptr, + *OutResult); +} + +unsigned clang_RenamingResult_getNumModifiedFiles(CXRenamingResult Result) { + if (Result) + return static_cast(Result)->getFilenames().size(); + return 0; +} + +void clang_RenamingResult_getResultForFile(CXRenamingResult Result, + unsigned FileIndex, + CXFileRenamingResult *OutResult) { + if (!Result || + FileIndex >= + static_cast(Result)->getFilenames().size()) { + OutResult->Filename = cxstring::createNull(); + OutResult->NumOccurrences = 0; + OutResult->Occurrences = nullptr; + return; + } + auto &RenameResult = *static_cast(Result); + OutResult->Filename = RenameResult.getFilenames()[FileIndex]; + OutResult->NumOccurrences = RenameResult.getOccurrences(FileIndex).size(); + OutResult->Occurrences = RenameResult.getOccurrences(FileIndex).data(); +} + +void clang_RenamingResult_dispose(CXRenamingResult Result) { + if (Result) + delete static_cast(Result); +} + +unsigned clang_SymbolOccurrences_getNumFiles(CXSymbolOccurrencesResult Result) { + if (Result) + return static_cast(Result) + ->getFilenames() + .size(); + return 0; +} + +void clang_SymbolOccurrences_getOccurrencesForFile( + CXSymbolOccurrencesResult Result, unsigned FileIndex, + CXSymbolOccurrencesInFile *OutResult) { + if (!Result || + FileIndex >= static_cast(Result) + ->getFilenames() + .size()) { + OutResult->Filename = cxstring::createNull(); + OutResult->NumOccurrences = 0; + OutResult->Occurrences = nullptr; + return; + } + auto &RenameResult = *static_cast(Result); + OutResult->Filename = RenameResult.getFilenames()[FileIndex]; + OutResult->NumOccurrences = RenameResult.getOccurrences(FileIndex).size(); + OutResult->Occurrences = RenameResult.getOccurrences(FileIndex).data(); +} + +void clang_SymbolOccurrences_dispose(CXSymbolOccurrencesResult Result) { + if (Result) + delete static_cast(Result); +} + +CXRefactoringResult clang_Refactoring_performOperation( + CXRefactoringAction Action, const char *const *CommandLineArgs, + int NumCommandLineArgs, struct CXUnsavedFile *UnsavedFiles, + unsigned NumUnsavedFiles, CXRefactoringOptionSet Options, + CXString *OutFailureReason) { + if (OutFailureReason) + *OutFailureReason = cxstring::createNull(); + if (!Action) + return nullptr; + RefactoringAction *RefAction = static_cast(Action); + if (!RefAction->getOperation()) + return nullptr; + + ASTUnit *CXXUnit = cxtu::getASTUnit(RefAction->InitiationTU); + if (!CXXUnit) + return nullptr; + + ASTUnit::ConcurrencyCheck Check(*CXXUnit); + + RefactoringOptionSet EmptyOptionSet; + const RefactoringOptionSet &OptionSet = + Options ? *static_cast(Options) : EmptyOptionSet; + llvm::Expected Result = RefAction->getOperation()->perform( + CXXUnit->getASTContext(), CXXUnit->getPreprocessor(), OptionSet, + RefAction->SelectedCandidate); + if (!Result) { + if (OutFailureReason) { + (void)!llvm::handleErrors( + Result.takeError(), [&](const RefactoringOperationError &Error) { + *OutFailureReason = cxstring::createDup(Error.FailureReason); + }); + } + return nullptr; + } + return new RefactoringResultWrapper( + Result.get().Replacements, Result.get().AssociatedSymbols, + std::move(Result.get().Continuation), CXXUnit->getASTContext(), + RefAction->InitiationTU); +} + +void clang_RefactoringResult_getReplacements( + CXRefactoringResult Result, + CXRefactoringReplacements_Old *OutReplacements) { + if (!OutReplacements) + return; + if (!Result) { + OutReplacements->FileReplacementSets = nullptr; + OutReplacements->NumFileReplacementSets = 0; + return; + } + *OutReplacements = static_cast(Result)->Replacements; +} + +CXRefactoringReplacements +clang_RefactoringResult_getSourceReplacements(CXRefactoringResult Result) { + if (!Result) + return CXRefactoringReplacements{nullptr, 0}; + return static_cast(Result)->SourceReplacements; +} + +CXRefactoringReplacementAssociatedSymbolOccurrences +clang_RefactoringReplacement_getAssociatedSymbolOccurrences( + CXRefactoringReplacement Replacement) { + if (!Replacement.AssociatedData) + return CXRefactoringReplacementAssociatedSymbolOccurrences{nullptr, 0}; + auto *Data = + static_cast( + Replacement.AssociatedData); + return CXRefactoringReplacementAssociatedSymbolOccurrences{ + Data->AssociatedSymbolOccurrences, Data->NumAssociatedSymbolOccurrences}; +} + +void clang_RefactoringResult_dispose(CXRefactoringResult Result) { + if (Result) + delete static_cast(Result); +} + +CXRefactoringContinuation +clang_RefactoringResult_getContinuation(CXRefactoringResult Result) { + if (!Result) + return nullptr; + auto *Wrapper = static_cast(Result); + if (!Wrapper->Continuation) + return nullptr; + return new RefactoringContinuationWrapper(std::move(Wrapper->Continuation), + Wrapper->TU); +} + +enum CXErrorCode +clang_RefactoringContinuation_loadSerializedIndexerQueryResults( + CXRefactoringContinuation Continuation, const char *Source) { + if (!Continuation) + return CXError_InvalidArguments; + auto *Wrapper = static_cast(Continuation); + llvm::SmallVector Queries; + for (const auto &Query : Wrapper->Queries) + Queries.push_back(Query.Query); + auto Err = indexer::IndexerQuery::loadResultsFromYAML(Source, Queries); + if (Err) { + consumeError(std::move(Err)); + return CXError_Failure; + } + return CXError_Success; +} + +unsigned clang_RefactoringContinuation_getNumIndexerQueries( + CXRefactoringContinuation Continuation) { + if (Continuation) + return static_cast(Continuation) + ->Queries.size(); + return 0; +} + +CXIndexerQuery clang_RefactoringContinuation_getIndexerQuery( + CXRefactoringContinuation Continuation, unsigned Index) { + if (!Continuation) + return nullptr; + auto *Wrapper = static_cast(Continuation); + if (Index >= Wrapper->Queries.size()) + return nullptr; + return &Wrapper->Queries[Index]; +} + +void clang_RefactoringContinuation_finalizeEvaluationInInitationTU( + CXRefactoringContinuation Continuation) { + if (!Continuation) + return; + auto *Wrapper = static_cast(Continuation); + Wrapper->Queries.clear(); + Wrapper->Continuation->persistTUSpecificState(); + Wrapper->IsInitiationTUAbandoned = true; +} + +CXRefactoringResult clang_RefactoringContinuation_continueOperationInTU( + CXRefactoringContinuation Continuation, CXTranslationUnit TU, + CXString *OutFailureReason) { + if (!Continuation || !TU) + return nullptr; + ASTUnit *CXXUnit = cxtu::getASTUnit(TU); + if (!CXXUnit) + return nullptr; + ASTUnit::ConcurrencyCheck Check(*CXXUnit); + const auto *Wrapper = + static_cast(Continuation); + if (!Wrapper->IsInitiationTUAbandoned) { + // FIXME: We can avoid conversions of TU-specific state if the given TU is + // the same as the initiation TU. + clang_RefactoringContinuation_finalizeEvaluationInInitationTU(Continuation); + } + auto Result = + Wrapper->Continuation->runInExternalASTUnit(CXXUnit->getASTContext()); + if (!Result) { + if (OutFailureReason) { + (void)!llvm::handleErrors( + Result.takeError(), [&](const RefactoringOperationError &Error) { + *OutFailureReason = cxstring::createDup(Error.FailureReason); + }); + } + return nullptr; + } + return new RefactoringResultWrapper( + Result.get().Replacements, Result.get().AssociatedSymbols, + std::move(Result.get().Continuation), CXXUnit->getASTContext(), TU); +} + +void clang_RefactoringContinuation_dispose( + CXRefactoringContinuation Continuation) { + if (Continuation) + delete static_cast(Continuation); +} + +enum CXIndexerQueryKind clang_IndexerQuery_getKind(CXIndexerQuery Query) { + if (!Query) + return CXIndexerQuery_Unknown; + const auto *IQ = + static_cast(Query)->Query; + if (const auto *DQ = dyn_cast(IQ)) { + const indexer::detail::DeclPredicateNode &Node = DQ->getPredicateNode(); + if (const auto *NP = + dyn_cast(&Node)) + return translateDeclPredicate( + cast(NP->getChild()) + .getPredicate()); + return translateDeclPredicate( + cast(Node).getPredicate()); + } else if (isa(IQ)) + return CXIndexerQuery_Decl_FileThatShouldImplement; + return CXIndexerQuery_Unknown; +} + +unsigned clang_IndexerQuery_getNumCursors(CXIndexerQuery Query) { + if (!Query) + return 0; + const auto *IQ = + static_cast(Query)->Query; + if (const auto *DQ = dyn_cast(IQ)) + return DQ->getInputs().size(); + else if (isa(IQ)) + return 1; + return 0; +} + +CXCursor clang_IndexerQuery_getCursor(CXIndexerQuery Query, + unsigned CursorIndex) { + if (Query) { + const auto *Wrapper = + static_cast(Query); + const indexer::IndexerQuery *IQ = Wrapper->Query; + CXTranslationUnit TU = Wrapper->TU; + if (const auto *DQ = dyn_cast(IQ)) { + if (CursorIndex < DQ->getInputs().size()) + return cxcursor::MakeCXCursor(DQ->getInputs()[CursorIndex], TU); + } else if (const auto *ASTQuery = dyn_cast< + indexer::ASTUnitForImplementationOfDeclarationQuery>(IQ)) { + if (CursorIndex == 0) + return cxcursor::MakeCXCursor(ASTQuery->getDecl(), TU); + } + } + return cxcursor::MakeCXCursorInvalid(CXCursor_InvalidCode); +} + +enum CXIndexerQueryAction +clang_IndexerQuery_consumeIntResult(CXIndexerQuery Query, unsigned CursorIndex, + int Value) { + if (!Query) + return CXIndexerQueryAction_None; + auto *Wrapper = + static_cast(Query); + auto *DQ = dyn_cast(Wrapper->Query); + if (!DQ) + return CXIndexerQueryAction_None; + if (CursorIndex >= DQ->getInputs().size() || + Wrapper->ConsumedResults == DQ->getInputs().size()) + return CXIndexerQueryAction_None; + if (Wrapper->DeclResults.empty()) + Wrapper->DeclResults.resize(DQ->getInputs().size(), + PersistentDeclRef::create(nullptr)); + // Filter the declarations! + bool IsNot = false; + if (isa(DQ->getPredicateNode())) + IsNot = true; + Wrapper->DeclResults[CursorIndex] = PersistentDeclRef::create( + (IsNot ? !Value : !!Value) ? DQ->getInputs()[CursorIndex] : nullptr); + Wrapper->ConsumedResults++; + if (Wrapper->ConsumedResults == Wrapper->DeclResults.size()) { + // We've received all the results, pass them back to the query. + DQ->setOutput(std::move(Wrapper->DeclResults)); + } + return CXIndexerQueryAction_None; +} + +enum CXIndexerQueryAction +clang_IndexerQuery_consumeFileResult(CXIndexerQuery Query, unsigned CursorIndex, + const char *Filename) { + if (!Query || !Filename) + return CXIndexerQueryAction_None; + auto *IQ = + static_cast(Query)->Query; + if (auto *ASTQuery = + dyn_cast(IQ)) { + if (CursorIndex != 0) + return CXIndexerQueryAction_None; + ASTQuery->setResult(PersistentFileID(Filename)); + return CXIndexerQueryAction_RunContinuationInTUThatHasThisFile; + } + return CXIndexerQueryAction_None; +} +} diff --git a/clang/tools/libclang/libclang.exports b/clang/tools/libclang/libclang.exports index 187d4749ebc13..4f2335ce77f35 100644 --- a/clang/tools/libclang/libclang.exports +++ b/clang/tools/libclang/libclang.exports @@ -350,3 +350,56 @@ clang_EvalResult_isUnsignedInt clang_EvalResult_getAsDouble clang_EvalResult_getAsStr clang_EvalResult_dispose +clang_RefactoringActionType_getName +clang_RefactoringActionSet_dispose +clang_RefactoringActionSetWithDiagnostics_dispose +clang_RefactoringOptionSet_create +clang_RefactoringOptionSet_createFromString +clang_RefactoringOptionSet_add +clang_RefactoringOptionSet_toString +clang_RefactoringOptionSet_dispose +clang_Refactoring_findActionsAt +clang_Refactoring_findActionsWithInitiationFailureDiagnosicsAt +clang_RefactoringAction_dispose +clang_RefactoringAction_getSourceRangeOfInterest +clang_RefactoringAction_getInitiatedActionType +clang_RefactoringAction_requiresImplementationTU +clang_RefactoringAction_getUSRThatRequiresImplementationTU +clang_RefactoringAction_addImplementationTU +clang_RefactoringAction_getRefactoringCandidates +clang_RefactoringAction_selectRefactoringCandidate +clang_Refactoring_initiateActionAt +clang_Refactoring_initiateAction +clang_Refactoring_initiateActionOnDecl +clang_Refactoring_initiateRenamingOperation +clang_RenamingOperation_setNewName +clang_RenamingOperation_getNumSymbols +clang_RenamingOperation_getUSRForSymbol +clang_Refactoring_findRenamedCursor +clang_Refactoring_findRenamedOccurrencesInPrimaryTUs +clang_Refactoring_findSymbolOccurrencesInInitiationTU +clang_Refactoring_findRenamedOccurrencesInIndexedFile +clang_Refactoring_findSymbolOccurrencesInIndexedFile +clang_RenamingResult_getNumModifiedFiles +clang_RenamingResult_getResultForFile +clang_RenamingResult_dispose +clang_SymbolOccurrences_getNumFiles +clang_SymbolOccurrences_getOccurrencesForFile +clang_SymbolOccurrences_dispose +clang_Refactoring_performOperation +clang_RefactoringResult_getReplacements +clang_RefactoringResult_getSourceReplacements +clang_RefactoringReplacement_getAssociatedSymbolOccurrences +clang_RefactoringResult_getContinuation +clang_RefactoringResult_dispose +clang_RefactoringContinuation_loadSerializedIndexerQueryResults +clang_RefactoringContinuation_getNumIndexerQueries +clang_RefactoringContinuation_getIndexerQuery +clang_RefactoringContinuation_finalizeEvaluationInInitationTU +clang_RefactoringContinuation_continueOperationInTU +clang_RefactoringContinuation_dispose +clang_IndexerQuery_getKind +clang_IndexerQuery_getNumCursors +clang_IndexerQuery_getCursor +clang_IndexerQuery_consumeIntResult +clang_IndexerQuery_consumeFileResult diff --git a/clang/unittests/Tooling/RefactoringTest.cpp b/clang/unittests/Tooling/RefactoringTest.cpp index 495ac755b39d9..10849af7ac74f 100644 --- a/clang/unittests/Tooling/RefactoringTest.cpp +++ b/clang/unittests/Tooling/RefactoringTest.cpp @@ -25,6 +25,9 @@ #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Refactor/IndexerQuery.h" +#include "clang/Tooling/Refactor/RefactoringOperation.h" +#include "clang/Tooling/Refactor/RefactoringOptions.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Refactoring/AtomicChange.h" #include "clang/Tooling/Tooling.h" @@ -1090,6 +1093,51 @@ TEST(DeduplicateByFileTest, NonExistingFilePath) { EXPECT_TRUE(FileToReplaces.empty()); } +namespace { +struct TestRefactoringValueOption final : RefactoringOption { + int Value; + TestRefactoringValueOption(int Value) : Value(Value) {} + + static constexpr const char *Name = "test value option"; +}; +} // end anonymous namespace + +TEST(RefactoringOptionSet, AddGet) { + RefactoringOptionSet Options; + const TestRefactoringValueOption Kind(21); + const TestRefactoringValueOption DefaultKind(42); + + EXPECT_EQ(Options.get(), nullptr); + EXPECT_EQ(Options.get(DefaultKind).Value, DefaultKind.Value); + + Options.add(Kind); + + auto *Ptr = Options.get(); + ASSERT_TRUE(Ptr); + EXPECT_EQ(Ptr->Value, Kind.Value); + EXPECT_EQ(Options.get(DefaultKind).Value, Kind.Value); +} + +namespace { +struct TestRefactoringOption final : RefactoringOption { + int &Counter; + TestRefactoringOption(int &Counter) : Counter(Counter) {} + ~TestRefactoringOption() { ++Counter; } + + static constexpr const char *Name = "test option"; +}; +} // end anonymous namespace + +TEST(RefactoringOptionSet, OptionDestroyed) { + int Counter = 0; + { + RefactoringOptionSet Options; + Options.add(TestRefactoringOption(Counter)); + Options.add(TestRefactoringOption(Counter)); + } + EXPECT_EQ(Counter, 3); +} + class AtomicChangeTest : public ::testing::Test { protected: void SetUp() override { @@ -1291,5 +1339,127 @@ TEST_F(AtomicChangeTest, InsertAfterWithInvalidLocation) { Replacement(Context.Sources, SourceLocation(), 0, "b"))); } +namespace { + +class RefactoringOperationTest { + RefactoringActionType Type; + unsigned Line, Column; + bool Success = true; + std::function ResultHandler; + +public: + RefactoringOperationTest( + RefactoringActionType Type, unsigned Line, unsigned Column, + std::function ResultHandler) + : Type(Type), Line(Line), Column(Column), + ResultHandler(std::move(ResultHandler)) {} + + bool runOver(StringRef Code) { + return runToolOnCode(new TestAction(this), Code); + } + + bool succeeded() const { return Success; } + + void run() { + assert(PP && Context && "Invalid state"); + SourceLocation Loc = Context->getSourceManager().translateLineCol( + Context->getSourceManager().getMainFileID(), Line, Column); + if (Loc.isInvalid()) { + Success = false; + return; + } + RefactoringOperationResult Op = + initiateRefactoringOperationAt(Loc, SourceRange(), *Context, Type); + if (!Op.Initiated) { + Success = false; + return; + } + RefactoringOptionSet Options; + llvm::Expected Result = + Op.RefactoringOp->perform(*Context, *PP, Options); + if (!Result) { + (void)!llvm::handleErrors( + Result.takeError(), + [&](const RefactoringOperationError &Error) { Success = false; }); + return; + } + ResultHandler(Result.get()); + } + +protected: + clang::Preprocessor *PP; + clang::ASTContext *Context; + +private: + class TestConsumer : public clang::ASTConsumer { + public: + TestConsumer(RefactoringOperationTest *Test) : Test(Test) {} + + void HandleTranslationUnit(clang::ASTContext &Context) override { + Test->run(); + } + + private: + RefactoringOperationTest *Test; + }; + + class TestAction : public clang::ASTFrontendAction { + public: + TestAction(RefactoringOperationTest *Test) : Test(Test) {} + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + llvm::StringRef) override { + Test->PP = &Compiler.getPreprocessor(); + Test->Context = &Compiler.getASTContext(); + return llvm::make_unique(Test); + } + + private: + RefactoringOperationTest *Test; + }; +}; + +} // end anonymous namespace + +TEST(RefactoringContinuation, ContinuationAndQueriesExist) { + using namespace clang::tooling::indexer; + using namespace clang::tooling::indexer::detail; + RefactoringOperationTest Test( + RefactoringActionType::ImplementDeclaredMethods, 2, 1, + [](const RefactoringResult &Result) { + EXPECT_TRUE(Result.Replacements.empty()); + ASSERT_NE(Result.Continuation, nullptr); + RefactoringContinuation &Continuation = *Result.Continuation; + + ASTProducerQuery *ASTQuery = Continuation.getASTUnitIndexerQuery(); + ASSERT_NE(ASTQuery, nullptr); + EXPECT_TRUE(isa(ASTQuery)); + EXPECT_TRUE(isa(ASTQuery)); + EXPECT_FALSE(isa(ASTQuery)); + + auto AdditionalQueries = Continuation.getAdditionalIndexerQueries(); + ASSERT_EQ(AdditionalQueries.size(), (size_t)1); + EXPECT_FALSE(isa(AdditionalQueries[0])); + EXPECT_FALSE(isa( + AdditionalQueries[0])); + ASSERT_TRUE(isa(AdditionalQueries[0])); + + const DeclPredicateNode &Predicate = + cast(AdditionalQueries[0])->getPredicateNode(); + ASSERT_TRUE(isa(Predicate)); + const DeclPredicateNode &SubPredicate = + cast(Predicate).getChild(); + ASSERT_TRUE(isa(SubPredicate)); + EXPECT_EQ(cast(SubPredicate).getPredicate(), + DeclEntity().isDefined().Predicate); + + ASTQuery->invalidateTUSpecificState(); + AdditionalQueries[0]->invalidateTUSpecificState(); + }); + Test.runOver("class Foo {\nvoid method();\n};\n"); + EXPECT_TRUE(Test.succeeded()); +} + } // end namespace tooling } // end namespace clang diff --git a/clang/unittests/libclang/LibclangTest.cpp b/clang/unittests/libclang/LibclangTest.cpp index f2a96d6be6c17..d5a9cbf1222fa 100644 --- a/clang/unittests/libclang/LibclangTest.cpp +++ b/clang/unittests/libclang/LibclangTest.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "clang-c/Index.h" +#include "clang-c/Refactor.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" @@ -572,3 +573,71 @@ TEST_F(LibclangReparseTest, clang_parseTranslationUnit2FullArgv) { EXPECT_EQ(0U, clang_getNumDiagnostics(ClangTU)); DisplayDiagnostics(); } + +TEST(libclang, RefactoringAction) { + std::string Name = + clang_getCString(clang_RefactoringActionType_getName(CXRefactor_Rename)); + EXPECT_EQ(Name, "Rename"); +} + +TEST_F(LibclangParseTest, RefactoringFindRenamedCursor) { + std::string Filename = "test.cpp"; + WriteFile(Filename, "int renamable = 0;\n"); + + ClangTU = clang_parseTranslationUnit(Index, Filename.c_str(), nullptr, 0, + nullptr, 0, TUFlags); + CXSourceLocation Loc = clang_getLocation( + ClangTU, clang_getFile(ClangTU, Filename.c_str()), 1, 5); + CXSourceRange Range = clang_getRange(Loc, Loc); + CXCursor Cursor; + EXPECT_EQ(CXError_Success, + clang_Refactoring_findRenamedCursor(ClangTU, Loc, Range, &Cursor)); + EXPECT_EQ(Cursor.kind, CXCursor_VarDecl); +} + +TEST_F(LibclangParseTest, RefactoringRenameIndexedUnsavedFiles) { + std::string Filename = "test.cpp"; + std::string PartialSource = "class Test { };\n"; + WriteFile(Filename, PartialSource); + std::string FullSource = PartialSource + "Test t;\n"; + + CXIndexedSymbolLocation IndexedLocations[2] = { + {{1, 7}, CXCursor_DeclRefExpr}, {{2, 1}, CXCursor_DeclRefExpr}}; + CXIndexedSymbol Symbols[1] = { + {IndexedLocations, 2, CXCursor_DeclRefExpr, /*Name=*/"Test"}}; + + CXIndex Idx = clang_createIndex(0, 0); + + auto test = [&](CXUnsavedFile *File = nullptr) -> CXSymbolOccurrencesInFile { + CXSymbolOccurrencesResult Result; + CXErrorCode Err = clang_Refactoring_findSymbolOccurrencesInIndexedFile( + Symbols, 1, Idx, Filename.c_str(), nullptr, 0, File, File ? 1 : 0, + /*Options=*/nullptr, &Result); + EXPECT_EQ(CXError_Success, Err); + unsigned NumFiles = clang_SymbolOccurrences_getNumFiles(Result); + EXPECT_EQ(NumFiles, 1u); + CXSymbolOccurrencesInFile Occurrences; + clang_SymbolOccurrences_getOccurrencesForFile(Result, 0, &Occurrences); + return Occurrences; + }; + CXSymbolOccurrencesInFile FileOccurrences = test(); + EXPECT_EQ(FileOccurrences.NumOccurrences, 1u); + EXPECT_EQ(clang_getCString(FileOccurrences.Filename), Filename); + EXPECT_EQ(FileOccurrences.Occurrences[0].NumNamePieces, 1u); + EXPECT_EQ(FileOccurrences.Occurrences[0].NamePieces[0].Begin.Line, 1u); + EXPECT_EQ(FileOccurrences.Occurrences[0].NamePieces[0].Begin.Column, 7u); + + CXUnsavedFile UnsavedFile = {Filename.c_str(), FullSource.c_str(), + FullSource.size()}; + CXSymbolOccurrencesInFile UnsavedFileOccurrences = test(&UnsavedFile); + EXPECT_EQ(UnsavedFileOccurrences.NumOccurrences, 2u); + EXPECT_EQ(clang_getCString(UnsavedFileOccurrences.Filename), Filename); + EXPECT_EQ(UnsavedFileOccurrences.Occurrences[0].NumNamePieces, 1u); + EXPECT_EQ(UnsavedFileOccurrences.Occurrences[0].NamePieces[0].Begin.Line, 1u); + EXPECT_EQ(UnsavedFileOccurrences.Occurrences[0].NamePieces[0].Begin.Column, + 7u); + EXPECT_EQ(UnsavedFileOccurrences.Occurrences[1].NumNamePieces, 1u); + EXPECT_EQ(UnsavedFileOccurrences.Occurrences[1].NamePieces[0].Begin.Line, 2u); + EXPECT_EQ(UnsavedFileOccurrences.Occurrences[1].NamePieces[0].Begin.Column, + 1u); +} From 18694022ed9988793305f4bf559176561c464ae4 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Thu, 15 Jun 2017 17:04:51 -0700 Subject: [PATCH 157/585] Remove .rej file accidentally committed in the previous commit apple-llvm-split-commit: 1fa921f1e3fe009ac6dbf374fae13ac82721e102 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticIDs.h.rej | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 clang/include/clang/Basic/DiagnosticIDs.h.rej diff --git a/clang/include/clang/Basic/DiagnosticIDs.h.rej b/clang/include/clang/Basic/DiagnosticIDs.h.rej deleted file mode 100644 index f81c0605abf59..0000000000000 --- a/clang/include/clang/Basic/DiagnosticIDs.h.rej +++ /dev/null @@ -1,18 +0,0 @@ -*************** -*** 37,43 **** - DIAG_START_COMMENT = DIAG_START_AST + 110, - DIAG_START_SEMA = DIAG_START_COMMENT + 100, - DIAG_START_ANALYSIS = DIAG_START_SEMA + 3500, -- DIAG_UPPER_LIMIT = DIAG_START_ANALYSIS + 100 - }; - - class CustomDiagInfo; ---- 37,44 ---- - DIAG_START_COMMENT = DIAG_START_AST + 110, - DIAG_START_SEMA = DIAG_START_COMMENT + 100, - DIAG_START_ANALYSIS = DIAG_START_SEMA + 3500, -+ DIAG_START_REFACTORING = DIAG_START_ANALYSIS + 100, -+ DIAG_UPPER_LIMIT = DIAG_START_REFACTORING + 100 - }; - - class CustomDiagInfo; From 35854d47b627af2900228564fb23b896040db54a Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Wed, 21 Jun 2017 10:56:08 +0100 Subject: [PATCH 158/585] [refactor] Add missing internal change to include/clang/Basic/DiagnosticIDs.h This change ensures that 'upstream-with-swift' can build again rdar://32878541 apple-llvm-split-commit: 94152766d32a3a9de8f9c891c6e82a659001137c apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/DiagnosticIDs.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticIDs.h b/clang/include/clang/Basic/DiagnosticIDs.h index 22e95fee95381..b964595076eae 100644 --- a/clang/include/clang/Basic/DiagnosticIDs.h +++ b/clang/include/clang/Basic/DiagnosticIDs.h @@ -37,7 +37,8 @@ namespace clang { DIAG_START_COMMENT = DIAG_START_AST + 110, DIAG_START_SEMA = DIAG_START_COMMENT + 100, DIAG_START_ANALYSIS = DIAG_START_SEMA + 4000, - DIAG_UPPER_LIMIT = DIAG_START_ANALYSIS + 100 + DIAG_START_REFACTORING = DIAG_START_ANALYSIS + 100, + DIAG_UPPER_LIMIT = DIAG_START_REFACTORING + 100 }; class CustomDiagInfo; From 912350b72a8d58da22c7b3e6f0a42a5c86dfb9a7 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 20 Jun 2017 13:43:17 +0100 Subject: [PATCH 159/585] [refactor] "Wrap in NSLocalizedString" should work with macro arguments rdar://32015671 apple-llvm-split-commit: 31fe591c33b051dca234ed3928df6dbdcea396a5 apple-llvm-split-dir: clang/ --- .../Tooling/Refactor/LocalizeObjCStringLiteral.cpp | 8 ++++++-- .../LocalizeObjCStringLiteral/localize-objc-perform.m | 11 +++++++++++ clang/tools/libclang/CRefactor.cpp | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/clang/lib/Tooling/Refactor/LocalizeObjCStringLiteral.cpp b/clang/lib/Tooling/Refactor/LocalizeObjCStringLiteral.cpp index d2ad11ec15366..08cf41dbbb1c1 100644 --- a/clang/lib/Tooling/Refactor/LocalizeObjCStringLiteral.cpp +++ b/clang/lib/Tooling/Refactor/LocalizeObjCStringLiteral.cpp @@ -69,11 +69,15 @@ LocalizeObjCStringLiteralOperation::perform(ASTContext &Context, const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { std::vector Replacements; - SourceLocation LocStart = E->getLocStart(); + // TODO: New API: Replace by something like Node.wrap("NSLocalizedString(", ", + // @""") + SourceLocation LocStart = + Context.getSourceManager().getSpellingLoc(E->getLocStart()); Replacements.emplace_back(SourceRange(LocStart, LocStart), StringRef("NSLocalizedString(")); SourceLocation LocEnd = getPreciseTokenLocEnd( - E->getLocEnd(), Context.getSourceManager(), Context.getLangOpts()); + Context.getSourceManager().getSpellingLoc(E->getLocEnd()), + Context.getSourceManager(), Context.getLangOpts()); Replacements.emplace_back(SourceRange(LocEnd, LocEnd), StringRef(", @\"\")")); return std::move(Replacements); } diff --git a/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-perform.m b/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-perform.m index 8c0de42faa39a..96095deffc4e5 100644 --- a/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-perform.m +++ b/clang/test/Refactor/LocalizeObjCStringLiteral/localize-objc-perform.m @@ -7,3 +7,14 @@ void perform() { // CHECK1-NEXT: ", @"")" [[@LINE-3]]:30 -> [[@LINE-3]]:30 // RUN: clang-refactor-test perform -action localize-objc-string-literal -at=%s:4:22 %s | FileCheck --check-prefix=CHECK1 %s // RUN: clang-refactor-test perform -action localize-objc-string-literal -selected=%s:4:23-4:30 %s | FileCheck --check-prefix=CHECK1 %s + +#define MACRO(x, y) x + +void performInMacroArgument() { + // macro-arg: +2:9 + // macro-arg-range-begin: +1:9 + MACRO(@"hello", 1); // CHECK2: "NSLocalizedString(" [[@LINE]]:9 -> [[@LINE]]:9 + // macro-arg-range-end: -1:17 // CHECK2: ", @"")" [[@LINE-1]]:17 -> [[@LINE-1]]:17 +} +// RUN: clang-refactor-test perform -action localize-objc-string-literal -at=macro-arg %s | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-refactor-test perform -action localize-objc-string-literal -selected=macro-arg-range %s | FileCheck --check-prefix=CHECK2 %s diff --git a/clang/tools/libclang/CRefactor.cpp b/clang/tools/libclang/CRefactor.cpp index 7331c3a53abb0..098e685f880e3 100644 --- a/clang/tools/libclang/CRefactor.cpp +++ b/clang/tools/libclang/CRefactor.cpp @@ -859,6 +859,7 @@ class RefactoringResultWrapper { SourceLocation Loc = Replacement.value().Range.getBegin(); const std::pair DecomposedLocation = SM.getDecomposedLoc(Loc); + assert(DecomposedLocation.first.isValid() && "Invalid file!"); const FileEntry *Entry = SM.getFileEntryForID(DecomposedLocation.first); FilesToReplacements.try_emplace(Entry, std::vector()) .first->second.push_back(Replacement.index()); From eaccec298b5f24cc69d07e60c38a58fa7e7b98fd Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 20 Jun 2017 14:03:24 +0100 Subject: [PATCH 160/585] [refactor] "Fill in missing switch cases" should work with macro arguments rdar://32015671 apple-llvm-split-commit: 06e56e53a08d1221fce62d75b33293bc5f5a1210 apple-llvm-split-dir: clang/ --- clang/lib/Edit/FillInMissingSwitchEnumCases.cpp | 3 ++- .../FillInEnumSwitchCases/fill-in-cases-perform.cpp | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp b/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp index e2dcdb1f36d65..ec10c1ebd451d 100644 --- a/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp +++ b/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp @@ -136,7 +136,8 @@ void edit::fillInMissingSwitchEnumCases( else InsertionLoc = Switch->getBody()->getLocEnd(); } - Consumer(FixItHint::CreateInsertion(InsertionLoc, OS.str())); + Consumer(FixItHint::CreateInsertion( + Context.getSourceManager().getSpellingLoc(InsertionLoc), OS.str())); }; // Determine which enum cases are uncovered. diff --git a/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-perform.cpp b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-perform.cpp index 76ab1c2e0a547..70aa585cad20c 100644 --- a/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-perform.cpp +++ b/clang/test/Refactor/FillInEnumSwitchCases/fill-in-cases-perform.cpp @@ -78,3 +78,16 @@ void perform1(PREFIX Color c) { // RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 %s -std=c++11 -D NESTED1 -D NESTED1NS -D ENUMCLASS | FileCheck --check-prefix=CHECK6 %s // RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 -at=%s:54:3 -at=%s:63:3 %s -std=c++11 -D NESTEDANON | FileCheck --check-prefix=CHECK1 %s // RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=%s:43:3 -at=%s:54:3 -at=%s:63:3 %s -std=c++11 -D NESTEDANON -D ENUMCLASS | FileCheck --check-prefix=CHECK2 %s + +#define MACROARG(X) X + +void macroArg(PREFIX Color c) { + // macro-arg: +2:12 + // macro-arg-range-begin: +1:12 + MACROARG(switch (c) { + }); // MACRO-ARG: "case Black:\n<#code#>\nbreak;\ncase Blue:\n<#code#>\nbreak;\ncase White:\n<#code#>\nbreak;\ncase Gold:\n<#code#>\nbreak;\n" [[@LINE]] + // macro-arg-range-end: -1:4 +} + +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -at=macro-arg %s | FileCheck --check-prefix=MACRO-ARG %s +// RUN: clang-refactor-test perform -action fill-in-enum-switch-cases -selected=macro-arg-range %s | FileCheck --check-prefix=MACRO-ARG %s From 08d72d76752d3e31855f4d0b5ddc3c89912648e5 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 20 Jun 2017 14:22:55 +0100 Subject: [PATCH 161/585] [refactor] "Convert to switch" should work with macro arguments rdar://32015671 apple-llvm-split-commit: 2590c521bb0ca983c3bc11d9d8394542f47143c5 apple-llvm-split-dir: clang/ --- .../Tooling/Refactor/IfSwitchConversion.cpp | 40 +++++++++++-------- .../if-switch-conversion-perform.cpp | 14 +++++++ 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/clang/lib/Tooling/Refactor/IfSwitchConversion.cpp b/clang/lib/Tooling/Refactor/IfSwitchConversion.cpp index 1a284b8d49814..5ce39552876ab 100644 --- a/clang/lib/Tooling/Refactor/IfSwitchConversion.cpp +++ b/clang/lib/Tooling/Refactor/IfSwitchConversion.cpp @@ -302,8 +302,9 @@ struct CasePlacement { CasePlacement(const IfStmt *If, const SourceManager &SM, bool AreBracesNeeded) { - CaseStartLoc = isa(If->getThen()) ? If->getThen()->getLocEnd() - : If->getElseLoc(); + CaseStartLoc = SM.getSpellingLoc(isa(If->getThen()) + ? If->getThen()->getLocEnd() + : If->getElseLoc()); SourceLocation BodyEndLoc = findLastNonCompoundLocation(If->getThen()); NeedsNewLine = BodyEndLoc.isValid() ? areOnSameLine(CaseStartLoc, BodyEndLoc, SM) @@ -343,16 +344,18 @@ addCaseReplacements(const IfStmt *If, const CasePlacement &CaseInfo, gatherCaseValues(If->getCond(), CaseValues); assert(!CaseValues.empty()); Replacements.emplace_back( - SourceRange(CaseInfo.CaseStartLoc, CaseValues[0]->getLocStart()), + SourceRange(CaseInfo.CaseStartLoc, + SM.getSpellingLoc(CaseValues[0]->getLocStart())), CaseInfo.getCaseReplacementString()); - SourceLocation PrevCaseEnd = - getPreciseTokenLocEnd(CaseValues[0]->getLocEnd(), SM, LangOpts); + SourceLocation PrevCaseEnd = getPreciseTokenLocEnd( + SM.getSpellingLoc(CaseValues[0]->getLocEnd()), SM, LangOpts); for (const Expr *CaseValue : llvm::makeArrayRef(CaseValues).drop_front()) { Replacements.emplace_back( - SourceRange(PrevCaseEnd, CaseValue->getLocStart()), + SourceRange(PrevCaseEnd, SM.getSpellingLoc(CaseValue->getLocStart())), StringRef(":\ncase ")); - PrevCaseEnd = getPreciseTokenLocEnd(CaseValue->getLocEnd(), SM, LangOpts); + PrevCaseEnd = getPreciseTokenLocEnd( + SM.getSpellingLoc(CaseValue->getLocEnd()), SM, LangOpts); } AreBracesNeeded = areBracesNeeded(If->getThen()); @@ -361,12 +364,13 @@ addCaseReplacements(const IfStmt *If, const CasePlacement &CaseInfo, Replacements.emplace_back( SourceRange( PrevCaseEnd, - getPreciseTokenLocEnd(If->getThen()->getLocStart(), SM, LangOpts)), + getPreciseTokenLocEnd( + SM.getSpellingLoc(If->getThen()->getLocStart()), SM, LangOpts)), ColonReplacement); } else { // Find the location of the if's ')' - SourceLocation End = - findClosingParenLocEnd(If->getCond()->getLocEnd(), SM, LangOpts); + SourceLocation End = findClosingParenLocEnd( + SM.getSpellingLoc(If->getCond()->getLocEnd()), SM, LangOpts); if (!End.isValid()) return llvm::make_error( "couldn't find the location of ')'"); @@ -388,13 +392,14 @@ IfSwitchConversionOperation::perform(ASTContext &Context, // should be preserved. const Expr *LHS = getConditionFirstLHS(If->getCond()); assert(LHS && "Missing == expression"); - Replacements.emplace_back(SourceRange(If->getLocStart(), LHS->getLocStart()), + Replacements.emplace_back(SourceRange(SM.getSpellingLoc(If->getLocStart()), + SM.getSpellingLoc(LHS->getLocStart())), StringRef("switch (")); bool AreBracesNeeded = false; if (auto Error = addCaseReplacements( - If, - CasePlacement(getPreciseTokenLocEnd(LHS->getLocEnd(), SM, LangOpts)), + If, CasePlacement(getPreciseTokenLocEnd( + SM.getSpellingLoc(LHS->getLocEnd()), SM, LangOpts)), AreBracesNeeded, Replacements, SM, LangOpts)) return std::move(Error); @@ -415,8 +420,10 @@ IfSwitchConversionOperation::perform(ASTContext &Context, if (const Stmt *Else = CurrentIf->getElse()) { CasePlacement DefaultInfo(CurrentIf, SM, AreBracesNeeded); AreBracesNeeded = areBracesNeeded(Else); + SourceLocation EndLoc = getPreciseTokenLocEnd( - isa(Else) ? Else->getLocStart() : CurrentIf->getElseLoc(), + SM.getSpellingLoc(isa(Else) ? Else->getLocStart() + : CurrentIf->getElseLoc()), SM, LangOpts); Replacements.emplace_back(SourceRange(DefaultInfo.CaseStartLoc, EndLoc), DefaultInfo.getCaseReplacementString( @@ -450,11 +457,12 @@ IfSwitchConversionOperation::perform(ASTContext &Context, OS << "}\n"; } - if (!OS.str().empty()) + if (!OS.str().empty()) { + TerminatingReplacementLoc = SM.getSpellingLoc(TerminatingReplacementLoc); Replacements.emplace_back( SourceRange(TerminatingReplacementLoc, TerminatingReplacementLoc), std::move(OS.str())); + } - // TODO: verify replacements (no macro locs + ordered). return std::move(Replacements); } diff --git a/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-perform.cpp b/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-perform.cpp index a16e165498696..9874795cb9045 100644 --- a/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-perform.cpp +++ b/clang/test/Refactor/IfSwitchConversion/if-switch-conversion-perform.cpp @@ -305,3 +305,17 @@ void noBracesNeeded(int x) { } // RUN: clang-refactor-test perform -action if-switch-conversion -at=%s:253:3 -at=%s:269:3 -at=%s:275:3 -at=%s:288:3 -at=%s:295:3 %s | FileCheck --check-prefix=CHECK8 %s + +#define MACRO(X) X + +void macroArg(int x) { + // macro-arg: +1:9 + MACRO(if (x == 2) { // MACRO-ARG: "switch (" [[@LINE]]:9 -> [[@LINE]]:13 + ; // MACRO-ARG: ") {\ncase " [[@LINE-1]]:14 -> [[@LINE-1]]:18 + // MACRO-ARG: ":" [[@LINE-2]]:19 -> [[@LINE-2]]:22 + } else if (x == 3) { // MACRO-ARG: "break;\ncase " [[@LINE]]:3 -> [[@LINE]]:19 + ; // MACRO-ARG: ":" [[@LINE-1]]:20 -> [[@LINE-1]]:23 + }); // MACRO-ARG: "break;\n" [[@LINE]]:3 -> [[@LINE]]:3 +} + +// RUN: clang-refactor-test perform -action if-switch-conversion -at=macro-arg %s | FileCheck --check-prefix=MACRO-ARG %s From a6667e8741dec44e06859df432b0c8c51eb2dab7 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 20 Jun 2017 15:00:02 +0100 Subject: [PATCH 162/585] [refactor] "Extract Repeated Expression" should work with macro arguments Also we shouldn't count repeated expressions in macros. rdar://32015671 apple-llvm-split-commit: 392c96e4e08f8f6bd21903e0a032d15bcd2581d1 apple-llvm-split-dir: clang/ --- .../ExtractRepeatedExpressionIntoVariable.cpp | 31 ++++++++++++++----- .../extract-repeated-expr-initiate.m | 12 +++++++ .../extract-repeated-expr-perform.m | 18 +++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp index 87b65a325c28f..2f46e0e18c70c 100644 --- a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp +++ b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp @@ -88,6 +88,7 @@ class DuplicateExprSemanticProfiler class DuplicateExprFinder : public RecursiveASTVisitor, PrinterHelper { const Expr *Target; + const ASTContext &Context; const PrintingPolicy &PP; Stmt::StmtClass ExprKind; QualType T; @@ -102,8 +103,10 @@ class DuplicateExprFinder : public RecursiveASTVisitor, public: SmallVector DuplicateExpressions; - DuplicateExprFinder(const Expr *E, const PrintingPolicy &PP) - : Target(E), PP(PP), ExprKind(E->getStmtClass()), T(E->getType()) { + DuplicateExprFinder(const Expr *E, const ASTContext &Context, + const PrintingPolicy &PP) + : Target(E), Context(Context), PP(PP), ExprKind(E->getStmtClass()), + T(E->getType()) { printExpr(ExprString, E); DuplicateExprSemanticProfiler(ExprDecls).TraverseStmt( const_cast(E)); @@ -127,6 +130,16 @@ class DuplicateExprFinder : public RecursiveASTVisitor, DuplicateExpressions.push_back(E); return true; } + // The expression should not be in a macro. + SourceRange R = E->getSourceRange(); + if (R.getBegin().isMacroID()) { + if (!Context.getSourceManager().isMacroArgExpansion(R.getBegin())) + return true; + } + if (R.getEnd().isMacroID()) { + if (!Context.getSourceManager().isMacroArgExpansion(R.getEnd())) + return true; + } // The expression types should match. if (E->getType() != T) return true; @@ -204,7 +217,7 @@ clang::tooling::initiateExtractRepeatedExpressionIntoVariableOperation( (!T->isAnyPointerType() && !T->isReferenceType())) return None; - DuplicateExprFinder DupFinder(E, Context.getPrintingPolicy()); + DuplicateExprFinder DupFinder(E, Context, Context.getPrintingPolicy()); DupFinder.TraverseDecl(const_cast(ParentDecl)); if (DupFinder.DuplicateExpressions.size() < 2) return None; @@ -354,15 +367,17 @@ ExtractRepeatedExpressionIntoVariableOperation::perform( OS << " = "; E->printPretty(OS, /*Helper=*/nullptr, Context.getPrintingPolicy()); OS << ";\n"; - Replacements.emplace_back(SourceRange(LocFinder.Loc, LocFinder.Loc), - OS.str()); + SourceLocation InsertionLoc = LocFinder.Loc; + if (InsertionLoc.isMacroID()) + InsertionLoc = SM.getExpansionLoc(InsertionLoc); + Replacements.emplace_back(SourceRange(InsertionLoc, InsertionLoc), OS.str()); // Replace the duplicates with a reference to the variable. for (const Expr *E : DuplicateExpressions) Replacements.emplace_back( - SourceRange( - E->getLocStart(), - getPreciseTokenLocEnd(E->getLocEnd(), SM, Context.getLangOpts())), + SourceRange(SM.getSpellingLoc(E->getLocStart()), + getPreciseTokenLocEnd(SM.getSpellingLoc(E->getLocEnd()), SM, + Context.getLangOpts())), Name); return std::move(Replacements); diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.m b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.m index 1cd28efa13c56..11df71f6f4347 100644 --- a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.m +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-initiate.m @@ -92,3 +92,15 @@ void implicitPropertyWithoutGetter(ImplicitPropertyWithoutGetter *x) { } // RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -at=%s:90:3 %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s + +// Prohibit ininiation in macros: + +#define MACROREF(X) X.object + +void prohibitMacroExpr(Wrapper *wrapper) { + // macro-prohibited: +1:3 + wrapper.object.prop = 0; + MACROREF(wrapper).prop = 1; +} + +// RUN: not clang-refactor-test initiate -action extract-repeated-expr-into-var -at=macro-prohibited %s 2>&1 | FileCheck --check-prefix=CHECK-NO %s diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.m b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.m index 723438d59345b..23ed359b61df6 100644 --- a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.m +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.m @@ -78,3 +78,21 @@ void worksOnClassProperty() { // CHECK5-NEXT: "classObject" [[@LINE-4]]:3 -> [[@LINE-4]]:22 // RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:73:3 %s | FileCheck --check-prefix=CHECK5 %s + +#define MACROARG1(X, Y) (X) +#define MACROARG2(X) X; ref.object.prop = 0 + +void macroArgument(Wrapper *ref) { + // macro-arg1: +1:13 + MACROARG1(Wrapper.classObject->ivar, 1); // MACRO-ARG1: "Object *classObject = Wrapper.classObject;\n" [[@LINE]]:3 -> [[@LINE]]:3 + // MACRO-ARG1-NEXT: "classObject" [[@LINE-1]]:13 -> [[@LINE-1]]:32 + MACROARG1(Wrapper.classObject.prop, 0) = 0;// MACRO-ARG1-NEXT: "classObject" [[@LINE]]:13 -> [[@LINE]]:32 + + // macro-arg2: +3:13 + MACROARG2(int x = 0; ref.object->ivar); // MACRO-ARG2: "Object *object = ref.object;\n" [[@LINE]]:3 -> [[@LINE]]:3 + // MACRO-ARG2-NEXT: "object" [[@LINE-1]]:24 -> [[@LINE-1]]:34 + MACROARG2(ref.object.prop);// MARO-ARG2-NEXT: "object" [[@LINE]]:13 -> [[@LINE]]:23 +} + +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=macro-arg1 %s | FileCheck --check-prefix=MACRO-ARG1 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=macro-arg2 %s | FileCheck --check-prefix=MACRO-ARG2 %s From 29931ec135aaca97ce86b043f07c22a967127dd0 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 20 Jun 2017 15:35:28 +0100 Subject: [PATCH 163/585] [refactor] "Generate Missing Function Definitions" should generate stubs in @implementation for a declaration in a class extension rdar://32869347 apple-llvm-split-commit: 3845806e35f24d0dd0fb842ab62f1ce6532ee955 apple-llvm-split-dir: clang/ --- .../Tooling/Refactor/ImplementDeclaredMethods.cpp | 6 ++++++ .../implement-declared-methods.m | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp b/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp index 5055aac32d158..ba4fb903c392c 100644 --- a/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp +++ b/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp @@ -149,6 +149,12 @@ clang::tooling::initiateImplementDeclaredMethodsOperation( CharSourceRange::getTokenRange(M->getSourceRange()))) SelectedMethods.push_back(M); } + // Method declarations from class extensions should be defined in class + // @implementations. + if (const auto *Category = dyn_cast(Container)) { + if (Category->IsClassExtension()) + Container = Category->getClassInterface(); + } return ImplementDeclaredObjCMethodsOperation::initiate( Container, SelectedMethods, CreateOperation); } diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m index 3949e32650c48..1c330392d2066 100644 --- a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m @@ -13,6 +13,18 @@ - (void)method:(int)x with:(int)y; @end +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK1 %s + +@interface MyClass () + +// extension-methods-begin: +1:1 +- (void)anExtensionMethod; +// extension-methods-end: +0:1 + +@end + +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=extension-methods -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK-EXT %s + #ifndef NO_IMPL @implementation MyClass @@ -21,6 +33,7 @@ - (void)someOtherMethod { } @end // CHECK1: "{{.*}}implement-declared-methods.m" "- (void)method { \n <#code#>;\n}\n\n+ (void)classMethod { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n- (void)method:(int)x with:(int)y { \n <#code#>;\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 // CHECK2: "{{.*}}implement-declared-methods.m" "- (void)method { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n" [[@LINE-2]]:1 +// CHECK-EXT: "{{.*}}implement-declared-methods.m" "- (void)anExtensionMethod { \n <#code#>;\n}\n\n" [[@LINE-3]]:1 #endif // RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK1 %s // RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-mix-impl %s | FileCheck --check-prefix=CHECK2 %s From 72ae067f999825507752a0096db3571406c66c5a Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 20 Jun 2017 17:41:44 +0100 Subject: [PATCH 164/585] [refactor] Fix a crash that happens when generating a "Add Missing Switch Cases" fixit while parsing an incomplete switch rdar://32854328 apple-llvm-split-commit: c73d7189e087914fabe9acd22049af091f8e8851 apple-llvm-split-dir: clang/ --- clang/lib/Edit/FillInMissingSwitchEnumCases.cpp | 2 ++ clang/test/FixIt/fixit-fill-in-switch-crash.cpp | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 clang/test/FixIt/fixit-fill-in-switch-crash.cpp diff --git a/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp b/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp index ec10c1ebd451d..c6d59340d30ad 100644 --- a/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp +++ b/clang/lib/Edit/FillInMissingSwitchEnumCases.cpp @@ -49,6 +49,8 @@ bool useBraces(const SwitchStmt *S) { unsigned CaseCount = 0, CompoundCasesCount = 0; for (const SwitchCase *Case = S->getSwitchCaseList(); Case; Case = Case->getNextSwitchCase(), ++CaseCount) { + if (!Case->getSubStmt()) + continue; if (isa(Case->getSubStmt())) ++CompoundCasesCount; } diff --git a/clang/test/FixIt/fixit-fill-in-switch-crash.cpp b/clang/test/FixIt/fixit-fill-in-switch-crash.cpp new file mode 100644 index 0000000000000..635fe818addd9 --- /dev/null +++ b/clang/test/FixIt/fixit-fill-in-switch-crash.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -verify -std=c++11 %s +// RUN: not %clang_cc1 -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 | FileCheck %s + +enum Color { + Black, Red +}; + +void dontCrashOnEmptySubStmt(Color c) { // expected-note {{to match this '{'}} + switch (c) { // expected-note {{to match this '{'}} \ + // expected-warning {{enumeration value 'Red' not handled in switch}} \ + // expected-note {{add missing switch cases}} + case Black: // CHECK: fix-it:{{.*}}:{[[@LINE+3]]:10-[[@LINE+3]]:10}:"case Red:\n<#code#>\nbreak;\n" + // expected-error@+2 {{expected expression}} + // expected-error@+1 2 {{expected '}'}} + case // From bfe258c8f7a643d0b17c66c9887c07c9587e076c Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Wed, 21 Jun 2017 13:52:53 +0100 Subject: [PATCH 165/585] [refactor] "Fill in abstract methods" should not place method groups after an implicit method that overrides a method in the abstract class This commit fixes an issue where the refactoring action "Fill in abstract methods" inserted method declarations outside of the class body because Clang decided to place them after the implicit destructor. rdar://32857076 apple-llvm-split-commit: 5463b61ddb27add0f828bb174e8f105aa1c3eaa1 apple-llvm-split-dir: clang/ --- ...nMissingMethodStubsFromAbstractClasses.cpp | 2 +- ...ll-in-missing-abstract-methods-perform.cpp | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/clang/lib/Tooling/Refactor/FillInMissingMethodStubsFromAbstractClasses.cpp b/clang/lib/Tooling/Refactor/FillInMissingMethodStubsFromAbstractClasses.cpp index 42497907ee936..ba730f68a8a20 100644 --- a/clang/lib/Tooling/Refactor/FillInMissingMethodStubsFromAbstractClasses.cpp +++ b/clang/lib/Tooling/Refactor/FillInMissingMethodStubsFromAbstractClasses.cpp @@ -156,7 +156,7 @@ static SourceLocation findInsertionLocationForMethodsFromAbstractClass( const SourceManager &SM, const LangOptions &LangOpts) { SourceLocation Loc; for (const CXXMethodDecl *M : Class->methods()) { - if (!M->isVirtual() || M->isPure()) + if (!M->isVirtual() || M->isPure() || M->isImplicit()) continue; for (const CXXMethodDecl *OM : M->overridden_methods()) { OM = OM->getCanonicalDecl(); diff --git a/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-perform.cpp b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-perform.cpp index cc6e8e59933f6..8ae223b67ea96 100644 --- a/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-perform.cpp +++ b/clang/test/Refactor/FillInMissingMethodStubsFromAbstractClasses/fill-in-missing-abstract-methods-perform.cpp @@ -99,3 +99,28 @@ struct GenericSubType : GenericType { // CHECK8: "void firstMethod(int x, int y) override;\n\nvoid thirdMethod(int a) override;\n\nvoid fourthMethod() override;\n\n" [[@LINE-1]]:1 // RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=%s:91:1 -at=%s:96:1 %s | FileCheck --check-prefix=CHECK8 %s + + +struct BaseClass2 +{ + virtual ~BaseClass2(); + virtual int load() = 0; +}; + +// correct-implicit-destructor-placement: +1:1 +struct DerivedImplicitDestructorClass2 +: public BaseClass2 +{ + +}; // CHECK-DESTRUCTOR: "int load() override;\n\n" [[@LINE]]:1 + +// Don't insert methods after the destructor: +// correct-destructor-placement: +1:1 +struct DerivedExplicitDestructorClass2 +: public BaseClass2 { + ~DerivedImplicitDestructorClass2(); + + +}; // CHECK-DESTRUCTOR: "int load() override;\n\n" [[@LINE]]:1 + +// RUN: clang-refactor-test perform -action fill-in-missing-abstract-methods -at=correct-implicit-destructor-placement -at=correct-destructor-placement %s | FileCheck --check-prefix=CHECK-DESTRUCTOR %s From d0f81be7748d5d7d9a2dea9304cc93f537a22970 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 14 Jun 2017 17:49:15 -0700 Subject: [PATCH 166/585] Revert "Temporary hack to allow making progress on master-next build failures." This reverts commit d1cd5fdf4f31d14fb20e40b4d3565fc865076e7b. apple-llvm-split-commit: b1404c0d6dca1e93d796e42244d567789effb15d apple-llvm-split-dir: clang/ --- clang/include/clang/Lex/MacroInfo.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/clang/include/clang/Lex/MacroInfo.h b/clang/include/clang/Lex/MacroInfo.h index e5ca15f2e7170..7da1e7b41ab8d 100644 --- a/clang/include/clang/Lex/MacroInfo.h +++ b/clang/include/clang/Lex/MacroInfo.h @@ -266,11 +266,6 @@ class MacroInfo { void setUsedForHeaderGuard(bool Val) { UsedForHeaderGuard = Val; } - // FIXME: hack to get past build failures - unsigned getOwningModuleID() const { - return 0; - } - void dump() const; private: From 0ebc64037f659cde986f324f3f70a7c0bb29bb1c Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 19 Jun 2017 10:59:31 -0700 Subject: [PATCH 167/585] [Lex] Expose a ModuleMacro's IdentifierInfo. Usually the caller already has this information, but not always. apple-llvm-split-commit: 3f87783b0429be1d4329a7b091351eae9b6e6d7a apple-llvm-split-dir: clang/ --- clang/include/clang/Lex/MacroInfo.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/include/clang/Lex/MacroInfo.h b/clang/include/clang/Lex/MacroInfo.h index 7da1e7b41ab8d..b1621b8d2280d 100644 --- a/clang/include/clang/Lex/MacroInfo.h +++ b/clang/include/clang/Lex/MacroInfo.h @@ -510,6 +510,9 @@ class ModuleMacro : public llvm::FoldingSetNode { ID.AddPointer(II); } + /// Get the name of the macro. + IdentifierInfo *getName() const { return II; } + /// Get the ID of the module that exports this macro. Module *getOwningModule() const { return OwningModule; } From 7efef4eec59be4433f8ced542c234acdb13fdb5d Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Thu, 22 Jun 2017 16:37:34 +0100 Subject: [PATCH 168/585] NFC, move nameForExtractedVariable to ExtractionUtils Prep for rdar://32793311 apple-llvm-split-commit: 3e1c0b75f652ee4ec90262b81c56e427e254c7e1 apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/CMakeLists.txt | 1 + .../ExtractRepeatedExpressionIntoVariable.cpp | 21 +++--------- .../lib/Tooling/Refactor/ExtractionUtils.cpp | 33 +++++++++++++++++++ clang/lib/Tooling/Refactor/ExtractionUtils.h | 31 +++++++++++++++++ 4 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 clang/lib/Tooling/Refactor/ExtractionUtils.cpp create mode 100644 clang/lib/Tooling/Refactor/ExtractionUtils.h diff --git a/clang/lib/Tooling/Refactor/CMakeLists.txt b/clang/lib/Tooling/Refactor/CMakeLists.txt index b65f794de4062..9828c1ac6813c 100644 --- a/clang/lib/Tooling/Refactor/CMakeLists.txt +++ b/clang/lib/Tooling/Refactor/CMakeLists.txt @@ -5,6 +5,7 @@ add_clang_library(clangToolingRefactor ASTStateSerialization.cpp Extract.cpp ExtractRepeatedExpressionIntoVariable.cpp + ExtractionUtils.cpp FillInEnumSwitchCases.cpp FillInMissingMethodStubsFromAbstractClasses.cpp FillInMissingProtocolStubs.cpp diff --git a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp index 2f46e0e18c70c..4427c14bbf381 100644 --- a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp +++ b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +#include "ExtractionUtils.h" #include "RefactoringOperations.h" #include "SourceLocationUtilities.h" #include "clang/AST/AST.h" @@ -322,22 +323,10 @@ class ExtractedVariableInsertionLocFinder } // end anonymous namespace static StringRef nameForExtractedVariable(const Expr *E) { - if (const auto *Call = dyn_cast(E)) { - if (const auto *Fn = Call->getDirectCallee()) - return Fn->getName(); - } else if (const auto *Msg = dyn_cast(E)) { - if (const auto *M = Msg->getMethodDecl()) { - if (M->getSelector().isUnarySelector()) - return M->getSelector().getNameForSlot(0); - } - } else if (const auto *PRE = dyn_cast(E)) { - if (PRE->isImplicitProperty()) { - if (const auto *M = PRE->getImplicitPropertyGetter()) - return M->getSelector().getNameForSlot(0); - } else if (const auto *Prop = PRE->getExplicitProperty()) - return Prop->getName(); - } - return "duplicate"; + auto SuggestedName = extract::nameForExtractedVariable(E); + if (!SuggestedName) + return "duplicate"; + return *SuggestedName; } llvm::Expected diff --git a/clang/lib/Tooling/Refactor/ExtractionUtils.cpp b/clang/lib/Tooling/Refactor/ExtractionUtils.cpp new file mode 100644 index 0000000000000..f1917e51926a8 --- /dev/null +++ b/clang/lib/Tooling/Refactor/ExtractionUtils.cpp @@ -0,0 +1,33 @@ +//===--- ExtractionUtils.cpp - Extraction helper functions ----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExtractionUtils.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprObjC.h" + +using namespace clang; + +Optional tooling::extract::nameForExtractedVariable(const Expr *E) { + if (const auto *Call = dyn_cast(E)) { + if (const auto *Fn = Call->getDirectCallee()) + return Fn->getName(); + } else if (const auto *Msg = dyn_cast(E)) { + if (const auto *M = Msg->getMethodDecl()) { + if (M->getSelector().isUnarySelector()) + return M->getSelector().getNameForSlot(0); + } + } else if (const auto *PRE = dyn_cast(E)) { + if (PRE->isImplicitProperty()) { + if (const auto *M = PRE->getImplicitPropertyGetter()) + return M->getSelector().getNameForSlot(0); + } else if (const auto *Prop = PRE->getExplicitProperty()) + return Prop->getName(); + } + return None; +} diff --git a/clang/lib/Tooling/Refactor/ExtractionUtils.h b/clang/lib/Tooling/Refactor/ExtractionUtils.h new file mode 100644 index 0000000000000..5607e46d6d59f --- /dev/null +++ b/clang/lib/Tooling/Refactor/ExtractionUtils.h @@ -0,0 +1,31 @@ +//===--- ExtractionUtils.h - Extraction helper functions ------------------===// +// +// 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_TOOLING_REFACTOR_EXTRACTION_UTILS_H +#define LLVM_CLANG_LIB_TOOLING_REFACTOR_EXTRACTION_UTILS_H + +#include "clang/AST/Type.h" +#include "clang/Basic/LLVM.h" + +namespace clang { + +class Expr; + +namespace tooling { +namespace extract { + +/// Returns a good name for an extracted variable based on the declaration +/// that's used in the given expression \p E. +Optional nameForExtractedVariable(const Expr *E); + +} // end namespace extract +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTOR_EXTRACTION_UTILS_H From f5487a150c76ec96cbe47654e2422ba2e9585b2b Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Thu, 22 Jun 2017 16:59:22 +0100 Subject: [PATCH 169/585] NFC, extract expression checking logic to StmtUtils Prep for rdar://32793311 apple-llvm-split-commit: 47f2d1f7afce429be53d3906f028805db0e01ad8 apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/Extract.cpp | 15 +-------------- clang/lib/Tooling/Refactor/StmtUtils.cpp | 18 ++++++++++++++++++ clang/lib/Tooling/Refactor/StmtUtils.h | 5 +++++ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/clang/lib/Tooling/Refactor/Extract.cpp b/clang/lib/Tooling/Refactor/Extract.cpp index ac381065709c9..8a807d34fa35c 100644 --- a/clang/lib/Tooling/Refactor/Extract.cpp +++ b/clang/lib/Tooling/Refactor/Extract.cpp @@ -1282,14 +1282,6 @@ PrintingPolicy getPrintingPolicy(const ASTContext &Context, return Policy; } -static bool isAssignmentOperator(const Stmt *S) { - if (const auto *PseudoExpr = dyn_cast(S)) - return isAssignmentOperator(PseudoExpr->getSyntacticForm()); - if (const auto *BO = dyn_cast(S)) - return BO->isAssignmentOp(); - return false; -} - static QualType getFunctionLikeParentDeclReturnType(const Decl *D) { // FIXME: might need to handle ObjC blocks in the future. if (const auto *M = dyn_cast(D)) @@ -1468,12 +1460,7 @@ llvm::Expected ExtractOperation::perform( } else Visitor.InspectExtractedStmt(const_cast(S), Context); // Compute the return type. - bool IsExpr = isa(S); - // Assignment operators should be treated as statements unless they are a part - // of an expression. - if (IsExpr && isAssignmentOperator(S) && - (!ParentStmt || !isa(ParentStmt))) - IsExpr = false; + bool IsExpr = isLexicalExpression(S, ParentStmt); QualType ReturnType; if (IsExpr || Visitor.HasReturnInExtracted) { if (const auto *E = dyn_cast(S)) { diff --git a/clang/lib/Tooling/Refactor/StmtUtils.cpp b/clang/lib/Tooling/Refactor/StmtUtils.cpp index 53d426b8b39c0..ed8ae30543a48 100644 --- a/clang/lib/Tooling/Refactor/StmtUtils.cpp +++ b/clang/lib/Tooling/Refactor/StmtUtils.cpp @@ -52,3 +52,21 @@ bool clang::tooling::isSemicolonRequiredAfter(const Stmt *S) { return true; } } + +static bool isAssignmentOperator(const Stmt *S) { + if (const auto *PseudoExpr = dyn_cast(S)) + return isAssignmentOperator(PseudoExpr->getSyntacticForm()); + if (const auto *BO = dyn_cast(S)) + return BO->isAssignmentOp(); + return false; +} + +bool clang::tooling::isLexicalExpression(const Stmt *S, const Stmt *Parent) { + if (!isa(S)) + return false; + // Assignment operators should be treated as statements unless they are a part + // of an expression. + if (isAssignmentOperator(S) && (!Parent || !isa(Parent))) + return false; + return true; +} diff --git a/clang/lib/Tooling/Refactor/StmtUtils.h b/clang/lib/Tooling/Refactor/StmtUtils.h index 8f458770ae23b..5bc319528e469 100644 --- a/clang/lib/Tooling/Refactor/StmtUtils.h +++ b/clang/lib/Tooling/Refactor/StmtUtils.h @@ -27,6 +27,11 @@ SourceLocation getLexicalEndLocForDecl(const Decl *D, const SourceManager &SM, /// statement. bool isSemicolonRequiredAfter(const Stmt *S); +/// Returns true if the given statement \p S is an actual expression in the +/// source. Assignment expressions are considered to be statements unless they +/// are a part of an expression. +bool isLexicalExpression(const Stmt *S, const Stmt *Parent); + } // end namespace tooling } // end namespace clang From 6f314c97e6b9d0a7e72d1dc4231f0b03ebf63620 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Thu, 22 Jun 2017 17:43:08 +0100 Subject: [PATCH 170/585] NFC, move variable placement logic to ExtractionUtils Prep for rdar://32793311 apple-llvm-split-commit: 3c6cc10ce4508826bab65510c13b75215d30b712 apple-llvm-split-dir: clang/ --- .../ExtractRepeatedExpressionIntoVariable.cpp | 99 +---------------- .../lib/Tooling/Refactor/ExtractionUtils.cpp | 102 ++++++++++++++++++ clang/lib/Tooling/Refactor/ExtractionUtils.h | 9 ++ 3 files changed, 115 insertions(+), 95 deletions(-) diff --git a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp index 4427c14bbf381..dc90d9ea93c1e 100644 --- a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp +++ b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp @@ -19,7 +19,6 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" #include "clang/AST/RecursiveASTVisitor.h" -#include "llvm/Support/SaveAndRestore.h" using namespace clang; using namespace clang::tooling; @@ -234,94 +233,6 @@ clang::tooling::initiateExtractRepeatedExpressionIntoVariableOperation( return Result; } -namespace { - -/// Checks if a set of expressions is directly contained in some AST region. -class StmtReachabilityChecker - : public RecursiveASTVisitor { - const llvm::SmallPtrSetImpl &Expressions; - unsigned Count = 0; - - StmtReachabilityChecker( - const llvm::SmallPtrSetImpl &Expressions) - : Expressions(Expressions) {} - - bool areAllExpressionsReached() const { return Count == Expressions.size(); } - -public: - bool VisitStmt(const Stmt *S) { - if (Expressions.count(S)) { - ++Count; - if (areAllExpressionsReached()) - return false; - } - return true; - } - - static bool areAllExpressionsReachableFrom( - CompoundStmt *S, const llvm::SmallPtrSetImpl &Expressions) { - StmtReachabilityChecker Checker(Expressions); - Checker.TraverseStmt(S); - return Checker.areAllExpressionsReached(); - } -}; - -/// Figures out where the extracted variable should go. -class ExtractedVariableInsertionLocFinder - : public RecursiveASTVisitor { - llvm::SmallPtrSet Expressions; - llvm::SmallVector, 4> - InsertionCandidateStack; - bool IsPrevCompoundStmt = false; - -public: - SourceLocation Loc; - - /// Initializes the insertion location finder using the set of duplicate - /// \p Expressions from one function. - ExtractedVariableInsertionLocFinder(ArrayRef Expressions) { - for (const Expr *E : Expressions) - this->Expressions.insert(E); - } - - bool TraverseStmt(Stmt *S) { - if (!S) - return RecursiveASTVisitor::TraverseStmt(S); - if (IsPrevCompoundStmt && !InsertionCandidateStack.empty()) - InsertionCandidateStack.back().second = S; - llvm::SaveAndRestore IsPrevCompoundStmtTracker(IsPrevCompoundStmt, - false); - if (auto *CS = dyn_cast(S)) { - IsPrevCompoundStmt = true; - InsertionCandidateStack.emplace_back(CS, nullptr); - return RecursiveASTVisitor::TraverseStmt(S); - } - return RecursiveASTVisitor::TraverseStmt(S); - } - - bool VisitStmt(const Stmt *S) { - if (Expressions.count(S)) { - // The insertion location should be in the first compound statement that - // includes all of the expressions as descendants as we want the new - // variable to be visible to all uses. - for (auto I = InsertionCandidateStack.rbegin(), - E = InsertionCandidateStack.rend(); - I != E; ++I) { - if (StmtReachabilityChecker::areAllExpressionsReachableFrom( - I->first, Expressions) && - I->second) { - Loc = I->second->getLocStart(); - break; - } - } - return false; - } - return true; - } -}; - -} // end anonymous namespace - static StringRef nameForExtractedVariable(const Expr *E) { auto SuggestedName = extract::nameForExtractedVariable(E); if (!SuggestedName) @@ -336,9 +247,10 @@ ExtractRepeatedExpressionIntoVariableOperation::perform( std::vector Replacements; const SourceManager &SM = Context.getSourceManager(); - ExtractedVariableInsertionLocFinder LocFinder(DuplicateExpressions); - LocFinder.TraverseDecl(const_cast(ParentDecl)); - if (LocFinder.Loc.isInvalid()) + SourceLocation InsertionLoc = + extract::locationForExtractedVariableDeclaration(DuplicateExpressions, + ParentDecl, SM); + if (InsertionLoc.isInvalid()) return llvm::make_error( "no appropriate insertion location found"); @@ -356,9 +268,6 @@ ExtractRepeatedExpressionIntoVariableOperation::perform( OS << " = "; E->printPretty(OS, /*Helper=*/nullptr, Context.getPrintingPolicy()); OS << ";\n"; - SourceLocation InsertionLoc = LocFinder.Loc; - if (InsertionLoc.isMacroID()) - InsertionLoc = SM.getExpansionLoc(InsertionLoc); Replacements.emplace_back(SourceRange(InsertionLoc, InsertionLoc), OS.str()); // Replace the duplicates with a reference to the variable. diff --git a/clang/lib/Tooling/Refactor/ExtractionUtils.cpp b/clang/lib/Tooling/Refactor/ExtractionUtils.cpp index f1917e51926a8..208685ed7b7d0 100644 --- a/clang/lib/Tooling/Refactor/ExtractionUtils.cpp +++ b/clang/lib/Tooling/Refactor/ExtractionUtils.cpp @@ -10,6 +10,9 @@ #include "ExtractionUtils.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/Support/SaveAndRestore.h" using namespace clang; @@ -31,3 +34,102 @@ Optional tooling::extract::nameForExtractedVariable(const Expr *E) { } return None; } + +namespace { + +/// Checks if a set of expressions is directly contained in some AST region. +class StmtReachabilityChecker + : public RecursiveASTVisitor { + const llvm::SmallPtrSetImpl &Expressions; + unsigned Count = 0; + + StmtReachabilityChecker( + const llvm::SmallPtrSetImpl &Expressions) + : Expressions(Expressions) {} + + bool areAllExpressionsReached() const { return Count == Expressions.size(); } + +public: + bool VisitStmt(const Stmt *S) { + if (Expressions.count(S)) { + ++Count; + if (areAllExpressionsReached()) + return false; + } + return true; + } + + static bool areAllExpressionsReachableFrom( + CompoundStmt *S, const llvm::SmallPtrSetImpl &Expressions) { + StmtReachabilityChecker Checker(Expressions); + Checker.TraverseStmt(S); + return Checker.areAllExpressionsReached(); + } +}; + +/// Figures out where the extracted variable should go. +class ExtractedVariableInsertionLocFinder + : public RecursiveASTVisitor { + llvm::SmallPtrSet Expressions; + llvm::SmallVector, 4> + InsertionCandidateStack; + bool IsPrevCompoundStmt = false; + +public: + SourceLocation Loc; + + /// Initializes the insertion location finder using the set of duplicate + /// \p Expressions from one function. + ExtractedVariableInsertionLocFinder(ArrayRef Expressions) { + for (const Expr *E : Expressions) + this->Expressions.insert(E); + } + + bool TraverseStmt(Stmt *S) { + if (!S) + return RecursiveASTVisitor::TraverseStmt(S); + if (IsPrevCompoundStmt && !InsertionCandidateStack.empty()) + InsertionCandidateStack.back().second = S; + llvm::SaveAndRestore IsPrevCompoundStmtTracker(IsPrevCompoundStmt, + false); + if (auto *CS = dyn_cast(S)) { + IsPrevCompoundStmt = true; + InsertionCandidateStack.emplace_back(CS, nullptr); + return RecursiveASTVisitor::TraverseStmt(S); + } + return RecursiveASTVisitor::TraverseStmt(S); + } + + bool VisitStmt(const Stmt *S) { + if (Expressions.count(S)) { + // The insertion location should be in the first compound statement that + // includes all of the expressions as descendants as we want the new + // variable to be visible to all uses. + for (auto I = InsertionCandidateStack.rbegin(), + E = InsertionCandidateStack.rend(); + I != E; ++I) { + if (StmtReachabilityChecker::areAllExpressionsReachableFrom( + I->first, Expressions) && + I->second) { + Loc = I->second->getLocStart(); + break; + } + } + return false; + } + return true; + } +}; + +} // end anonymous namespace + +SourceLocation tooling::extract::locationForExtractedVariableDeclaration( + ArrayRef Expressions, const Decl *ParentDecl, + const SourceManager &SM) { + ExtractedVariableInsertionLocFinder LocFinder(Expressions); + LocFinder.TraverseDecl(const_cast(ParentDecl)); + SourceLocation Result = LocFinder.Loc; + if (Result.isValid() && Result.isMacroID()) + return SM.getExpansionLoc(Result); + return Result; +} diff --git a/clang/lib/Tooling/Refactor/ExtractionUtils.h b/clang/lib/Tooling/Refactor/ExtractionUtils.h index 5607e46d6d59f..816a0815106de 100644 --- a/clang/lib/Tooling/Refactor/ExtractionUtils.h +++ b/clang/lib/Tooling/Refactor/ExtractionUtils.h @@ -16,6 +16,8 @@ namespace clang { class Expr; +class Decl; +class SourceManager; namespace tooling { namespace extract { @@ -24,6 +26,13 @@ namespace extract { /// that's used in the given expression \p E. Optional nameForExtractedVariable(const Expr *E); +/// Returns an appropriate location for a variable declaration that will be +/// visible to all the given expressions. +SourceLocation +locationForExtractedVariableDeclaration(ArrayRef Expressions, + const Decl *ParentDecl, + const SourceManager &SM); + } // end namespace extract } // end namespace tooling } // end namespace clang From e7213dac7b52f5833304914fd7e327de041e45aa Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Thu, 22 Jun 2017 18:18:10 +0100 Subject: [PATCH 171/585] Add "Extract Expression" action rdar://32793311 apple-llvm-split-commit: 64fda69e359299f65557802094464a1334a1564c apple-llvm-split-dir: clang/ --- clang/include/clang-c/Refactor.h | 6 + .../Tooling/Refactor/RefactoringActions.def | 2 + clang/lib/Tooling/Refactor/Extract.cpp | 133 ++++++++++++++---- .../lib/Tooling/Refactor/ExtractionUtils.cpp | 4 +- .../Extract/extract-expression-into-var.cpp | 39 +++++ .../Extract/extract-expression-into-var.m | 12 ++ 6 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 clang/test/Refactor/Extract/extract-expression-into-var.cpp create mode 100644 clang/test/Refactor/Extract/extract-expression-into-var.m diff --git a/clang/include/clang-c/Refactor.h b/clang/include/clang-c/Refactor.h index 869f562726b6d..63b01a4175450 100644 --- a/clang/include/clang-c/Refactor.h +++ b/clang/include/clang-c/Refactor.h @@ -156,6 +156,12 @@ enum CXRefactoringActionType { * declarations without respective definitions. */ CXRefactor_ImplementDeclaredMethods = 10, + + /** + * \brief The sub-action of 'extract' that extracts source expression into a + * new variable. + */ + CXRefactor_Extract_Expression = 11, }; /** diff --git a/clang/include/clang/Tooling/Refactor/RefactoringActions.def b/clang/include/clang/Tooling/Refactor/RefactoringActions.def index 9b7e7e04d037f..f5c2f668e0141 100644 --- a/clang/include/clang/Tooling/Refactor/RefactoringActions.def +++ b/clang/include/clang/Tooling/Refactor/RefactoringActions.def @@ -32,6 +32,8 @@ REFACTORING_SUB_ACTION(Local, Rename, "Rename") REFACTORING_OPERATION_ACTION(Extract, "Extract Function", "extract") REFACTORING_OPERATION_SUB_ACTION(Method, Extract, "Extract Method", "extract-method") +REFACTORING_OPERATION_SUB_ACTION(Expression, Extract, "Extract Expression", + "extract-expression") REFACTORING_OPERATION_ACTION(IfSwitchConversion, "Convert to Switch", "if-switch-conversion") diff --git a/clang/lib/Tooling/Refactor/Extract.cpp b/clang/lib/Tooling/Refactor/Extract.cpp index 8a807d34fa35c..624b18dc772a3 100644 --- a/clang/lib/Tooling/Refactor/Extract.cpp +++ b/clang/lib/Tooling/Refactor/Extract.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "ExtractionUtils.h" #include "RefactoringOperations.h" #include "SourceLocationUtilities.h" #include "StmtUtils.h" @@ -56,6 +57,8 @@ struct CompoundStatementRange { CompoundStmt::const_body_iterator end() const { return Last + 1; } }; +enum class ExtractionKind { Function, Method, Expression }; + class ExtractOperation : public RefactoringOperation { public: struct CandidateInfo { @@ -80,12 +83,11 @@ class ExtractOperation : public RefactoringOperation { std::vector Candidates, Optional ExtractedStmtRange, Optional FirstCandidateInfo, - bool IsMethodExtraction) + ExtractionKind Kind) : S(S), ParentStmt(ParentStmt), FunctionLikeParentDecl(FunctionLikeParentDecl), Candidates(std::move(Candidates)), - ExtractedStmtRange(ExtractedStmtRange), - IsMethodExtraction(IsMethodExtraction) { + ExtractedStmtRange(ExtractedStmtRange), Kind(Kind) { if (FirstCandidateInfo) CandidateExtractionInfo.push_back(*FirstCandidateInfo); } @@ -107,23 +109,35 @@ class ExtractOperation : public RefactoringOperation { } std::vector getAvailableSubActions() override { + std::vector SubActions; if (isa(FunctionLikeParentDecl) || isa(FunctionLikeParentDecl)) - return {RefactoringActionType::Extract_Method}; - return {}; + SubActions.push_back(RefactoringActionType::Extract_Method); + if (isLexicalExpression(S, ParentStmt)) + SubActions.push_back(RefactoringActionType::Extract_Expression); + return SubActions; + } + + bool isMethodExtraction() const { return Kind == ExtractionKind::Method; } + + bool isExpressionExtraction() const { + return Kind == ExtractionKind::Expression; } llvm::Expected perform(ASTContext &Context, const Preprocessor &ThePreprocessor, const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) override; + llvm::Expected + performExpressionExtraction(ASTContext &Context, PrintingPolicy &PP); + const Stmt *S, *ParentStmt; const Decl *FunctionLikeParentDecl; std::vector Candidates; /// A set of extraction candidates that correspond to the extracted code. SmallVector CandidateExtractionInfo; Optional ExtractedStmtRange; - bool IsMethodExtraction; + ExtractionKind Kind; }; } // end anonymous namespace @@ -167,7 +181,7 @@ static RefactoringOperationResult initiateAnyExtractOperation(ASTSlice &Slice, ASTContext &Context, SourceLocation Location, SourceRange SelectionRange, bool CreateOperation, - bool IsMethodExtraction = false) { + ExtractionKind Kind = ExtractionKind::Function) { auto SelectedStmtsOpt = Slice.getSelectedStmtSet(); if (!SelectedStmtsOpt) return None; @@ -196,6 +210,12 @@ initiateAnyExtractOperation(ASTSlice &Slice, ASTContext &Context, return RefactoringOperationResult("property setter can't be extracted"); } + const Stmt *ParentStmt = + Slice.parentStmtForIndex(*Stmts.containsSelectionRangeIndex); + if (Kind == ExtractionKind::Expression && + !isLexicalExpression(Selected, ParentStmt)) + return None; + RefactoringOperationResult Result; Result.Initiated = true; if (!CreateOperation) @@ -244,9 +264,8 @@ initiateAnyExtractOperation(ASTSlice &Slice, ASTContext &Context, } auto Operation = llvm::make_unique( - Selected, Slice.parentStmtForIndex(*Stmts.containsSelectionRangeIndex), - ParentDecl, std::move(Candidates), ExtractedStmtRange, FirstCandidateInfo, - IsMethodExtraction); + Selected, ParentStmt, ParentDecl, std::move(Candidates), + ExtractedStmtRange, FirstCandidateInfo, Kind); auto &CandidateExtractionInfo = Operation->CandidateExtractionInfo; SourceRange Range; if (ExtractedStmtRange) @@ -290,8 +309,16 @@ RefactoringOperationResult clang::tooling::initiateExtractMethodOperation( SourceRange SelectionRange, bool CreateOperation) { // TODO: Verify that method extraction is actually possible. return initiateAnyExtractOperation(Slice, Context, Location, SelectionRange, - CreateOperation, - /*IsMethodExtraction=*/true); + CreateOperation, ExtractionKind::Method); +} + +RefactoringOperationResult clang::tooling::initiateExtractExpressionOperation( + ASTSlice &Slice, ASTContext &Context, SourceLocation Location, + SourceRange SelectionRange, bool CreateOperation) { + RefactoringOperationResult R = + initiateAnyExtractOperation(Slice, Context, Location, SelectionRange, + CreateOperation, ExtractionKind::Expression); + return R; } using ReferencedEntity = @@ -1429,6 +1456,54 @@ static bool isInHeader(SourceLocation Loc, const SourceManager &SM) { .Default(false); } +llvm::Expected +ExtractOperation::performExpressionExtraction(ASTContext &Context, + PrintingPolicy &PP) { + assert(isExpressionExtraction() && "Not an expression extraction"); + std::vector Replacements; + const Expr *E = cast(S); + QualType VarType = findExpressionLexicalType(FunctionLikeParentDecl, E, + E->getType(), PP, Context); + StringRef VarName = "extractedExpr"; + auto CreatedSymbol = + llvm::make_unique(SymbolName(VarName)); + + SourceRange ExtractedTokenRange = CandidateExtractionInfo[0].Range; + SourceRange ExtractedCharRange = SourceRange( + ExtractedTokenRange.getBegin(), + getPreciseTokenLocEnd(ExtractedTokenRange.getEnd(), + Context.getSourceManager(), Context.getLangOpts())); + + // Create the variable that will hold the value of the duplicate expression. + std::string VariableDeclarationString; + llvm::raw_string_ostream OS(VariableDeclarationString); + VarType.print(OS, PP, /*PlaceHolder*/ VarName); + // FIXME: We should hook into the TypePrinter when moving over to llvm.org + // instead and get the offset from it. + unsigned NameOffset = StringRef(OS.str()).find(VarName); + OS << " = "; + OS << Lexer::getSourceText(CharSourceRange::getCharRange(ExtractedCharRange), + Context.getSourceManager(), Context.getLangOpts()); + OS << ";\n"; + + // Variable declaration. + SourceLocation InsertionLoc = + extract::locationForExtractedVariableDeclaration( + E, FunctionLikeParentDecl, Context.getSourceManager()); + Replacements.push_back(RefactoringReplacement( + SourceRange(InsertionLoc, InsertionLoc), OS.str(), CreatedSymbol.get(), + RefactoringReplacement::AssociatedSymbolLocation( + llvm::makeArrayRef(NameOffset), /*IsDeclaration=*/true))); + // Replace the expression with the variable. + Replacements.push_back( + RefactoringReplacement(ExtractedCharRange, VarName, CreatedSymbol.get(), + /*NameOffset=*/llvm::makeArrayRef(unsigned(0)))); + + RefactoringResult Result(std::move(Replacements)); + Result.AssociatedSymbols.push_back(std::move(CreatedSymbol)); + return std::move(Result); +} + llvm::Expected ExtractOperation::perform( ASTContext &Context, const Preprocessor &ThePreprocessor, const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { @@ -1442,6 +1517,9 @@ llvm::Expected ExtractOperation::perform( PP.SuppressLifetimeQualifiers = true; PP.SuppressUnwrittenScope = true; + if (isExpressionExtraction()) + return performExpressionExtraction(Context, PP); + const Stmt *S = CandidateExtractionInfo[SelectedCandidateIndex].AnalyzedStatement ? CandidateExtractionInfo[SelectedCandidateIndex].AnalyzedStatement @@ -1573,7 +1651,7 @@ llvm::Expected ExtractOperation::perform( } // Capture any fields if necessary. bool IsThisConstInCapturedFieldUses = true; - if (!IsMethodExtraction) { + if (!isMethodExtraction()) { for (const auto &I : Visitor.CapturedFields) { if (I.getSecond().isPassedByRefOrPtr() && !I.getSecond().isRefOrPtrConst()) @@ -1592,7 +1670,7 @@ llvm::Expected ExtractOperation::perform( return X.getName() < Y.getName(); }); // 'This'/'self' should be passed-in first. - if (!IsMethodExtraction && Visitor.CaptureThis) { + if (!isMethodExtraction() && Visitor.CaptureThis) { CapturedVariables.insert( CapturedVariables.begin(), CapturedVariable::getThis( @@ -1612,7 +1690,7 @@ llvm::Expected ExtractOperation::perform( PtrThisRewriter.TraverseStmt(const_cast(S)); } else PtrThisRewriter.TraverseStmt(const_cast(S)); - } else if (!IsMethodExtraction && Visitor.CaptureSelf && + } else if (!isMethodExtraction() && Visitor.CaptureSelf && EnclosingObjCMethod) { if (EnclosingObjCMethod->isInstanceMethod()) { // Instance methods rewrite 'self' into an 'object' parameter. @@ -1638,7 +1716,7 @@ llvm::Expected ExtractOperation::perform( SelfRewriter.TraverseStmt(const_cast(S)); } } - if (!IsMethodExtraction && Visitor.CaptureSuper && EnclosingObjCMethod) { + if (!isMethodExtraction() && Visitor.CaptureSuper && EnclosingObjCMethod) { if (EnclosingObjCMethod->isInstanceMethod()) // Instance methods rewrite 'super' into an 'superObject' parameter. CapturedVariables.insert(Visitor.CaptureSelf @@ -1701,7 +1779,8 @@ llvm::Expected ExtractOperation::perform( StringRef ExtractedName = "extracted"; llvm::SmallVector ExtractedNamePieces; ExtractedNamePieces.push_back(ExtractedName); - if (IsMethodExtraction && EnclosingObjCMethod && !CapturedVariables.empty()) { + if (isMethodExtraction() && EnclosingObjCMethod && + !CapturedVariables.empty()) { for (const auto &Var : llvm::makeArrayRef(CapturedVariables).drop_front()) ExtractedNamePieces.push_back(Var.getName()); } @@ -1710,7 +1789,7 @@ llvm::Expected ExtractOperation::perform( SymbolName(ExtractedNamePieces)); SourceLocation FunctionExtractionLoc = computeFunctionExtractionLocation( - FunctionLikeParentDecl, IsMethodExtraction); + FunctionLikeParentDecl, isMethodExtraction()); FunctionExtractionLoc = getLocationOfPrecedingComment(FunctionExtractionLoc, SM, LangOpts); @@ -1719,7 +1798,7 @@ llvm::Expected ExtractOperation::perform( [&](llvm::raw_string_ostream &OS, bool IsDefinition = true) -> RefactoringReplacement::AssociatedSymbolLocation { - if (IsMethodExtraction && EnclosingObjCMethod) { + if (isMethodExtraction() && EnclosingObjCMethod) { OS << (EnclosingObjCMethod->isClassMethod() ? '+' : '-') << " ("; ReturnType.print(OS, PP); OS << ')'; @@ -1742,7 +1821,7 @@ llvm::Expected ExtractOperation::perform( NameOffsets, /*IsDeclaration=*/true); } auto *FD = dyn_cast(FunctionLikeParentDecl); - if (IsMethodExtraction && IsDefinition && + if (isMethodExtraction() && IsDefinition && !FD->getDescribedFunctionTemplate()) { // Print the class template parameter lists for an out-of-line method. for (unsigned I = 0, @@ -1752,13 +1831,13 @@ llvm::Expected ExtractOperation::perform( OS << "\n"; } } - if (IsMethodExtraction && isEnclosingMethodStatic(FunctionLikeParentDecl)) + if (isMethodExtraction() && isEnclosingMethodStatic(FunctionLikeParentDecl)) OS << "static "; - else if (!IsMethodExtraction) + else if (!isMethodExtraction()) OS << (isInHeader(FunctionExtractionLoc, SM) ? "inline " : "static "); ReturnType.print(OS, PP); OS << ' '; - if (IsMethodExtraction && IsDefinition) + if (isMethodExtraction() && IsDefinition) printEnclosingMethodScope(FunctionLikeParentDecl, OS, PP); unsigned NameOffset = OS.str().size(); OS << ExtractedName << '('; @@ -1770,14 +1849,14 @@ llvm::Expected ExtractOperation::perform( Var.ParameterType.print(OS, PP, /*PlaceHolder=*/Var.getName()); } OS << ')'; - if (IsMethodExtraction && isEnclosingMethodConst(FunctionLikeParentDecl)) + if (isMethodExtraction() && isEnclosingMethodConst(FunctionLikeParentDecl)) OS << " const"; return RefactoringReplacement::AssociatedSymbolLocation( NameOffset, /*IsDeclaration=*/true); ; }; - if (IsMethodExtraction && + if (isMethodExtraction() && isEnclosingMethodOutOfLine(FunctionLikeParentDecl)) { // The location of the declaration should be either before the original // declararation, or, if this method has not declaration, somewhere @@ -1786,7 +1865,7 @@ llvm::Expected ExtractOperation::perform( SourceLocation DeclarationLoc; if (FunctionLikeParentDecl->getCanonicalDecl() != FunctionLikeParentDecl) { DeclarationLoc = computeFunctionExtractionLocation( - FunctionLikeParentDecl->getCanonicalDecl(), IsMethodExtraction); + FunctionLikeParentDecl->getCanonicalDecl(), isMethodExtraction()); Placement = MethodDeclarationPlacement::Before; } else { auto LocAndPlacement = @@ -1856,7 +1935,7 @@ llvm::Expected ExtractOperation::perform( } InsertedOS << CandidateExtractionInfo[SelectedCandidateIndex].PreInsertedText; llvm::SmallVector NameOffsets; - if (IsMethodExtraction && EnclosingObjCMethod) { + if (isMethodExtraction() && EnclosingObjCMethod) { InsertedOS << "[self "; NameOffsets.push_back(InsertedOS.str().size()); InsertedOS << ExtractedName; diff --git a/clang/lib/Tooling/Refactor/ExtractionUtils.cpp b/clang/lib/Tooling/Refactor/ExtractionUtils.cpp index 208685ed7b7d0..a733add982ed6 100644 --- a/clang/lib/Tooling/Refactor/ExtractionUtils.cpp +++ b/clang/lib/Tooling/Refactor/ExtractionUtils.cpp @@ -95,7 +95,9 @@ class ExtractedVariableInsertionLocFinder if (auto *CS = dyn_cast(S)) { IsPrevCompoundStmt = true; InsertionCandidateStack.emplace_back(CS, nullptr); - return RecursiveASTVisitor::TraverseStmt(S); + RecursiveASTVisitor::TraverseStmt(S); + InsertionCandidateStack.pop_back(); + return true; } return RecursiveASTVisitor::TraverseStmt(S); } diff --git a/clang/test/Refactor/Extract/extract-expression-into-var.cpp b/clang/test/Refactor/Extract/extract-expression-into-var.cpp new file mode 100644 index 0000000000000..f1ae8f5ae41f1 --- /dev/null +++ b/clang/test/Refactor/Extract/extract-expression-into-var.cpp @@ -0,0 +1,39 @@ +int call(int x); + +void extractExpressionIntoVar(int x, int y, int z) { + { + // expr1-begin: +1:13 + int p = x + y - z; // CHECK: "int extractedExpr = x + y;\n" [[@LINE]]:5 -> [[@LINE]]:5 [Symbol extracted-decl 0 1:5 -> 1:18] + // expr1-end: -1:19 // CHECK: "extractedExpr" [[@LINE-1]]:13 -> [[@LINE-1]]:18 [Symbol extracted-decl-ref 0 1:1 -> 1:14] + // expr2-begin: +1:6 // CHECK: "std::function extractedExpr = [&](int x) {\n p = x;\n };\n" [[@LINE+1]]:5 -> [[@LINE+1]]:5 [Symbol extracted-decl 0 1:27 -> 1:40] + ([&](int x) { + p = x; + })(0); + // expr2-end: -1:6 // CHECK: "extractedExpr" [[@LINE-3]]:6 -> [[@LINE-1]]:6 + } + #define MACROARG(x) (x) + // expr3-begin: +1:20 + int p = MACROARG(y + z) - x; // CHECK: "int extractedExpr = y + z;\n" [[@LINE]]:3 -> [[@LINE]]:3 + // expr3-end: -1:25 // CHECK: "extractedExpr" [[@LINE-1]]:20 -> [[@LINE-1]]:25 + #define MACROSTMT(x, y) int var = (x) + (y); + // expr4-begin: +1:16 + MACROSTMT(0, call(p * z)) // CHECK: "int extractedExpr = call(p * z);\n" [[@LINE]]:3 -> [[@LINE]]:3 + // expr4-end: -1:27 // CHECK: "extractedExpr" [[@LINE-1]]:16 -> [[@LINE-1]]:27 +} + +// RUN: clang-refactor-test perform -action extract-expression -selected=expr1 -selected=expr2 -selected=expr3 -selected=expr4 -emit-associated %s -std=c++11 | FileCheck %s + +// RUN: clang-refactor-test list-actions -at=%s:6:13 -selected=%s:6:13-6:18 %s -std=c++11 | FileCheck --check-prefix=CHECK-ACTION %s +// CHECK-ACTION: Extract Expression + +void dontExtractStatement(int x, int y) { + // stmt1-begin: +1:3 + x = y; + // stmt1-end: -1:8 + // stmt2-begin: +1:3 + return; + // stmt2-end: +0:1 +} + +// RUN: not clang-refactor-test perform -action extract-expression -selected=stmt1 -selected=stmt2 %s -std=c++11 2>&1 | FileCheck --check-prefix=CHECK-FAIL %s +// CHECK-FAIL: Failed to initiate the refactoring action! diff --git a/clang/test/Refactor/Extract/extract-expression-into-var.m b/clang/test/Refactor/Extract/extract-expression-into-var.m new file mode 100644 index 0000000000000..ec94dab8a8f4f --- /dev/null +++ b/clang/test/Refactor/Extract/extract-expression-into-var.m @@ -0,0 +1,12 @@ +// RUN: clang-refactor-test perform -action extract-expression -selected=expr1 -selected=expr2 %s | FileCheck %s + +void extractExpressionIntoVar(int x, int y, int z) { + // expr1-begin: +1:9 + (void)@selector(foo:bar:); // CHECK: "SEL extractedExpr = @selector(foo:bar:);\n" [[@LINE]]:3 -> [[@LINE]]:3 + // expr1-end: -1:28 // CHECK: "extractedExpr" [[@LINE-1]]:9 -> [[@LINE-1]]:28 + // expr2-begin: +1:4 // CHECK: "void (^extractedExpr)(void) = ^ {\n // do nothing\n };\n" [[@LINE+1]]:3 -> [[@LINE+1]]:3 + (^ { + // do nothing + })(); + // expr2-end: -1:4 // CHECK: "extractedExpr" [[@LINE-3]]:4 -> [[@LINE-1]]:4 +} From 2714f2fd8148d9b786ee0c4547cafb683c51f981 Mon Sep 17 00:00:00 2001 From: David Farler Date: Mon, 26 Jun 2017 17:57:22 -0700 Subject: [PATCH 172/585] [index/build] Upstream Clang index-while-building This patch upstreams previously AppleInternal index-while-building changes released in the Xcode 9 timeframe. apple-llvm-split-commit: 0524768abaa7caa7630b7d1f3dc46c52f716962c apple-llvm-split-dir: clang/ --- .../clang/Basic/DiagnosticFrontendKinds.td | 4 + clang/include/clang/Basic/DiagnosticGroups.td | 1 + .../clang/DirectoryWatcher/DirectoryWatcher.h | 47 ++ clang/include/clang/Driver/Job.h | 6 +- clang/include/clang/Driver/Options.td | 7 + .../include/clang/Frontend/FrontendOptions.h | 9 +- clang/include/clang/Index/IndexDataStore.h | 102 +++ .../clang/Index/IndexDataStoreSymbolUtils.h | 53 ++ clang/include/clang/Index/IndexRecordReader.h | 109 +++ clang/include/clang/Index/IndexRecordWriter.h | 102 +++ clang/include/clang/Index/IndexUnitReader.h | 85 +++ clang/include/clang/Index/IndexUnitWriter.h | 140 ++++ clang/include/clang/Index/IndexingAction.h | 30 + clang/include/indexstore/IndexStoreCXX.h | 502 ++++++++++++++ clang/include/indexstore/indexstore.h | 487 +++++++++++++ clang/lib/CMakeLists.txt | 1 + clang/lib/DirectoryWatcher/CMakeLists.txt | 8 + .../lib/DirectoryWatcher/DirectoryWatcher.cpp | 275 ++++++++ clang/lib/Driver/Driver.cpp | 4 +- clang/lib/Driver/Job.cpp | 21 + clang/lib/Driver/ToolChains/Clang.cpp | 20 + clang/lib/Driver/ToolChains/Darwin.cpp | 4 + clang/lib/Frontend/CMakeLists.txt | 1 + clang/lib/Frontend/CompilerInstance.cpp | 15 +- clang/lib/Frontend/CompilerInvocation.cpp | 4 + .../ExecuteCompilerInvocation.cpp | 7 + clang/lib/Index/BitstreamVisitor.h | 163 +++++ clang/lib/Index/CMakeLists.txt | 9 + clang/lib/Index/ClangIndexRecordWriter.cpp | 128 ++++ clang/lib/Index/ClangIndexRecordWriter.h | 55 ++ clang/lib/Index/FileIndexRecord.cpp | 59 ++ clang/lib/Index/FileIndexRecord.h | 69 ++ clang/lib/Index/IndexDataStore.cpp | 259 +++++++ clang/lib/Index/IndexDataStoreUtils.cpp | 398 +++++++++++ clang/lib/Index/IndexDataStoreUtils.h | 116 ++++ clang/lib/Index/IndexRecordHasher.cpp | 468 +++++++++++++ clang/lib/Index/IndexRecordHasher.h | 58 ++ clang/lib/Index/IndexRecordReader.cpp | 407 +++++++++++ clang/lib/Index/IndexRecordWriter.cpp | 366 ++++++++++ clang/lib/Index/IndexUnitReader.cpp | 516 ++++++++++++++ clang/lib/Index/IndexUnitWriter.cpp | 628 +++++++++++++++++ clang/lib/Index/IndexingAction.cpp | 648 +++++++++++++++++- clang/lib/Index/IndexingContext.cpp | 66 +- clang/lib/Index/IndexingContext.h | 15 +- clang/test/Index/Store/Inputs/head.h | 3 + clang/test/Index/Store/Inputs/json.c.json | 151 ++++ clang/test/Index/Store/Inputs/module/ModDep.h | 3 + .../Index/Store/Inputs/module/ModSystem.h | 4 + clang/test/Index/Store/Inputs/module/ModTop.h | 4 + .../Index/Store/Inputs/module/ModTopSub1.h | 1 + .../Index/Store/Inputs/module/ModTopSub2.h | 1 + .../Store/Inputs/module/module.modulemap | 12 + clang/test/Index/Store/Inputs/overlay.yaml | 6 + clang/test/Index/Store/Inputs/print-unit.h | 2 + clang/test/Index/Store/Inputs/sys/another.h | 2 + clang/test/Index/Store/Inputs/sys/syshead.h | 4 + clang/test/Index/Store/Inputs/test1.c | 3 + clang/test/Index/Store/Inputs/test2.c | 3 + clang/test/Index/Store/Inputs/test3.cpp | 2 + clang/test/Index/Store/Inputs/using-overlay.h | 1 + clang/test/Index/Store/assembly-invocation.c | 3 + .../Index/Store/external-source-symbol-hash.m | 47 ++ .../test/Index/Store/handle-prebuilt-module.m | 25 + clang/test/Index/Store/json-with-module.m | 9 + .../test/Index/Store/json-with-module.m.json | 151 ++++ clang/test/Index/Store/json-with-pch.c | 10 + clang/test/Index/Store/json-with-pch.c.json | 96 +++ clang/test/Index/Store/json.c | 10 + clang/test/Index/Store/print-record.mm | 28 + clang/test/Index/Store/print-unit.c | 39 ++ .../Index/Store/print-units-with-modules.m | 59 ++ clang/test/Index/Store/print-units-with-pch.c | 29 + clang/test/Index/Store/record-hash-crash.cpp | 12 + clang/test/Index/Store/record-hash.cpp | 12 + clang/test/Index/Store/relative-out-path.c | 19 + clang/test/Index/Store/syntax-only.c | 11 + clang/test/Index/Store/unit-with-vfs.c | 12 + clang/test/Index/Store/unit-workdir-prefix.c | 30 + clang/test/Index/Store/using-libstdcpp-arc.mm | 10 + clang/tools/CMakeLists.txt | 1 + clang/tools/IndexStore/CMakeLists.txt | 94 +++ clang/tools/IndexStore/IndexStore.cpp | 647 +++++++++++++++++ clang/tools/IndexStore/IndexStore.exports | 69 ++ clang/tools/c-index-test/CMakeLists.txt | 20 + clang/tools/c-index-test/JSONAggregation.cpp | 409 +++++++++++ clang/tools/c-index-test/JSONAggregation.h | 24 + clang/tools/c-index-test/core_main.cpp | 607 +++++++++++++++- 87 files changed, 9127 insertions(+), 30 deletions(-) create mode 100644 clang/include/clang/DirectoryWatcher/DirectoryWatcher.h create mode 100644 clang/include/clang/Index/IndexDataStore.h create mode 100644 clang/include/clang/Index/IndexDataStoreSymbolUtils.h create mode 100644 clang/include/clang/Index/IndexRecordReader.h create mode 100644 clang/include/clang/Index/IndexRecordWriter.h create mode 100644 clang/include/clang/Index/IndexUnitReader.h create mode 100644 clang/include/clang/Index/IndexUnitWriter.h create mode 100644 clang/include/indexstore/IndexStoreCXX.h create mode 100644 clang/include/indexstore/indexstore.h create mode 100644 clang/lib/DirectoryWatcher/CMakeLists.txt create mode 100644 clang/lib/DirectoryWatcher/DirectoryWatcher.cpp create mode 100644 clang/lib/Index/BitstreamVisitor.h create mode 100644 clang/lib/Index/ClangIndexRecordWriter.cpp create mode 100644 clang/lib/Index/ClangIndexRecordWriter.h create mode 100644 clang/lib/Index/FileIndexRecord.cpp create mode 100644 clang/lib/Index/FileIndexRecord.h create mode 100644 clang/lib/Index/IndexDataStore.cpp create mode 100644 clang/lib/Index/IndexDataStoreUtils.cpp create mode 100644 clang/lib/Index/IndexDataStoreUtils.h create mode 100644 clang/lib/Index/IndexRecordHasher.cpp create mode 100644 clang/lib/Index/IndexRecordHasher.h create mode 100644 clang/lib/Index/IndexRecordReader.cpp create mode 100644 clang/lib/Index/IndexRecordWriter.cpp create mode 100644 clang/lib/Index/IndexUnitReader.cpp create mode 100644 clang/lib/Index/IndexUnitWriter.cpp create mode 100644 clang/test/Index/Store/Inputs/head.h create mode 100644 clang/test/Index/Store/Inputs/json.c.json create mode 100644 clang/test/Index/Store/Inputs/module/ModDep.h create mode 100644 clang/test/Index/Store/Inputs/module/ModSystem.h create mode 100644 clang/test/Index/Store/Inputs/module/ModTop.h create mode 100644 clang/test/Index/Store/Inputs/module/ModTopSub1.h create mode 100644 clang/test/Index/Store/Inputs/module/ModTopSub2.h create mode 100644 clang/test/Index/Store/Inputs/module/module.modulemap create mode 100644 clang/test/Index/Store/Inputs/overlay.yaml create mode 100644 clang/test/Index/Store/Inputs/print-unit.h create mode 100644 clang/test/Index/Store/Inputs/sys/another.h create mode 100644 clang/test/Index/Store/Inputs/sys/syshead.h create mode 100644 clang/test/Index/Store/Inputs/test1.c create mode 100644 clang/test/Index/Store/Inputs/test2.c create mode 100644 clang/test/Index/Store/Inputs/test3.cpp create mode 100644 clang/test/Index/Store/Inputs/using-overlay.h create mode 100644 clang/test/Index/Store/assembly-invocation.c create mode 100644 clang/test/Index/Store/external-source-symbol-hash.m create mode 100644 clang/test/Index/Store/handle-prebuilt-module.m create mode 100644 clang/test/Index/Store/json-with-module.m create mode 100644 clang/test/Index/Store/json-with-module.m.json create mode 100644 clang/test/Index/Store/json-with-pch.c create mode 100644 clang/test/Index/Store/json-with-pch.c.json create mode 100644 clang/test/Index/Store/json.c create mode 100644 clang/test/Index/Store/print-record.mm create mode 100644 clang/test/Index/Store/print-unit.c create mode 100644 clang/test/Index/Store/print-units-with-modules.m create mode 100644 clang/test/Index/Store/print-units-with-pch.c create mode 100644 clang/test/Index/Store/record-hash-crash.cpp create mode 100644 clang/test/Index/Store/record-hash.cpp create mode 100644 clang/test/Index/Store/relative-out-path.c create mode 100644 clang/test/Index/Store/syntax-only.c create mode 100644 clang/test/Index/Store/unit-with-vfs.c create mode 100644 clang/test/Index/Store/unit-workdir-prefix.c create mode 100644 clang/test/Index/Store/using-libstdcpp-arc.mm create mode 100644 clang/tools/IndexStore/CMakeLists.txt create mode 100644 clang/tools/IndexStore/IndexStore.cpp create mode 100644 clang/tools/IndexStore/IndexStore.exports create mode 100644 clang/tools/c-index-test/JSONAggregation.cpp create mode 100644 clang/tools/c-index-test/JSONAggregation.h diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index 9e4eeee94f1f9..5045cc53a0766 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -214,6 +214,10 @@ def err_modules_embed_file_not_found : Error<"file '%0' specified by '-fmodules-embed-file=' not found">, DefaultFatal; +def remark_index_producing_module_file_data : Remark<"producing index data for " + "module file '%0'">, + InGroup; + def err_test_module_file_extension_version : Error< "test module file extension '%0' has different version (%1.%2) than expected " "(%3.%4)">; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index ea3ba2f04e610..5b6806cbb55a6 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -292,6 +292,7 @@ def MissingFieldInitializers : DiagGroup<"missing-field-initializers">; def ModuleBuild : DiagGroup<"module-build">; def ModuleConflict : DiagGroup<"module-conflict">; def ModuleFileExtension : DiagGroup<"module-file-extension">; +def IndexStore : DiagGroup<"index-store">; def NewlineEOF : DiagGroup<"newline-eof">; def Nullability : DiagGroup<"nullability">; def NullabilityDeclSpec : DiagGroup<"nullability-declspec">; diff --git a/clang/include/clang/DirectoryWatcher/DirectoryWatcher.h b/clang/include/clang/DirectoryWatcher/DirectoryWatcher.h new file mode 100644 index 0000000000000..09d17a996126c --- /dev/null +++ b/clang/include/clang/DirectoryWatcher/DirectoryWatcher.h @@ -0,0 +1,47 @@ +//===- DirectoryWatcher.h - Listens for directory file changes --*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// \file +/// \brief Utility class for listening for file system changes in a directory. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_DIRECTORYWATCHER_DIRECTORYWATCHER_H +#define LLVM_CLANG_DIRECTORYWATCHER_DIRECTORYWATCHER_H + +#include "clang/Basic/LLVM.h" +#include "clang/Index/IndexDataStore.h" +#include +#include +#include + +namespace clang { + +/// Provides notifications for file system changes in a directory. +/// +/// Guarantees that the first time the directory is processed, the receiver will +/// be invoked even if the directory is empty. +class DirectoryWatcher : public index::AbstractDirectoryWatcher { + struct Implementation; + Implementation &Impl; + + DirectoryWatcher(); + + DirectoryWatcher(const DirectoryWatcher&) = delete; + DirectoryWatcher &operator =(const DirectoryWatcher&) = delete; + +public: + ~DirectoryWatcher(); + + static std::unique_ptr + create(StringRef Path, EventReceiver Receiver, bool waitInitialSync, + std::string &Error); +}; + +} // namespace clang + +#endif diff --git a/clang/include/clang/Driver/Job.h b/clang/include/clang/Driver/Job.h index ff88256b8c0d6..09f7ab50ce574 100644 --- a/clang/include/clang/Driver/Job.h +++ b/clang/include/clang/Driver/Job.h @@ -34,9 +34,11 @@ using llvm::opt::ArgStringList; struct CrashReportInfo { StringRef Filename; StringRef VFSPath; + StringRef IndexStorePath; - CrashReportInfo(StringRef Filename, StringRef VFSPath) - : Filename(Filename), VFSPath(VFSPath) {} + CrashReportInfo(StringRef Filename, StringRef VFSPath, + StringRef IndexStorePath) + : Filename(Filename), VFSPath(VFSPath), IndexStorePath(IndexStorePath) {} }; /// Command - An executable path/name and argument vector to diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 4a9fd1f9700a8..18754243c21a1 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -310,6 +310,13 @@ def objcmt_whitelist_dir_path: Joined<["-"], "objcmt-whitelist-dir-path=">, Flag def : Joined<["-"], "objcmt-white-list-dir-path=">, Flags<[CC1Option]>, Alias; +def index_store_path : Separate<["-"], "index-store-path">, Flags<[CC1Option]>, + HelpText<"Enable indexing with the specified data store path">; +def index_ignore_system_symbols : Flag<["-"], "index-ignore-system-symbols">, Flags<[CC1Option]>, + HelpText<"Ignore symbols from system headers">; +def index_record_codegen_name : Flag<["-"], "index-record-codegen-name">, Flags<[CC1Option]>, + HelpText<"Record the codegen name for symbols">; + // Make sure all other -ccc- options are rejected. def ccc_ : Joined<["-"], "ccc-">, Group, Flags<[Unsupported]>; diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index e757a7e397e35..b784a6f03697e 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -256,6 +256,10 @@ class FrontendOptions { std::string MTMigrateDir; std::string ARCMTMigrateReportOut; + std::string IndexStorePath; + unsigned IndexIgnoreSystemSymbols : 1; + unsigned IndexRecordCodegenName : 1; + /// The input files and their types. std::vector Inputs; @@ -333,8 +337,9 @@ class FrontendOptions { SkipFunctionBodies(false), UseGlobalModuleIndex(true), GenerateGlobalModuleIndex(true), ASTDumpDecls(false), ASTDumpLookups(false), BuildingImplicitModule(false), ModulesEmbedAllFiles(false), - IncludeTimestamps(true), ARCMTAction(ARCMT_None), - ObjCMTAction(ObjCMT_None), ProgramAction(frontend::ParseSyntaxOnly) + IncludeTimestamps(true), ARCMTAction(ARCMT_None), ObjCMTAction(ObjCMT_None), + IndexIgnoreSystemSymbols(false), IndexRecordCodegenName(false), + ProgramAction(frontend::ParseSyntaxOnly) {} /// getInputKindForExtension - Return the appropriate input kind for a file diff --git a/clang/include/clang/Index/IndexDataStore.h b/clang/include/clang/Index/IndexDataStore.h new file mode 100644 index 0000000000000..714ccddc8c450 --- /dev/null +++ b/clang/include/clang/Index/IndexDataStore.h @@ -0,0 +1,102 @@ +//===--- IndexDataStore.h - Index data store info -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INDEX_INDEXDATASTORE_H +#define LLVM_CLANG_INDEX_INDEXDATASTORE_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/STLExtras.h" +#include +#include +#include +#include + +namespace clang { +namespace index { + +class AbstractDirectoryWatcher { +public: + enum class EventKind { + /// A file was added. + Added, + /// A file was removed. + Removed, + /// A file was modified. + Modified, + /// The watched directory got deleted. No more events will follow. + DirectoryDeleted, + }; + + struct Event { + EventKind Kind; + std::string Filename; + timespec ModTime; + }; + + typedef std::function Events, bool isInitial)> EventReceiver; + typedef std::unique_ptr(CreateFnTy) + (StringRef Path, EventReceiver Receiver, bool waitInitialSync, std::string &Error); + + virtual ~AbstractDirectoryWatcher() {} +}; + +class IndexDataStore { +public: + ~IndexDataStore(); + + static std::unique_ptr + create(StringRef IndexStorePath, std::string &Error); + + StringRef getFilePath() const; + bool foreachUnitName(bool sorted, + llvm::function_ref receiver); + + static unsigned getFormatVersion(); + + enum class UnitEventKind { + Added, + Removed, + Modified, + /// The directory got deleted. No more events will follow. + DirectoryDeleted, + }; + struct UnitEvent { + UnitEventKind Kind; + StringRef UnitName; + timespec ModTime; + }; + struct UnitEventNotification { + bool IsInitial; + ArrayRef Events; + }; + typedef std::function UnitEventHandler; + + void setUnitEventHandler(UnitEventHandler Handler); + /// \returns true if an error occurred. + bool startEventListening(llvm::function_ref createFn, + bool waitInitialSync, std::string &Error); + void stopEventListening(); + + void discardUnit(StringRef UnitName); + void discardRecord(StringRef RecordName); + + void purgeStaleData(); + +private: + IndexDataStore(void *Impl) : Impl(Impl) {} + + void *Impl; // An IndexDataStoreImpl. +}; + +} // namespace index +} // namespace clang + +#endif diff --git a/clang/include/clang/Index/IndexDataStoreSymbolUtils.h b/clang/include/clang/Index/IndexDataStoreSymbolUtils.h new file mode 100644 index 0000000000000..e1d982de094cd --- /dev/null +++ b/clang/include/clang/Index/IndexDataStoreSymbolUtils.h @@ -0,0 +1,53 @@ +//===--- IndexDataStoreSymbolUtils.h - Utilities for indexstore symbols ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INDEX_INDEXDATASTORESYMBOLUTILS_H +#define LLVM_CLANG_INDEX_INDEXDATASTORESYMBOLUTILS_H + +#include "indexstore/indexstore.h" +#include "clang/Index/IndexSymbol.h" + +namespace clang { +namespace index { + +/// Map an indexstore_symbol_kind_t to a SymbolKind, handling unknown values. +SymbolKind getSymbolKind(indexstore_symbol_kind_t K); + +SymbolSubKind getSymbolSubKind(indexstore_symbol_subkind_t K); + +/// Map an indexstore_symbol_language_t to a SymbolLanguage, handling unknown +/// values. +SymbolLanguage getSymbolLanguage(indexstore_symbol_language_t L); + +/// Map an indexstore representation to a SymbolPropertySet, handling +/// unknown values. +SymbolPropertySet getSymbolProperties(uint64_t Props); + +/// Map an indexstore representation to a SymbolRoleSet, handling unknown +/// values. +SymbolRoleSet getSymbolRoles(uint64_t Roles); + +/// Map a SymbolLanguage to a indexstore_symbol_language_t. +indexstore_symbol_kind_t getIndexStoreKind(SymbolKind K); + +indexstore_symbol_subkind_t getIndexStoreSubKind(SymbolSubKind K); + +/// Map a SymbolLanguage to a indexstore_symbol_language_t. +indexstore_symbol_language_t getIndexStoreLang(SymbolLanguage L); + +/// Map a SymbolPropertySet to its indexstore representation. +uint64_t getIndexStoreProperties(SymbolPropertySet Props); + +/// Map a SymbolRoleSet to its indexstore representation. +uint64_t getIndexStoreRoles(SymbolRoleSet Roles); + +} // end namespace index +} // end namespace clang + +#endif // LLVM_CLANG_INDEX_INDEXDATASTORESYMBOLUTILS_H diff --git a/clang/include/clang/Index/IndexRecordReader.h b/clang/include/clang/Index/IndexRecordReader.h new file mode 100644 index 0000000000000..ef8edff2db86c --- /dev/null +++ b/clang/include/clang/Index/IndexRecordReader.h @@ -0,0 +1,109 @@ +//===--- IndexRecordReader.h - Index record deserialization ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INDEX_INDEXRECORDREADER_H +#define LLVM_CLANG_INDEX_INDEXRECORDREADER_H + +#include "clang/Index/IndexSymbol.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace llvm { + class MemoryBuffer; +} + +namespace clang { +namespace index { + +struct IndexRecordDecl { + unsigned DeclID; + SymbolInfo SymInfo; + SymbolRoleSet Roles; + SymbolRoleSet RelatedRoles; + StringRef Name; + StringRef USR; + StringRef CodeGenName; +}; + +struct IndexRecordRelation { + SymbolRoleSet Roles; + const IndexRecordDecl *Dcl = nullptr; + + IndexRecordRelation() = default; + IndexRecordRelation(SymbolRoleSet Roles, const IndexRecordDecl *Dcl) + : Roles(Roles), Dcl(Dcl) {} +}; + +struct IndexRecordOccurrence { + const IndexRecordDecl *Dcl; + SmallVector Relations; + SymbolRoleSet Roles; + unsigned Line; + unsigned Column; +}; + +class IndexRecordReader { + IndexRecordReader(); + +public: + static std::unique_ptr + createWithRecordFilename(StringRef RecordFilename, StringRef StorePath, + std::string &Error); + static std::unique_ptr + createWithFilePath(StringRef FilePath, std::string &Error); + static std::unique_ptr + createWithBuffer(std::unique_ptr Buffer, + std::string &Error); + + ~IndexRecordReader(); + + struct DeclSearchReturn { + bool AcceptDecl; + bool ContinueSearch; + }; + typedef DeclSearchReturn(DeclSearchCheck)(const IndexRecordDecl &); + + /// Goes through and passes record decls, after filtering using a \c Checker + /// function. + /// + /// Resulting decls can be used as filter for \c foreachOccurrence. This + /// allows allocating memory only for the record decls that the caller is + /// interested in. + bool searchDecls(llvm::function_ref Checker, + llvm::function_ref Receiver); + + /// \param NoCache if true, avoids allocating memory for the decls. + /// Useful when the caller does not intend to keep \c IndexRecordReader + /// for more queries. + bool foreachDecl(bool NoCache, + llvm::function_ref Receiver); + + /// \param DeclsFilter if non-empty indicates the list of decls that we want + /// to get occurrences for. An empty array indicates that we want occurrences + /// for all decls. + /// \param RelatedDeclsFilter Same as \c DeclsFilter but for related decls. + bool foreachOccurrence(ArrayRef DeclsFilter, + ArrayRef RelatedDeclsFilter, + llvm::function_ref Receiver); + bool foreachOccurrence( + llvm::function_ref Receiver); + + bool foreachOccurrenceInLineRange(unsigned lineStart, unsigned lineCount, + llvm::function_ref Receiver); + + struct Implementation; +private: + Implementation &Impl; +}; + +} // namespace index +} // namespace clang + +#endif diff --git a/clang/include/clang/Index/IndexRecordWriter.h b/clang/include/clang/Index/IndexRecordWriter.h new file mode 100644 index 0000000000000..8a9720aa98fc6 --- /dev/null +++ b/clang/include/clang/Index/IndexRecordWriter.h @@ -0,0 +1,102 @@ +//===--- IndexRecordWriter.h - Index record serialization -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INDEX_INDEXRECORDWRITER_H +#define LLVM_CLANG_INDEX_INDEXRECORDWRITER_H + +#include "clang/Index/IndexSymbol.h" +#include "llvm/ADT/SmallString.h" + +namespace clang { +namespace index { + +namespace writer { +/// An opaque pointer to a declaration or other symbol used by the +/// IndexRecordWriter to identify when two occurrences refer to the same symbol, +/// and as a token for getting information about a symbol from the caller. +typedef const void *OpaqueDecl; + +/// An indexer symbol suitable for serialization. +/// +/// This includes all the information about the symbol that will be serialized +/// except for roles, which are synthesized by looking at all the occurrences. +/// +/// \seealso IndexRecordDecl +/// \note this struct is generally accompanied by a buffer that owns the string +/// storage. It should not be stored permanently. +struct Symbol { + SymbolInfo SymInfo; + StringRef Name; + StringRef USR; + StringRef CodeGenName; +}; + +/// An relation to an opaque symbol. +/// \seealso IndexRecordRelation +struct SymbolRelation { + OpaqueDecl RelatedSymbol; + SymbolRoleSet Roles; +}; + +typedef llvm::function_ref &Scratch)> + SymbolWriterCallback; +} // end namespace writer + +/// A language-independent utility for serializing index record files. +/// +/// Internally, this class is a small state machine. Users should first call +/// beginRecord, and if the file does not already exist, then proceed to add +/// all symbol occurrences (addOccurrence) and finally finish with endRecord. +class IndexRecordWriter { + SmallString<64> RecordsPath; ///< The records directory path. + void *Record = nullptr; ///< The state of the current record. +public: + IndexRecordWriter(StringRef IndexPath); + + enum class Result { + Success, + Failure, + AlreadyExists, + }; + + /// Begin writing a record for the file \p Filename with contents uniquely + /// identified by \p RecordHash. + /// + /// \param Filename the name of the file this is a record for. + /// \param RecordHash the unique hash of the record contents. + /// \param Error on failure, set to the error message. + /// \param RecordFile if non-null, this is set to the name of the record file. + /// + /// \returns Success if we should continue writing this record, AlreadyExists + /// if the record file has already been written, or Failure if there was an + /// error, in which case \p Error will be set. + Result beginRecord(StringRef Filename, llvm::hash_code RecordHash, + std::string &Error, std::string *RecordFile = nullptr); + + /// Finish writing the record file. + /// + /// \param Error on failure, set to the error message. + /// \param GetSymbolForDecl a callback mapping an writer::OpaqueDecl to its + /// writer::Symbol. This is how the language-specific symbol information is + /// provided to the IndexRecordWriter. The scratch parameter can be used for + /// any necessary storage. + /// + /// \return Success, or Failure and sets \p Error. + Result endRecord(std::string &Error, + writer::SymbolWriterCallback GetSymbolForDecl); + + /// Add an occurrence of the symbol \p D with the given \p Roles and location. + void addOccurrence(writer::OpaqueDecl D, SymbolRoleSet Roles, unsigned Line, + unsigned Column, ArrayRef Related); +}; + +} // end namespace index +} // end namespace clang + +#endif // LLVM_CLANG_INDEX_INDEXRECORDWRITER_H diff --git a/clang/include/clang/Index/IndexUnitReader.h b/clang/include/clang/Index/IndexUnitReader.h new file mode 100644 index 0000000000000..ccd2dceb21bce --- /dev/null +++ b/clang/include/clang/Index/IndexUnitReader.h @@ -0,0 +1,85 @@ +//===--- IndexUnitReader.h - Index unit deserialization -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INDEX_INDEXUNITREADER_H +#define LLVM_CLANG_INDEX_INDEXUNITREADER_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Chrono.h" + +namespace clang { +namespace index { + +class IndexUnitReader { +public: + enum class DependencyKind { + Unit, + Record, + File, + }; + + ~IndexUnitReader(); + + static std::unique_ptr + createWithUnitFilename(StringRef UnitFilename, StringRef StorePath, + std::string &Error); + static std::unique_ptr + createWithFilePath(StringRef FilePath, std::string &Error); + + static Optional> + getModificationTimeForUnit(StringRef UnitFilename, StringRef StorePath, + std::string &Error); + + StringRef getProviderIdentifier() const; + StringRef getProviderVersion() const; + + llvm::sys::TimePoint<> getModificationTime() const; + StringRef getWorkingDirectory() const; + StringRef getOutputFile() const; + StringRef getSysrootPath() const; + StringRef getMainFilePath() const; + StringRef getModuleName() const; + StringRef getTarget() const; + bool hasMainFile() const; + bool isSystemUnit() const; + bool isModuleUnit() const; + bool isDebugCompilation() const; + + struct DependencyInfo { + DependencyKind Kind; + bool IsSystem; + StringRef UnitOrRecordName; + StringRef FilePath; + StringRef ModuleName; + size_t FileSize; + time_t ModTime; + }; + struct IncludeInfo { + StringRef SourcePath; + unsigned SourceLine; + StringRef TargetPath; + }; + /// Unit dependencies are provided ahead of record ones, record ones + /// ahead of the file ones. + bool foreachDependency(llvm::function_ref Receiver); + + bool foreachInclude(llvm::function_ref Receiver); + +private: + IndexUnitReader(void *Impl) : Impl(Impl) {} + + void *Impl; // An IndexUnitReaderImpl. +}; + +} // namespace index +} // namespace clang + +#endif diff --git a/clang/include/clang/Index/IndexUnitWriter.h b/clang/include/clang/Index/IndexUnitWriter.h new file mode 100644 index 0000000000000..40d2c112ec0f5 --- /dev/null +++ b/clang/include/clang/Index/IndexUnitWriter.h @@ -0,0 +1,140 @@ +//===--- IndexUnitWriter.h - Index unit serialization ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INDEX_INDEXUNITWRITER_H +#define LLVM_CLANG_INDEX_INDEXUNITWRITER_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallString.h" +#include +#include + +namespace llvm { + class BitstreamWriter; +} + +namespace clang { + class FileEntry; + class FileManager; + +namespace index { + +namespace writer { +/// An opaque pointer to a module used by the IndexUnitWriter to associate +/// record and file dependencies with a module, and as a token for getting +/// information about the module from the caller. +typedef const void *OpaqueModule; + +/// Module info suitable for serialization. +/// +/// This is used for top-level modules and sub-modules. +struct ModuleInfo { + /// Full, dot-separate, module name. + StringRef Name; +}; + +typedef llvm::function_ref &Scratch)> + ModuleInfoWriterCallback; +} // end namespace writer + +class IndexUnitWriter { + FileManager &FileMgr; + SmallString<64> UnitsPath; + std::string ProviderIdentifier; + std::string ProviderVersion; + std::string OutputFile; + std::string ModuleName; + const FileEntry *MainFile; + bool IsSystemUnit; + bool IsModuleUnit; + bool IsDebugCompilation; + std::string TargetTriple; + std::string WorkDir; + std::string SysrootPath; + std::function &Scratch)> GetInfoForModuleFn; + struct FileInclude { + int Index; + unsigned Line; + }; + struct FileEntryData { + const FileEntry *File; + bool IsSystem; + int ModuleIndex; + std::vector Includes; + }; + std::vector Files; + std::vector Modules; + llvm::DenseMap IndexByFile; + llvm::DenseMap IndexByModule; + llvm::DenseSet SeenASTFiles; + struct RecordOrUnitData { + std::string Name; + int FileIndex; + int ModuleIndex; + bool IsSystem; + }; + std::vector Records; + std::vector ASTFileUnits; + +public: + /// \param MainFile the main file for a compiled source file. This should be + /// null for PCH and module units. + /// \param IsSystem true for system module units, false otherwise. + IndexUnitWriter(FileManager &FileMgr, + StringRef StorePath, + StringRef ProviderIdentifier, StringRef ProviderVersion, + StringRef OutputFile, + StringRef ModuleName, + const FileEntry *MainFile, + bool IsSystem, + bool IsModuleUnit, + bool IsDebugCompilation, + StringRef TargetTriple, + StringRef SysrootPath, + writer::ModuleInfoWriterCallback GetInfoForModule); + ~IndexUnitWriter(); + + int addFileDependency(const FileEntry *File, bool IsSystem, + writer::OpaqueModule Mod); + void addRecordFile(StringRef RecordFile, const FileEntry *File, bool IsSystem, + writer::OpaqueModule Mod); + void addASTFileDependency(const FileEntry *File, bool IsSystem, + writer::OpaqueModule Mod, bool withoutUnitName = false); + void addUnitDependency(StringRef UnitFile, const FileEntry *File, bool IsSystem, + writer::OpaqueModule Mod); + bool addInclude(const FileEntry *Source, unsigned Line, const FileEntry *Target); + + bool write(std::string &Error); + + void getUnitNameForOutputFile(StringRef FilePath, SmallVectorImpl &Str); + void getUnitPathForOutputFile(StringRef FilePath, SmallVectorImpl &Str); + /// If the unit file exists and \p timeCompareFilePath is provided, it will + /// return true if \p timeCompareFilePath is older than the unit file. + Optional isUnitUpToDateForOutputFile(StringRef FilePath, + Optional TimeCompareFilePath, + std::string &Error); + static void getUnitNameForAbsoluteOutputFile(StringRef FilePath, SmallVectorImpl &Str); + static bool initIndexDirectory(StringRef StorePath, std::string &Error); + +private: + class PathStorage; + int addModule(writer::OpaqueModule Mod); + void writeUnitInfo(llvm::BitstreamWriter &Stream, PathStorage &PathStore); + void writeDependencies(llvm::BitstreamWriter &Stream, PathStorage &PathStore); + void writeIncludes(llvm::BitstreamWriter &Stream, PathStorage &PathStore); + void writePaths(llvm::BitstreamWriter &Stream, PathStorage &PathStore); + void writeModules(llvm::BitstreamWriter &Stream); +}; + +} // end namespace index +} // end namespace clang + +#endif diff --git a/clang/include/clang/Index/IndexingAction.h b/clang/include/clang/Index/IndexingAction.h index 8eed33c612275..7dd25b8ec0b3a 100644 --- a/clang/include/clang/Index/IndexingAction.h +++ b/clang/include/clang/Index/IndexingAction.h @@ -12,11 +12,15 @@ #include "clang/Basic/LLVM.h" #include +#include namespace clang { class ASTReader; class ASTUnit; + class CompilerInstance; class FrontendAction; + class FrontendOptions; + class Module; namespace serialization { class ModuleFile; @@ -24,6 +28,7 @@ namespace serialization { namespace index { class IndexDataConsumer; + class IndexUnitWriter; struct IndexingOptions { enum class SystemSymbolFilterKind { @@ -37,6 +42,19 @@ struct IndexingOptions { bool IndexFunctionLocals = false; }; +struct RecordingOptions { + enum class IncludesRecordingKind { + None, + UserOnly, // only record includes inside non-system files. + All, + }; + + std::string DataDirPath; + bool RecordSymbolCodeGenName = false; + bool RecordSystemDependencies = true; + IncludesRecordingKind RecordIncludes = IncludesRecordingKind::UserOnly; +}; + /// \param WrappedAction another frontend action to wrap over or null. std::unique_ptr createIndexingAction(std::shared_ptr DataConsumer, @@ -52,6 +70,18 @@ void indexModuleFile(serialization::ModuleFile &Mod, std::shared_ptr DataConsumer, IndexingOptions Opts); +/// \param WrappedAction another frontend action to wrap over or null. +std::unique_ptr +createIndexDataRecordingAction(const FrontendOptions &FEOpts, + std::unique_ptr WrappedAction); + +/// Checks if the unit file exists for the module file, if it doesn't it +/// generates index data for it. +/// +/// \returns true if the index data were generated, false otherwise. +bool emitIndexDataForModuleFile(const Module *Mod, const CompilerInstance &CI, + IndexUnitWriter &ParentUnitWriter); + } // namespace index } // namespace clang diff --git a/clang/include/indexstore/IndexStoreCXX.h b/clang/include/indexstore/IndexStoreCXX.h new file mode 100644 index 0000000000000..addaa86f130c6 --- /dev/null +++ b/clang/include/indexstore/IndexStoreCXX.h @@ -0,0 +1,502 @@ +//===--- IndexStoreCXX.h - C++ wrapper for the Index Store C API. ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Header-only C++ wrapper for the Index Store C API. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INDEXSTORE_INDEXSTORECXX_H +#define LLVM_CLANG_INDEXSTORE_INDEXSTORECXX_H + +#include "indexstore/indexstore.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include + +namespace indexstore { + using llvm::ArrayRef; + using llvm::Optional; + using llvm::StringRef; + +static inline StringRef stringFromIndexStoreStringRef(indexstore_string_ref_t str) { + return StringRef(str.data, str.length); +} + +class IndexRecordSymbol { + indexstore_symbol_t obj; + friend class IndexRecordReader; + +public: + IndexRecordSymbol(indexstore_symbol_t obj) : obj(obj) {} + + indexstore_symbol_language_t getLanguage() { + return indexstore_symbol_get_language(obj); + } + indexstore_symbol_kind_t getKind() { return indexstore_symbol_get_kind(obj); } + indexstore_symbol_subkind_t getSubKind() { return indexstore_symbol_get_subkind(obj); } + uint64_t getProperties() { + return indexstore_symbol_get_properties(obj); + } + uint64_t getRoles() { return indexstore_symbol_get_roles(obj); } + uint64_t getRelatedRoles() { return indexstore_symbol_get_related_roles(obj); } + StringRef getName() { return stringFromIndexStoreStringRef(indexstore_symbol_get_name(obj)); } + StringRef getUSR() { return stringFromIndexStoreStringRef(indexstore_symbol_get_usr(obj)); } + StringRef getCodegenName() { return stringFromIndexStoreStringRef(indexstore_symbol_get_codegen_name(obj)); } +}; + +class IndexSymbolRelation { + indexstore_symbol_relation_t obj; + +public: + IndexSymbolRelation(indexstore_symbol_relation_t obj) : obj(obj) {} + + uint64_t getRoles() { return indexstore_symbol_relation_get_roles(obj); } + IndexRecordSymbol getSymbol() { return indexstore_symbol_relation_get_symbol(obj); } +}; + +class IndexRecordOccurrence { + indexstore_occurrence_t obj; + +public: + IndexRecordOccurrence(indexstore_occurrence_t obj) : obj(obj) {} + + IndexRecordSymbol getSymbol() { return indexstore_occurrence_get_symbol(obj); } + uint64_t getRoles() { return indexstore_occurrence_get_roles(obj); } + + bool foreachRelation(llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + return indexstore_occurrence_relations_apply(obj, ^bool(indexstore_symbol_relation_t sym_rel) { + return receiver(sym_rel); + }); +#else + return false; +#endif + } + + std::pair getLineCol() { + unsigned line, col; + indexstore_occurrence_get_line_col(obj, &line, &col); + return std::make_pair(line, col); + } +}; + +class IndexStore; +typedef std::shared_ptr IndexStoreRef; + +class IndexStore { + indexstore_t obj; + friend class IndexRecordReader; + friend class IndexUnitReader; + +public: + IndexStore(StringRef path, std::string &error) { + llvm::SmallString<64> buf = path; + indexstore_error_t c_err = nullptr; + obj = indexstore_store_create(buf.c_str(), &c_err); + if (c_err) { + error = indexstore_error_get_description(c_err); + indexstore_error_dispose(c_err); + } + } + + IndexStore(IndexStore &&other) : obj(other.obj) { + other.obj = nullptr; + } + + ~IndexStore() { + indexstore_store_dispose(obj); + } + + static IndexStoreRef create(StringRef path, std::string &error) { + auto storeRef = std::make_shared(path, error); + if (storeRef->isInvalid()) + return nullptr; + return storeRef; + } + + static unsigned formatVersion() { + return indexstore_format_version(); + } + + bool isValid() const { return obj; } + bool isInvalid() const { return !isValid(); } + explicit operator bool() const { return isValid(); } + + bool foreachUnit(bool sorted, llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + return indexstore_store_units_apply(obj, sorted, ^bool(indexstore_string_ref_t unit_name) { + return receiver(stringFromIndexStoreStringRef(unit_name)); + }); +#else + return false; +#endif + } + + class UnitEvent { + indexstore_unit_event_t obj; + public: + UnitEvent(indexstore_unit_event_t obj) : obj(obj) {} + + enum class Kind { + Added, + Removed, + Modified, + DirectoryDeleted, + }; + Kind getKind() const { + indexstore_unit_event_kind_t c_k = indexstore_unit_event_get_kind(obj); + Kind K; + switch (c_k) { + case INDEXSTORE_UNIT_EVENT_ADDED: K = Kind::Added; break; + case INDEXSTORE_UNIT_EVENT_REMOVED: K = Kind::Removed; break; + case INDEXSTORE_UNIT_EVENT_MODIFIED: K = Kind::Modified; break; + case INDEXSTORE_UNIT_EVENT_DIRECTORY_DELETED: K = Kind::DirectoryDeleted; break; + } + return K; + } + + StringRef getUnitName() const { + return stringFromIndexStoreStringRef(indexstore_unit_event_get_unit_name(obj)); + } + + timespec getModificationTime() const { return indexstore_unit_event_get_modification_time(obj); } + }; + + class UnitEventNotification { + indexstore_unit_event_notification_t obj; + public: + UnitEventNotification(indexstore_unit_event_notification_t obj) : obj(obj) {} + + bool isInitial() const { return indexstore_unit_event_notification_is_initial(obj); } + size_t getEventsCount() const { return indexstore_unit_event_notification_get_events_count(obj); } + UnitEvent getEvent(size_t index) const { return indexstore_unit_event_notification_get_event(obj, index); } + }; + + typedef std::function UnitEventHandler; + + void setUnitEventHandler(UnitEventHandler handler) { +#if INDEXSTORE_HAS_BLOCKS + if (!handler) { + indexstore_store_set_unit_event_handler(obj, nullptr); + return; + } + + indexstore_store_set_unit_event_handler(obj, ^(indexstore_unit_event_notification_t evt_note) { + handler(UnitEventNotification(evt_note)); + }); +#endif + } + + bool startEventListening(bool waitInitialSync, std::string &error) { + indexstore_unit_event_listen_options_t opts; + opts.wait_initial_sync = waitInitialSync; + indexstore_error_t c_err = nullptr; + bool ret = indexstore_store_start_unit_event_listening(obj, &opts, sizeof(opts), &c_err); + if (c_err) { + error = indexstore_error_get_description(c_err); + indexstore_error_dispose(c_err); + } + return ret; + } + + void stopEventListening() { + return indexstore_store_stop_unit_event_listening(obj); + } + + void discardUnit(StringRef UnitName) { + llvm::SmallString<64> buf = UnitName; + indexstore_store_discard_unit(obj, buf.c_str()); + } + + void discardRecord(StringRef RecordName) { + llvm::SmallString<64> buf = RecordName; + indexstore_store_discard_record(obj, buf.c_str()); + } + + void getUnitNameFromOutputPath(StringRef outputPath, llvm::SmallVectorImpl &nameBuf) { + llvm::SmallString<256> buf = outputPath; + size_t nameLen = indexstore_store_get_unit_name_from_output_path(obj, buf.c_str(), nameBuf.data(), nameBuf.size()); + if (nameLen+1 > nameBuf.size()) { + nameBuf.resize(nameLen+1); + indexstore_store_get_unit_name_from_output_path(obj, buf.c_str(), nameBuf.data(), nameBuf.size()); + } + } + + llvm::Optional + getUnitModificationTime(StringRef unitName, std::string &error) { + llvm::SmallString<64> buf = unitName; + int64_t seconds, nanoseconds; + indexstore_error_t c_err = nullptr; + bool err = indexstore_store_get_unit_modification_time(obj, buf.c_str(), + &seconds, &nanoseconds, &c_err); + if (err && c_err) { + error = indexstore_error_get_description(c_err); + indexstore_error_dispose(c_err); + return llvm::None; + } + timespec ts; + ts.tv_sec = seconds; + ts.tv_nsec = nanoseconds; + return ts; + } + + void purgeStaleData() { + indexstore_store_purge_stale_data(obj); + } +}; + +class IndexRecordReader { + indexstore_record_reader_t obj; + +public: + IndexRecordReader(IndexStore &store, StringRef recordName, std::string &error) { + llvm::SmallString<64> buf = recordName; + indexstore_error_t c_err = nullptr; + obj = indexstore_record_reader_create(store.obj, buf.c_str(), &c_err); + if (c_err) { + error = indexstore_error_get_description(c_err); + indexstore_error_dispose(c_err); + } + } + + IndexRecordReader(IndexRecordReader &&other) : obj(other.obj) { + other.obj = nullptr; + } + + ~IndexRecordReader() { + indexstore_record_reader_dispose(obj); + } + + bool isValid() const { return obj; } + bool isInvalid() const { return !isValid(); } + explicit operator bool() const { return isValid(); } + + /// Goes through and passes record decls, after filtering using a \c Checker + /// function. + /// + /// Resulting decls can be used as filter for \c foreachOccurrence. This + /// allows allocating memory only for the record decls that the caller is + /// interested in. + bool searchSymbols(llvm::function_ref filter, + llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + return indexstore_record_reader_search_symbols(obj, ^bool(indexstore_symbol_t symbol, bool *stop) { + return filter(symbol, *stop); + }, ^(indexstore_symbol_t symbol) { + receiver(symbol); + }); +#else + return false; +#endif + } + + bool foreachSymbol(bool noCache, llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + return indexstore_record_reader_symbols_apply(obj, noCache, ^bool(indexstore_symbol_t sym) { + return receiver(sym); + }); +#else + return false; +#endif + } + + /// \param DeclsFilter if non-empty indicates the list of decls that we want + /// to get occurrences for. An empty array indicates that we want occurrences + /// for all decls. + /// \param RelatedDeclsFilter Same as \c DeclsFilter but for related decls. + bool foreachOccurrence(ArrayRef symbolsFilter, + ArrayRef relatedSymbolsFilter, + llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + llvm::SmallVector c_symbolsFilter; + c_symbolsFilter.reserve(symbolsFilter.size()); + for (IndexRecordSymbol sym : symbolsFilter) { + c_symbolsFilter.push_back(sym.obj); + } + llvm::SmallVector c_relatedSymbolsFilter; + c_relatedSymbolsFilter.reserve(relatedSymbolsFilter.size()); + for (IndexRecordSymbol sym : relatedSymbolsFilter) { + c_relatedSymbolsFilter.push_back(sym.obj); + } + return indexstore_record_reader_occurrences_of_symbols_apply(obj, + c_symbolsFilter.data(), c_symbolsFilter.size(), + c_relatedSymbolsFilter.data(), + c_relatedSymbolsFilter.size(), + ^bool(indexstore_occurrence_t occur) { + return receiver(occur); + }); +#else + return false; +#endif + } + + bool foreachOccurrence( + llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + return indexstore_record_reader_occurrences_apply(obj, ^bool(indexstore_occurrence_t occur) { + return receiver(occur); + }); +#else + return false; +#endif + } + + bool foreachOccurrenceInLineRange(unsigned lineStart, unsigned lineEnd, + llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + return indexstore_record_reader_occurrences_in_line_range_apply(obj, + lineStart, + lineEnd, + ^bool(indexstore_occurrence_t occur) { + return receiver(occur); + }); +#else + return false; +#endif + } +}; + +class IndexUnitDependency { + indexstore_unit_dependency_t obj; + friend class IndexUnitReader; + +public: + IndexUnitDependency(indexstore_unit_dependency_t obj) : obj(obj) {} + + enum class DependencyKind { + Unit, + Record, + File, + }; + DependencyKind getKind() { + switch (indexstore_unit_dependency_get_kind(obj)) { + case INDEXSTORE_UNIT_DEPENDENCY_UNIT: return DependencyKind::Unit; + case INDEXSTORE_UNIT_DEPENDENCY_RECORD: return DependencyKind::Record; + case INDEXSTORE_UNIT_DEPENDENCY_FILE: return DependencyKind::File; + } + } + bool isSystem() { return indexstore_unit_dependency_is_system(obj); } + StringRef getName() { return stringFromIndexStoreStringRef(indexstore_unit_dependency_get_name(obj)); } + StringRef getFilePath() { return stringFromIndexStoreStringRef(indexstore_unit_dependency_get_filepath(obj)); } + StringRef getModuleName() { return stringFromIndexStoreStringRef(indexstore_unit_dependency_get_modulename(obj)); } + time_t getModificationTime() { return indexstore_unit_dependency_get_modification_time(obj); } + size_t getFileSize() { return indexstore_unit_dependency_get_file_size(obj); } + +}; + +class IndexUnitInclude { + indexstore_unit_include_t obj; + friend class IndexUnitReader; + +public: + IndexUnitInclude(indexstore_unit_include_t obj) : obj(obj) {} + + StringRef getSourcePath() { + return stringFromIndexStoreStringRef(indexstore_unit_include_get_source_path(obj)); + } + StringRef getTargetPath() { + return stringFromIndexStoreStringRef(indexstore_unit_include_get_target_path(obj)); + } + unsigned getSourceLine() { + return indexstore_unit_include_get_source_line(obj); + } +}; + +class IndexUnitReader { + indexstore_unit_reader_t obj; + +public: + IndexUnitReader(IndexStore &store, StringRef unitName, std::string &error) { + llvm::SmallString<64> buf = unitName; + indexstore_error_t c_err = nullptr; + obj = indexstore_unit_reader_create(store.obj, buf.c_str(), &c_err); + if (c_err) { + error = indexstore_error_get_description(c_err); + indexstore_error_dispose(c_err); + } + } + + IndexUnitReader(IndexUnitReader &&other) : obj(other.obj) { + other.obj = nullptr; + } + + ~IndexUnitReader() { + indexstore_unit_reader_dispose(obj); + } + + bool isValid() const { return obj; } + bool isInvalid() const { return !isValid(); } + explicit operator bool() const { return isValid(); } + + StringRef getProviderIdentifier() { + return stringFromIndexStoreStringRef(indexstore_unit_reader_get_provider_identifier(obj)); + } + StringRef getProviderVersion() { + return stringFromIndexStoreStringRef(indexstore_unit_reader_get_provider_version(obj)); + } + + timespec getModificationTime() { + int64_t seconds, nanoseconds; + indexstore_unit_reader_get_modification_time(obj, &seconds, &nanoseconds); + timespec ts; + ts.tv_sec = seconds; + ts.tv_nsec = nanoseconds; + return ts; + } + + bool isSystemUnit() { return indexstore_unit_reader_is_system_unit(obj); } + bool isModuleUnit() { return indexstore_unit_reader_is_module_unit(obj); } + bool isDebugCompilation() { return indexstore_unit_reader_is_debug_compilation(obj); } + bool hasMainFile() { return indexstore_unit_reader_has_main_file(obj); } + + StringRef getMainFilePath() { + return stringFromIndexStoreStringRef(indexstore_unit_reader_get_main_file(obj)); + } + StringRef getModuleName() { + return stringFromIndexStoreStringRef(indexstore_unit_reader_get_module_name(obj)); + } + StringRef getWorkingDirectory() { + return stringFromIndexStoreStringRef(indexstore_unit_reader_get_working_dir(obj)); + } + StringRef getOutputFile() { + return stringFromIndexStoreStringRef(indexstore_unit_reader_get_output_file(obj)); + } + StringRef getSysrootPath() { + return stringFromIndexStoreStringRef(indexstore_unit_reader_get_sysroot_path(obj)); + } + StringRef getTarget() { + return stringFromIndexStoreStringRef(indexstore_unit_reader_get_target(obj)); + } + + bool foreachDependency(llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + return indexstore_unit_reader_dependencies_apply(obj, ^bool(indexstore_unit_dependency_t dep) { + return receiver(dep); + }); +#else + return false; +#endif + } + + bool foreachInclude(llvm::function_ref receiver) { +#if INDEXSTORE_HAS_BLOCKS + return indexstore_unit_reader_includes_apply(obj, ^bool(indexstore_unit_include_t inc) { + return receiver(inc); + }); +#else + return false; +#endif + } +}; + +} // namespace indexstore + +#endif diff --git a/clang/include/indexstore/indexstore.h b/clang/include/indexstore/indexstore.h new file mode 100644 index 0000000000000..024cdf6c7967d --- /dev/null +++ b/clang/include/indexstore/indexstore.h @@ -0,0 +1,487 @@ +/*===-- indexstore/indexstore.h - Index Store C API ----------------- C -*-===*\ +|* *| +|* The LLVM Compiler Infrastructure *| +|* *| +|* This file is distributed under the University of Illinois Open Source *| +|* License. See LICENSE.TXT for details. *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This header provides a C API for the index store. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_CLANG_C_INDEXSTORE_INDEXSTORE_H +#define LLVM_CLANG_C_INDEXSTORE_INDEXSTORE_H + +#include +#include +#include + +/** + * \brief The version constants for the Index Store C API. + * INDEXSTORE_VERSION_MINOR should increase when there are API additions. + * INDEXSTORE_VERSION_MAJOR is intended for "major" source/ABI breaking changes. + */ +#define INDEXSTORE_VERSION_MAJOR 0 +#define INDEXSTORE_VERSION_MINOR 9 + +#define INDEXSTORE_VERSION_ENCODE(major, minor) ( \ + ((major) * 10000) \ + + ((minor) * 1)) + +#define INDEXSTORE_VERSION INDEXSTORE_VERSION_ENCODE( \ + INDEXSTORE_VERSION_MAJOR, \ + INDEXSTORE_VERSION_MINOR ) + +#define INDEXSTORE_VERSION_STRINGIZE_(major, minor) \ + #major"."#minor +#define INDEXSTORE_VERSION_STRINGIZE(major, minor) \ + INDEXSTORE_VERSION_STRINGIZE_(major, minor) + +#define INDEXSTORE_VERSION_STRING INDEXSTORE_VERSION_STRINGIZE( \ + INDEXSTORE_VERSION_MAJOR, \ + INDEXSTORE_VERSION_MINOR) + +#ifdef __cplusplus +# define INDEXSTORE_BEGIN_DECLS extern "C" { +# define INDEXSTORE_END_DECLS } +#else +# define INDEXSTORE_BEGIN_DECLS +# define INDEXSTORE_END_DECLS +#endif + +#ifndef INDEXSTORE_PUBLIC +# if defined (_MSC_VER) +# define INDEXSTORE_PUBLIC __declspec(dllimport) +# else +# define INDEXSTORE_PUBLIC +# endif +#endif + +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#if __has_feature(blocks) +# define INDEXSTORE_HAS_BLOCKS 1 +#else +# define INDEXSTORE_HAS_BLOCKS 0 +#endif + +INDEXSTORE_BEGIN_DECLS + +typedef void *indexstore_error_t; + +INDEXSTORE_PUBLIC const char * +indexstore_error_get_description(indexstore_error_t); + +INDEXSTORE_PUBLIC void +indexstore_error_dispose(indexstore_error_t); + +typedef struct { + const char *data; + size_t length; +} indexstore_string_ref_t; + +INDEXSTORE_PUBLIC unsigned +indexstore_format_version(void); + +typedef void *indexstore_t; + +INDEXSTORE_PUBLIC indexstore_t +indexstore_store_create(const char *store_path, indexstore_error_t *error); + +INDEXSTORE_PUBLIC void +indexstore_store_dispose(indexstore_t); + +#if INDEXSTORE_HAS_BLOCKS +INDEXSTORE_PUBLIC bool +indexstore_store_units_apply(indexstore_t, unsigned sorted, + bool(^applier)(indexstore_string_ref_t unit_name)); +#endif + +typedef void *indexstore_unit_event_notification_t; +typedef void *indexstore_unit_event_t; + +INDEXSTORE_PUBLIC size_t +indexstore_unit_event_notification_get_events_count(indexstore_unit_event_notification_t); + +INDEXSTORE_PUBLIC indexstore_unit_event_t +indexstore_unit_event_notification_get_event(indexstore_unit_event_notification_t, size_t index); + +INDEXSTORE_PUBLIC bool +indexstore_unit_event_notification_is_initial(indexstore_unit_event_notification_t); + +typedef enum { + INDEXSTORE_UNIT_EVENT_ADDED = 1, + INDEXSTORE_UNIT_EVENT_REMOVED = 2, + INDEXSTORE_UNIT_EVENT_MODIFIED = 3, + INDEXSTORE_UNIT_EVENT_DIRECTORY_DELETED = 4, +} indexstore_unit_event_kind_t; + +INDEXSTORE_PUBLIC indexstore_unit_event_kind_t +indexstore_unit_event_get_kind(indexstore_unit_event_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_event_get_unit_name(indexstore_unit_event_t); + +INDEXSTORE_PUBLIC timespec +indexstore_unit_event_get_modification_time(indexstore_unit_event_t); + +#if INDEXSTORE_HAS_BLOCKS +typedef void (^indexstore_unit_event_handler_t)(indexstore_unit_event_notification_t); + +INDEXSTORE_PUBLIC void +indexstore_store_set_unit_event_handler(indexstore_t, + indexstore_unit_event_handler_t handler); +#endif + +typedef struct { + /// If true, \c indexstore_store_start_unit_event_listening will block until + /// the initial set of units is passed to the unit event handler, otherwise + /// the function will return and the initial set will be passed asynchronously. + bool wait_initial_sync; +} indexstore_unit_event_listen_options_t; + +INDEXSTORE_PUBLIC bool +indexstore_store_start_unit_event_listening(indexstore_t, + indexstore_unit_event_listen_options_t *, + size_t listen_options_struct_size, + indexstore_error_t *error); + +INDEXSTORE_PUBLIC void +indexstore_store_stop_unit_event_listening(indexstore_t); + +INDEXSTORE_PUBLIC void +indexstore_store_discard_unit(indexstore_t, const char *unit_name); + +INDEXSTORE_PUBLIC void +indexstore_store_discard_record(indexstore_t, const char *record_name); + +INDEXSTORE_PUBLIC void +indexstore_store_purge_stale_data(indexstore_t); + +/// Determines the unit name from the \c output_path and writes it out in the +/// \c name_buf buffer. It doesn't write more than \c buf_size. +/// \returns the length of the name. If this is larger than \c buf_size, the +/// caller should call the function again with a buffer of the appropriate size. +INDEXSTORE_PUBLIC size_t +indexstore_store_get_unit_name_from_output_path(indexstore_t store, + const char *output_path, + char *name_buf, + size_t buf_size); + +/// \returns true if an error occurred, false otherwise. +INDEXSTORE_PUBLIC bool +indexstore_store_get_unit_modification_time(indexstore_t store, + const char *unit_name, + int64_t *seconds, + int64_t *nanoseconds, + indexstore_error_t *error); + +typedef void *indexstore_symbol_t; + +typedef enum { + INDEXSTORE_SYMBOL_KIND_UNKNOWN = 0, + INDEXSTORE_SYMBOL_KIND_MODULE = 1, + INDEXSTORE_SYMBOL_KIND_NAMESPACE = 2, + INDEXSTORE_SYMBOL_KIND_NAMESPACEALIAS = 3, + INDEXSTORE_SYMBOL_KIND_MACRO = 4, + INDEXSTORE_SYMBOL_KIND_ENUM = 5, + INDEXSTORE_SYMBOL_KIND_STRUCT = 6, + INDEXSTORE_SYMBOL_KIND_CLASS = 7, + INDEXSTORE_SYMBOL_KIND_PROTOCOL = 8, + INDEXSTORE_SYMBOL_KIND_EXTENSION = 9, + INDEXSTORE_SYMBOL_KIND_UNION = 10, + INDEXSTORE_SYMBOL_KIND_TYPEALIAS = 11, + INDEXSTORE_SYMBOL_KIND_FUNCTION = 12, + INDEXSTORE_SYMBOL_KIND_VARIABLE = 13, + INDEXSTORE_SYMBOL_KIND_FIELD = 14, + INDEXSTORE_SYMBOL_KIND_ENUMCONSTANT = 15, + INDEXSTORE_SYMBOL_KIND_INSTANCEMETHOD = 16, + INDEXSTORE_SYMBOL_KIND_CLASSMETHOD = 17, + INDEXSTORE_SYMBOL_KIND_STATICMETHOD = 18, + INDEXSTORE_SYMBOL_KIND_INSTANCEPROPERTY = 19, + INDEXSTORE_SYMBOL_KIND_CLASSPROPERTY = 20, + INDEXSTORE_SYMBOL_KIND_STATICPROPERTY = 21, + INDEXSTORE_SYMBOL_KIND_CONSTRUCTOR = 22, + INDEXSTORE_SYMBOL_KIND_DESTRUCTOR = 23, + INDEXSTORE_SYMBOL_KIND_CONVERSIONFUNCTION = 24, + INDEXSTORE_SYMBOL_KIND_PARAMETER = 25, + + INDEXSTORE_SYMBOL_KIND_COMMENTTAG = 1000, +} indexstore_symbol_kind_t; + +typedef enum { + INDEXSTORE_SYMBOL_SUBKIND_NONE = 0, + INDEXSTORE_SYMBOL_SUBKIND_CXXCOPYCONSTRUCTOR = 1, + INDEXSTORE_SYMBOL_SUBKIND_CXXMOVECONSTRUCTOR = 2, + INDEXSTORE_SYMBOL_SUBKIND_ACCESSORGETTER = 3, + INDEXSTORE_SYMBOL_SUBKIND_ACCESSORSETTER = 4, + + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORWILLSET = 1000, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORDIDSET = 1001, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORADDRESSOR = 1002, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORMUTABLEADDRESSOR = 1003, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFSTRUCT = 1004, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFCLASS = 1005, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFENUM = 1006, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFPROTOCOL = 1007, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTPREFIXOPERATOR = 1008, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTPOSTFIXOPERATOR = 1009, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTINFIXOPERATOR = 1010, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTSUBSCRIPT = 1011, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTASSOCIATEDTYPE = 1012, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTGENERICTYPEPARAM = 1013, +} indexstore_symbol_subkind_t; + +typedef enum { + INDEXSTORE_SYMBOL_PROPERTY_GENERIC = 1 << 0, + INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_PARTIAL_SPECIALIZATION = 1 << 1, + INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_SPECIALIZATION = 1 << 2, + INDEXSTORE_SYMBOL_PROPERTY_UNITTEST = 1 << 3, + INDEXSTORE_SYMBOL_PROPERTY_IBANNOTATED = 1 << 4, + INDEXSTORE_SYMBOL_PROPERTY_IBOUTLETCOLLECTION = 1 << 5, + INDEXSTORE_SYMBOL_PROPERTY_GKINSPECTABLE = 1 << 6, + INDEXSTORE_SYMBOL_PROPERTY_LOCAL = 1 << 7, +} indexstore_symbol_property_t; + +typedef enum { + INDEXSTORE_SYMBOL_LANG_C = 0, + INDEXSTORE_SYMBOL_LANG_OBJC = 1, + INDEXSTORE_SYMBOL_LANG_CXX = 2, + + INDEXSTORE_SYMBOL_LANG_SWIFT = 100, +} indexstore_symbol_language_t; + +typedef enum { + INDEXSTORE_SYMBOL_ROLE_DECLARATION = 1 << 0, + INDEXSTORE_SYMBOL_ROLE_DEFINITION = 1 << 1, + INDEXSTORE_SYMBOL_ROLE_REFERENCE = 1 << 2, + INDEXSTORE_SYMBOL_ROLE_READ = 1 << 3, + INDEXSTORE_SYMBOL_ROLE_WRITE = 1 << 4, + INDEXSTORE_SYMBOL_ROLE_CALL = 1 << 5, + INDEXSTORE_SYMBOL_ROLE_DYNAMIC = 1 << 6, + INDEXSTORE_SYMBOL_ROLE_ADDRESSOF = 1 << 7, + INDEXSTORE_SYMBOL_ROLE_IMPLICIT = 1 << 8, + + // Relation roles. + INDEXSTORE_SYMBOL_ROLE_REL_CHILDOF = 1 << 9, + INDEXSTORE_SYMBOL_ROLE_REL_BASEOF = 1 << 10, + INDEXSTORE_SYMBOL_ROLE_REL_OVERRIDEOF = 1 << 11, + INDEXSTORE_SYMBOL_ROLE_REL_RECEIVEDBY = 1 << 12, + INDEXSTORE_SYMBOL_ROLE_REL_CALLEDBY = 1 << 13, + INDEXSTORE_SYMBOL_ROLE_REL_EXTENDEDBY = 1 << 14, + INDEXSTORE_SYMBOL_ROLE_REL_ACCESSOROF = 1 << 15, + INDEXSTORE_SYMBOL_ROLE_REL_CONTAINEDBY = 1 << 16, + INDEXSTORE_SYMBOL_ROLE_REL_IBTYPEOF = 1 << 17, + INDEXSTORE_SYMBOL_ROLE_REL_SPECIALIZATIONOF = 1 << 18, +} indexstore_symbol_role_t; + +INDEXSTORE_PUBLIC indexstore_symbol_language_t +indexstore_symbol_get_language(indexstore_symbol_t); + +INDEXSTORE_PUBLIC indexstore_symbol_kind_t +indexstore_symbol_get_kind(indexstore_symbol_t); + +INDEXSTORE_PUBLIC indexstore_symbol_subkind_t +indexstore_symbol_get_subkind(indexstore_symbol_t); + +INDEXSTORE_PUBLIC uint64_t +indexstore_symbol_get_properties(indexstore_symbol_t); + +INDEXSTORE_PUBLIC uint64_t +indexstore_symbol_get_roles(indexstore_symbol_t); + +INDEXSTORE_PUBLIC uint64_t +indexstore_symbol_get_related_roles(indexstore_symbol_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_symbol_get_name(indexstore_symbol_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_symbol_get_usr(indexstore_symbol_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_symbol_get_codegen_name(indexstore_symbol_t); + +typedef void *indexstore_symbol_relation_t; + +INDEXSTORE_PUBLIC uint64_t +indexstore_symbol_relation_get_roles(indexstore_symbol_relation_t); + +INDEXSTORE_PUBLIC indexstore_symbol_t +indexstore_symbol_relation_get_symbol(indexstore_symbol_relation_t); + +typedef void *indexstore_occurrence_t; + +INDEXSTORE_PUBLIC indexstore_symbol_t +indexstore_occurrence_get_symbol(indexstore_occurrence_t); + +#if INDEXSTORE_HAS_BLOCKS +INDEXSTORE_PUBLIC bool +indexstore_occurrence_relations_apply(indexstore_occurrence_t, + bool(^applier)(indexstore_symbol_relation_t symbol_rel)); +#endif + +INDEXSTORE_PUBLIC uint64_t +indexstore_occurrence_get_roles(indexstore_occurrence_t); + +INDEXSTORE_PUBLIC void +indexstore_occurrence_get_line_col(indexstore_occurrence_t, + unsigned *line, unsigned *column); + +typedef void *indexstore_record_reader_t; + +INDEXSTORE_PUBLIC indexstore_record_reader_t +indexstore_record_reader_create(indexstore_t store, const char *record_name, + indexstore_error_t *error); + +INDEXSTORE_PUBLIC void +indexstore_record_reader_dispose(indexstore_record_reader_t); + +#if INDEXSTORE_HAS_BLOCKS +/// Goes through the symbol data and passes symbols to \c receiver, for the +/// symbol data that \c filter returns true on. +/// +/// This allows allocating memory only for the record symbols that the caller is +/// interested in. +INDEXSTORE_PUBLIC bool +indexstore_record_reader_search_symbols(indexstore_record_reader_t, + bool(^filter)(indexstore_symbol_t symbol, bool *stop), + void(^receiver)(indexstore_symbol_t symbol)); + +/// \param nocache if true, avoids allocating memory for the symbols. +/// Useful when the caller does not intend to keep \c indexstore_record_reader_t +/// for more queries. +INDEXSTORE_PUBLIC bool +indexstore_record_reader_symbols_apply(indexstore_record_reader_t, + bool nocache, + bool(^applier)(indexstore_symbol_t symbol)); + +INDEXSTORE_PUBLIC bool +indexstore_record_reader_occurrences_apply(indexstore_record_reader_t, + bool(^applier)(indexstore_occurrence_t occur)); + +INDEXSTORE_PUBLIC bool +indexstore_record_reader_occurrences_in_line_range_apply(indexstore_record_reader_t, + unsigned line_start, + unsigned line_count, + bool(^applier)(indexstore_occurrence_t occur)); + +/// \param symbols if non-zero \c symbols_count, indicates the list of symbols +/// that we want to get occurrences for. An empty array indicates that we want +/// occurrences for all symbols. +/// \param related_symbols Same as \c symbols but for related symbols. +INDEXSTORE_PUBLIC bool +indexstore_record_reader_occurrences_of_symbols_apply(indexstore_record_reader_t, + indexstore_symbol_t *symbols, size_t symbols_count, + indexstore_symbol_t *related_symbols, size_t related_symbols_count, + bool(^applier)(indexstore_occurrence_t occur)); +#endif + + +typedef void *indexstore_unit_reader_t; + +INDEXSTORE_PUBLIC indexstore_unit_reader_t +indexstore_unit_reader_create(indexstore_t store, const char *unit_name, + indexstore_error_t *error); + +INDEXSTORE_PUBLIC void +indexstore_unit_reader_dispose(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_reader_get_provider_identifier(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_reader_get_provider_version(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC void +indexstore_unit_reader_get_modification_time(indexstore_unit_reader_t, + int64_t *seconds, + int64_t *nanoseconds); + +INDEXSTORE_PUBLIC bool +indexstore_unit_reader_is_system_unit(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC bool +indexstore_unit_reader_is_module_unit(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC bool +indexstore_unit_reader_is_debug_compilation(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC bool +indexstore_unit_reader_has_main_file(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_reader_get_main_file(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_reader_get_module_name(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_reader_get_working_dir(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_reader_get_output_file(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_reader_get_sysroot_path(indexstore_unit_reader_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_reader_get_target(indexstore_unit_reader_t); + +typedef void *indexstore_unit_dependency_t; +typedef void *indexstore_unit_include_t; + +typedef enum { + INDEXSTORE_UNIT_DEPENDENCY_UNIT = 1, + INDEXSTORE_UNIT_DEPENDENCY_RECORD = 2, + INDEXSTORE_UNIT_DEPENDENCY_FILE = 3, +} indexstore_unit_dependency_kind_t; + +INDEXSTORE_PUBLIC indexstore_unit_dependency_kind_t +indexstore_unit_dependency_get_kind(indexstore_unit_dependency_t); + +INDEXSTORE_PUBLIC bool +indexstore_unit_dependency_is_system(indexstore_unit_dependency_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_dependency_get_filepath(indexstore_unit_dependency_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_dependency_get_modulename(indexstore_unit_dependency_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_dependency_get_name(indexstore_unit_dependency_t); + +INDEXSTORE_PUBLIC time_t +indexstore_unit_dependency_get_modification_time(indexstore_unit_dependency_t); + +INDEXSTORE_PUBLIC size_t +indexstore_unit_dependency_get_file_size(indexstore_unit_dependency_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_include_get_source_path(indexstore_unit_include_t); + +INDEXSTORE_PUBLIC indexstore_string_ref_t +indexstore_unit_include_get_target_path(indexstore_unit_include_t); + +INDEXSTORE_PUBLIC unsigned +indexstore_unit_include_get_source_line(indexstore_unit_include_t); + +#if INDEXSTORE_HAS_BLOCKS +INDEXSTORE_PUBLIC bool +indexstore_unit_reader_dependencies_apply(indexstore_unit_reader_t, + bool(^applier)(indexstore_unit_dependency_t)); + +INDEXSTORE_PUBLIC bool +indexstore_unit_reader_includes_apply(indexstore_unit_reader_t, + bool(^applier)(indexstore_unit_include_t)); + +#endif + +INDEXSTORE_END_DECLS + +#endif diff --git a/clang/lib/CMakeLists.txt b/clang/lib/CMakeLists.txt index 574c511747f40..5e291e52c52c9 100644 --- a/clang/lib/CMakeLists.txt +++ b/clang/lib/CMakeLists.txt @@ -19,6 +19,7 @@ add_subdirectory(Frontend) add_subdirectory(FrontendTool) add_subdirectory(Tooling) add_subdirectory(Index) +add_subdirectory(DirectoryWatcher) if(CLANG_ENABLE_STATIC_ANALYZER) add_subdirectory(StaticAnalyzer) endif() diff --git a/clang/lib/DirectoryWatcher/CMakeLists.txt b/clang/lib/DirectoryWatcher/CMakeLists.txt new file mode 100644 index 0000000000000..425a40ff4509e --- /dev/null +++ b/clang/lib/DirectoryWatcher/CMakeLists.txt @@ -0,0 +1,8 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangDirectoryWatcher + DirectoryWatcher.cpp + + LINK_LIBS + clangBasic + ) diff --git a/clang/lib/DirectoryWatcher/DirectoryWatcher.cpp b/clang/lib/DirectoryWatcher/DirectoryWatcher.cpp new file mode 100644 index 0000000000000..3a90526aac756 --- /dev/null +++ b/clang/lib/DirectoryWatcher/DirectoryWatcher.cpp @@ -0,0 +1,275 @@ +//===- DirectoryWatcher.cpp - Listens for directory file changes ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// \file +/// \brief Utility class for listening for file system changes in a directory. +//===----------------------------------------------------------------------===// + +#include "clang/DirectoryWatcher/DirectoryWatcher.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" + +#define HAVE_CORESERVICES 0 + +#if defined(__has_include) +#if __has_include() + +#include +#undef HAVE_CORESERVICES +#define HAVE_CORESERVICES 1 + +#endif +#endif + +using namespace clang; +using namespace llvm; + +static timespec toTimeSpec(sys::TimePoint<> tp) { + std::chrono::seconds sec = std::chrono::time_point_cast( + tp).time_since_epoch(); + std::chrono::nanoseconds nsec = + std::chrono::time_point_cast(tp - sec) + .time_since_epoch(); + timespec ts; + ts.tv_sec = sec.count(); + ts.tv_nsec = nsec.count(); + return ts; +} + +static Optional getModTime(StringRef path) { + sys::fs::file_status Status; + std::error_code EC = status(path, Status); + if (EC) + return None; + return toTimeSpec(Status.getLastModificationTime()); +} + +struct DirectoryWatcher::Implementation { +#if HAVE_CORESERVICES + FSEventStreamRef EventStream = nullptr; + + bool setupFSEventStream(StringRef path, EventReceiver receiver, + dispatch_queue_t queue); + void stopFSEventStream(); + + ~Implementation() { + stopFSEventStream(); + }; +#endif +}; + +#if HAVE_CORESERVICES +namespace { +struct EventStreamContextData { + std::string WatchedPath; + DirectoryWatcher::EventReceiver Receiver; + + EventStreamContextData(std::string watchedPath, DirectoryWatcher::EventReceiver receiver) + : WatchedPath(std::move(watchedPath)), Receiver(std::move(receiver)) { + } + + static void dispose(const void *ctx) { + delete static_cast(ctx); + } +}; +} + +static void eventStreamCallback( + ConstFSEventStreamRef stream, + void *clientCallBackInfo, + size_t numEvents, + void *eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) { + auto *ctx = static_cast(clientCallBackInfo); + + std::vector Events; + for (size_t i = 0; i < numEvents; ++i) { + StringRef path = ((const char **)eventPaths)[i]; + const FSEventStreamEventFlags flags = eventFlags[i]; + if (!(flags & kFSEventStreamEventFlagItemIsFile)) { + if ((flags & kFSEventStreamEventFlagItemRemoved) && path == ctx->WatchedPath) { + DirectoryWatcher::Event Evt{DirectoryWatcher::EventKind::DirectoryDeleted, path, timespec{}}; + Events.push_back(Evt); + break; + } + continue; + } + DirectoryWatcher::EventKind K = DirectoryWatcher::EventKind::Modified; + if ((flags & kFSEventStreamEventFlagItemCreated) || + (flags & kFSEventStreamEventFlagItemRenamed)) + K = DirectoryWatcher::EventKind::Added; + if (flags & kFSEventStreamEventFlagItemRemoved) + K = DirectoryWatcher::EventKind::Removed; + timespec modTime{}; + if (K != DirectoryWatcher::EventKind::Removed) { + auto modTimeOpt = getModTime(path); + if (!modTimeOpt.hasValue()) + continue; + modTime = modTimeOpt.getValue(); + } + DirectoryWatcher::Event Evt{K, path, modTime}; + Events.push_back(Evt); + } + + ctx->Receiver(Events, /*isInitial=*/false); +} + +bool DirectoryWatcher::Implementation::setupFSEventStream(StringRef path, + EventReceiver receiver, + dispatch_queue_t queue) { + if (path.empty()) + return true; + + CFMutableArrayRef pathsToWatch = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); + CFStringRef cfPathStr = CFStringCreateWithBytes(nullptr, (const UInt8 *)path.data(), path.size(), kCFStringEncodingUTF8, false); + CFArrayAppendValue(pathsToWatch, cfPathStr); + CFRelease(cfPathStr); + CFAbsoluteTime latency = 0.2; // Latency in seconds. + + std::string realPath; + { + SmallString<128> Storage; + StringRef P = llvm::Twine(path).toNullTerminatedStringRef(Storage); + char Buffer[PATH_MAX]; + // Use ::realpath to get the real path name + if (::realpath(P.begin(), Buffer) != nullptr) + realPath = Buffer; + else + realPath = path; + } + + EventStreamContextData *ctxData = new EventStreamContextData(std::move(realPath), std::move(receiver)); + FSEventStreamContext context; + context.version = 0; + context.info = ctxData; + context.retain = nullptr; + context.release = EventStreamContextData::dispose; + context.copyDescription = nullptr; + + EventStream = FSEventStreamCreate(nullptr, + eventStreamCallback, + &context, + pathsToWatch, + kFSEventStreamEventIdSinceNow, + latency, + kFSEventStreamCreateFlagFileEvents | + kFSEventStreamCreateFlagNoDefer); + CFRelease(pathsToWatch); + if (!EventStream) { + return true; + } + FSEventStreamSetDispatchQueue(EventStream, queue); + FSEventStreamStart(EventStream); + return false; +} + +void DirectoryWatcher::Implementation::stopFSEventStream() { + if (!EventStream) + return; + FSEventStreamStop(EventStream); + FSEventStreamInvalidate(EventStream); + FSEventStreamRelease(EventStream); + EventStream = nullptr; +} +#endif + +DirectoryWatcher::DirectoryWatcher() + : Impl(*new Implementation()) {} + +DirectoryWatcher::~DirectoryWatcher() { + delete &Impl; +} + +#if HAVE_CORESERVICES +static std::vector scanDirectory(StringRef Path) { + using namespace llvm::sys; + + std::vector Events; + std::error_code EC; + for (auto It = fs::directory_iterator(Path, EC), End = fs::directory_iterator(); + !EC && It != End; It.increment(EC)) { + auto modTime = getModTime(It->path()); + if (!modTime.hasValue()) + continue; + DirectoryWatcher::Event Event{DirectoryWatcher::EventKind::Added, It->path(), modTime.getValue()}; + Events.push_back(std::move(Event)); + } + return Events; +} +#endif + +std::unique_ptr DirectoryWatcher::create(StringRef Path, + EventReceiver Receiver, bool waitInitialSync, std::string &Error) { +#if HAVE_CORESERVICES + + using namespace llvm::sys; + + if (!fs::exists(Path)) { + std::error_code EC = fs::create_directories(Path); + if (EC) { + Error = EC.message(); + return nullptr; + } + } + + bool IsDir; + std::error_code EC = fs::is_directory(Path, IsDir); + if (EC) { + Error = EC.message(); + return nullptr; + } + if (!IsDir) { + Error = "path is not a directory: "; + Error += Path; + return nullptr; + } + + std::unique_ptr DirWatch; + DirWatch.reset(new DirectoryWatcher()); + auto &Impl = DirWatch->Impl; + + dispatch_queue_t queue = dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL); + dispatch_semaphore_t initScanSema = dispatch_semaphore_create(0); + dispatch_semaphore_t setupFSEventsSema = dispatch_semaphore_create(0); + + std::string copiedPath = Path; + dispatch_retain(initScanSema); + dispatch_retain(setupFSEventsSema); + dispatch_async(queue, ^{ + // Wait for the event stream to be setup before doing the initial scan, + // to make sure we won't miss any events. + dispatch_semaphore_wait(setupFSEventsSema, DISPATCH_TIME_FOREVER); + auto events = scanDirectory(copiedPath); + Receiver(events, /*isInitial=*/true); + dispatch_semaphore_signal(initScanSema); + dispatch_release(setupFSEventsSema); + dispatch_release(initScanSema); + }); + bool fsErr = Impl.setupFSEventStream(Path, Receiver, queue); + dispatch_semaphore_signal(setupFSEventsSema); + + if (waitInitialSync) { + dispatch_semaphore_wait(initScanSema, DISPATCH_TIME_FOREVER); + } + dispatch_release(setupFSEventsSema); + dispatch_release(initScanSema); + dispatch_release(queue); + + if (fsErr) { + raw_string_ostream(Error) << "failed to setup FSEvents stream for path: " << Path; + return nullptr; + } + + return DirWatch; +#else + return nullptr; +#endif +} diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index f23975829eaf3..98e806ec66305 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -966,7 +966,9 @@ void Driver::generateCompilationDiagnostics(Compilation &C, } // Assume associated files are based off of the first temporary file. - CrashReportInfo CrashInfo(TempFiles[0], VFS); + CrashReportInfo CrashInfo( + TempFiles[0], VFS, + C.getArgs().getLastArgValue(options::OPT_index_store_path)); std::string Script = CrashInfo.Filename.rsplit('.').first.str() + ".sh"; std::error_code EC; diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp index 789f107bbe610..355b9e0556a6c 100644 --- a/clang/lib/Driver/Job.cpp +++ b/clang/lib/Driver/Job.cpp @@ -67,6 +67,8 @@ static bool skipArgs(const char *Flag, bool HaveCrashVFS, int &SkipNum, .Default(false); if (IsInclude) return HaveCrashVFS ? false : true; + if (StringRef(Flag).startswith("-index-store-path")) + return true; // The remaining flags are treated as a single argument. @@ -221,6 +223,7 @@ void Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, } bool HaveCrashVFS = CrashInfo && !CrashInfo->VFSPath.empty(); + bool HaveIndexStorePath = CrashInfo && !CrashInfo->IndexStorePath.empty(); for (size_t i = 0, e = Args.size(); i < e; ++i) { const char *const Arg = Args[i]; @@ -284,6 +287,24 @@ void Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, printArg(OS, ModCachePath, Quote); } + if (CrashInfo && HaveIndexStorePath) { + SmallString<128> IndexStoreDir; + + if (HaveCrashVFS) { + IndexStoreDir = llvm::sys::path::parent_path( + llvm::sys::path::parent_path(CrashInfo->VFSPath)); + llvm::sys::path::append(IndexStoreDir, "index-store"); + } else { + IndexStoreDir = "index-store"; + } + + OS << ' '; + printArg(OS, "-index-store-path", Quote); + OS << ' '; + printArg(OS, IndexStoreDir.c_str(), Quote); + } + + if (ResponseFile != nullptr) { OS << "\n Arguments passed via response file:\n"; writeResponseFile(OS); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index f45b2df204798..8c50695ffd559 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -2957,6 +2957,26 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Args.AddLastArg(CmdArgs, options::OPT_objcmt_whitelist_dir_path); } + if (Args.hasArg(options::OPT_index_store_path)) { + Args.AddLastArg(CmdArgs, options::OPT_index_store_path); + Args.AddLastArg(CmdArgs, options::OPT_index_ignore_system_symbols); + Args.AddLastArg(CmdArgs, options::OPT_index_record_codegen_name); + + // If '-o' is passed along with '-fsyntax-only' pass it along the cc1 + // invocation so that the index action knows what the out file is. + if (isa(JA) && JA.getType() == types::TY_Nothing) { + Args.AddLastArg(CmdArgs, options::OPT_o); + } + } + + if (const char *IdxStorePath = ::getenv("CLANG_PROJECT_INDEX_PATH")) { + CmdArgs.push_back("-index-store-path"); + CmdArgs.push_back(IdxStorePath); + CmdArgs.push_back("-index-ignore-system-symbols"); + CmdArgs.push_back("-index-record-codegen-name"); + } + + // Add preprocessing options like -I, -D, etc. if we are using the // preprocessor. // diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp index e41b50c40b289..29704b4291cb8 100644 --- a/clang/lib/Driver/ToolChains/Darwin.cpp +++ b/clang/lib/Driver/ToolChains/Darwin.cpp @@ -435,6 +435,10 @@ void darwin::Linker::ConstructJob(Compilation &C, const JobAction &JA, // more information. ArgStringList CmdArgs; + Args.ClaimAllArgs(options::OPT_index_store_path); + Args.ClaimAllArgs(options::OPT_index_ignore_system_symbols); + Args.ClaimAllArgs(options::OPT_index_record_codegen_name); + /// Hack(tm) to ignore linking errors when we are doing ARC migration. if (Args.hasArg(options::OPT_ccc_arcmt_check, options::OPT_ccc_arcmt_migrate)) { diff --git a/clang/lib/Frontend/CMakeLists.txt b/clang/lib/Frontend/CMakeLists.txt index ba3bd7d28c703..9f4f7d316cee5 100644 --- a/clang/lib/Frontend/CMakeLists.txt +++ b/clang/lib/Frontend/CMakeLists.txt @@ -57,6 +57,7 @@ add_clang_library(clangFrontend clangBasic clangDriver clangEdit + clangIndex clangLex clangParse clangSema diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 849d6651fb796..de594a0ce4393 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -29,6 +29,7 @@ #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/Utils.h" #include "clang/Frontend/VerifyDiagnosticConsumer.h" +#include "clang/Index/IndexingAction.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/PTHManager.h" #include "clang/Lex/Preprocessor.h" @@ -1166,8 +1167,18 @@ compileModuleImpl(CompilerInstance &ImportingInstance, SourceLocation ImportLoc, llvm::CrashRecoveryContext CRC; CRC.RunSafelyOnThread( [&]() { - GenerateModuleFromModuleMapAction Action; - Instance.ExecuteAction(Action); + // FIXME: I have no idea what the best way to do this is, but it's + // probably not this. Interfaces changed upstream. + std::unique_ptr Action( + new GenerateModuleFromModuleMapAction); + + if (!FrontendOpts.IndexStorePath.empty()) { +#if defined(__APPLE__) + Action = index::createIndexDataRecordingAction(FrontendOpts, + std::move(Action)); +#endif + } + Instance.ExecuteAction(*Action); }, ThreadStackSize); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index e7603ce522214..adb970c0ea5dc 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1382,6 +1382,10 @@ static InputKind ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args, << "ARC migration" << "ObjC migration"; } + Opts.IndexStorePath = Args.getLastArgValue(OPT_index_store_path); + Opts.IndexIgnoreSystemSymbols = Args.hasArg(OPT_index_ignore_system_symbols); + Opts.IndexRecordCodegenName = Args.hasArg(OPT_index_record_codegen_name); + InputKind DashX(InputKind::Unknown); if (const Arg *A = Args.getLastArg(OPT_x)) { StringRef XValue = A->getValue(); diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index a7c140188b35b..816a1e7a817d9 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -22,6 +22,7 @@ #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/FrontendPluginRegistry.h" #include "clang/Frontend/Utils.h" +#include "clang/Index/IndexingAction.h" #include "clang/Rewrite/Frontend/FrontendActions.h" #include "clang/StaticAnalyzer/Frontend/FrontendActions.h" #include "llvm/Option/OptTable.h" @@ -163,6 +164,12 @@ CreateFrontendAction(CompilerInstance &CI) { } #endif + if (!FEOpts.IndexStorePath.empty()) { +#if defined(__APPLE__) + Act = index::createIndexDataRecordingAction(FEOpts, std::move(Act)); +#endif + } + // If there are any AST files to merge, create a frontend action // adaptor to perform the merge. if (!FEOpts.ASTMergeFiles.empty()) diff --git a/clang/lib/Index/BitstreamVisitor.h b/clang/lib/Index/BitstreamVisitor.h new file mode 100644 index 0000000000000..d324f1a4fdbac --- /dev/null +++ b/clang/lib/Index/BitstreamVisitor.h @@ -0,0 +1,163 @@ +//===--- BitstreamVisitor.h - Helper for reading a bitstream --------------===// +// +// 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_INDEX_BITSTREAMVISITOR_H +#define LLVM_CLANG_LIB_INDEX_BITSTREAMVISITOR_H + +#include "llvm/Bitcode/BitstreamReader.h" +#include "clang/Basic/LLVM.h" +#include "clang/Serialization/ASTReader.h" +#include + +namespace clang { +namespace index { +namespace store { + +/// Helper class that saves the current stream position and +/// then restores it when destroyed. +struct SavedStreamPosition { + explicit SavedStreamPosition(llvm::BitstreamCursor &Cursor) + : Cursor(Cursor), Offset(Cursor.GetCurrentBitNo()) { } + + ~SavedStreamPosition() { + Cursor.JumpToBit(Offset); + } + +private: + llvm::BitstreamCursor &Cursor; + uint64_t Offset; +}; + +enum class StreamVisit { + Continue, + Skip, + Abort +}; + +template +class BitstreamVisitor { + SmallVector BlockStack; + +protected: + llvm::BitstreamCursor &Stream; + Optional BlockInfo; + std::string *Error; + +public: + BitstreamVisitor(llvm::BitstreamCursor &Stream) + : Stream(Stream) {} + + StreamVisit visitBlock(unsigned ID) { + return StreamVisit::Continue; + } + + bool visit(std::string &Error) { + this->Error = &Error; + + ASTReader::RecordData Record; + while (1) { + llvm::BitstreamEntry Entry = Stream.advance(llvm::BitstreamCursor::AF_DontPopBlockAtEnd); + + switch (Entry.Kind) { + case llvm::BitstreamEntry::Error: + Error = "malformed serialization"; + return false; + + case llvm::BitstreamEntry::EndBlock: + if (BlockStack.empty()) + return true; + BlockStack.pop_back(); + if (Stream.ReadBlockEnd()) { + Error = "malformed serialization"; + return false; + } + if (Stream.AtEndOfStream()) + return true; + break; + + case llvm::BitstreamEntry::SubBlock: { + if (Entry.ID == llvm::bitc::BLOCKINFO_BLOCK_ID) { + BlockInfo = Stream.ReadBlockInfoBlock(); + if (!BlockInfo) { + Error = "malformed BlockInfoBlock"; + return false; + } + Stream.setBlockInfo(&*BlockInfo); + break; + } + + StreamVisit Ret = static_cast(this)->visitBlock(Entry.ID); + switch (Ret) { + case StreamVisit::Continue: + if (Stream.EnterSubBlock(Entry.ID)) { + Error = "malformed block record"; + return false; + } + readBlockAbbrevs(Stream); + BlockStack.push_back(Entry.ID); + break; + + case StreamVisit::Skip: + if (Stream.SkipBlock()) { + Error = "malformed serialization"; + return false; + } + if (Stream.AtEndOfStream()) + return true; + break; + + case StreamVisit::Abort: + return false; + } + break; + } + + case llvm::BitstreamEntry::Record: { + Record.clear(); + StringRef Blob; + unsigned RecID = Stream.readRecord(Entry.ID, Record, &Blob); + unsigned BlockID = BlockStack.empty() ? 0 : BlockStack.back(); + StreamVisit Ret = static_cast(this)->visitRecord(BlockID, RecID, Record, Blob); + switch (Ret) { + case StreamVisit::Continue: + break; + + case StreamVisit::Skip: + Stream.skipRecord(Entry.ID); + break; + + case StreamVisit::Abort: + return false; + } + break; + } + } + } + } + + static void readBlockAbbrevs(llvm::BitstreamCursor &Cursor) { + while (true) { + uint64_t Offset = Cursor.GetCurrentBitNo(); + unsigned Code = Cursor.ReadCode(); + + // We expect all abbrevs to be at the start of the block. + if (Code != llvm::bitc::DEFINE_ABBREV) { + Cursor.JumpToBit(Offset); + return; + } + Cursor.ReadAbbrevRecord(); + } + } +}; + +} // end namespace store +} // end namespace index +} // end namespace clang + +#endif diff --git a/clang/lib/Index/CMakeLists.txt b/clang/lib/Index/CMakeLists.txt index c9fbfafcf9460..b5dea4cd05c50 100644 --- a/clang/lib/Index/CMakeLists.txt +++ b/clang/lib/Index/CMakeLists.txt @@ -4,14 +4,23 @@ set(LLVM_LINK_COMPONENTS ) add_clang_library(clangIndex + ClangIndexRecordWriter.cpp CodegenNameGenerator.cpp CommentToXML.cpp + FileIndexRecord.cpp IndexBody.cpp + IndexDataStore.cpp + IndexDataStoreUtils.cpp IndexDecl.cpp IndexingAction.cpp IndexingContext.cpp + IndexRecordHasher.cpp + IndexRecordReader.cpp + IndexRecordWriter.cpp IndexSymbol.cpp IndexTypeSourceInfo.cpp + IndexUnitReader.cpp + IndexUnitWriter.cpp USRGeneration.cpp ADDITIONAL_HEADERS diff --git a/clang/lib/Index/ClangIndexRecordWriter.cpp b/clang/lib/Index/ClangIndexRecordWriter.cpp new file mode 100644 index 0000000000000..612ef1b5b41a0 --- /dev/null +++ b/clang/lib/Index/ClangIndexRecordWriter.cpp @@ -0,0 +1,128 @@ +//===--- ClangIndexRecordWriter.cpp - Index record serialization ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangIndexRecordWriter.h" +#include "FileIndexRecord.h" +#include "clang/Index/IndexSymbol.h" +#include "clang/Index/IndexRecordReader.h" +#include "clang/Index/USRGeneration.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" + +using namespace clang; +using namespace clang::index; + +StringRef ClangIndexRecordWriter::getUSR(const Decl *D) { + assert(D->isCanonicalDecl()); + auto Insert = USRByDecl.insert(std::make_pair(D, StringRef())); + if (Insert.second) { + Insert.first->second = getUSRNonCached(D); + } + return Insert.first->second; +} + +StringRef ClangIndexRecordWriter::getUSRNonCached(const Decl *D) { + SmallString<256> Buf; + bool Ignore = generateUSRForDecl(D, Buf); + if (Ignore) + return StringRef(); + StringRef USR = Buf.str(); + char *Ptr = Allocator.Allocate(USR.size()); + std::copy(USR.begin(), USR.end(), Ptr); + return StringRef(Ptr, USR.size()); +} + +ClangIndexRecordWriter::ClangIndexRecordWriter(ASTContext &Ctx, + RecordingOptions Opts) + : Impl(Opts.DataDirPath), Ctx(Ctx), RecordOpts(std::move(Opts)), + Hasher(Ctx) { + if (Opts.RecordSymbolCodeGenName) + CGNameGen.reset(new CodegenNameGenerator(Ctx)); +} + +ClangIndexRecordWriter::~ClangIndexRecordWriter() {} + +bool ClangIndexRecordWriter::writeRecord(StringRef Filename, + const FileIndexRecord &IdxRecord, + std::string &Error, + std::string *OutRecordFile) { + + auto RecordHash = Hasher.hashRecord(IdxRecord); + + switch (Impl.beginRecord(Filename, RecordHash, Error, OutRecordFile)) { + case IndexRecordWriter::Result::Success: + break; // Continue writing. + case IndexRecordWriter::Result::Failure: + return true; + case IndexRecordWriter::Result::AlreadyExists: + return false; + } + + ASTContext &Ctx = getASTContext(); + SourceManager &SM = Ctx.getSourceManager(); + FileID FID = IdxRecord.getFileID(); + auto getLineCol = [&](unsigned Offset) -> std::pair { + unsigned LineNo = SM.getLineNumber(FID, Offset); + unsigned ColNo = SM.getColumnNumber(FID, Offset); + return std::make_pair(LineNo, ColNo); + }; + + for (auto &Occur : IdxRecord.getDeclOccurrences()) { + unsigned Line, Col; + std::tie(Line, Col) = getLineCol(Occur.Offset); + SmallVector Related; + Related.reserve(Occur.Relations.size()); + for (auto &Rel : Occur.Relations) + Related.push_back(writer::SymbolRelation{Rel.RelatedSymbol, Rel.Roles}); + + Impl.addOccurrence(Occur.Dcl, Occur.Roles, Line, Col, Related); + } + + PrintingPolicy Policy(Ctx.getLangOpts()); + Policy.SuppressTemplateArgsInCXXConstructors = true; + + auto Result = Impl.endRecord(Error, + [&](writer::OpaqueDecl OD, SmallVectorImpl &Scratch) { + const Decl *D = static_cast(OD); + auto Info = getSymbolInfo(D); + + writer::Symbol Sym; + Sym.SymInfo = Info; + + auto *ND = dyn_cast(D); + if (ND) { + llvm::raw_svector_ostream OS(Scratch); + DeclarationName DeclName = ND->getDeclName(); + if (!DeclName.isEmpty()) + DeclName.print(OS, Policy); + } + unsigned NameLen = Scratch.size(); + Sym.Name = StringRef(Scratch.data(), NameLen); + + Sym.USR = getUSR(D); + assert(!Sym.USR.empty() && "Recorded decl without USR!"); + + if (CGNameGen && ND) { + llvm::raw_svector_ostream OS(Scratch); + CGNameGen->writeName(ND, OS); + } + unsigned CGNameLen = Scratch.size() - NameLen; + Sym.CodeGenName = StringRef(Scratch.data() + NameLen, CGNameLen); + return Sym; + }); + + switch (Result) { + case IndexRecordWriter::Result::Success: + case IndexRecordWriter::Result::AlreadyExists: + return false; + case IndexRecordWriter::Result::Failure: + return true; + } +} diff --git a/clang/lib/Index/ClangIndexRecordWriter.h b/clang/lib/Index/ClangIndexRecordWriter.h new file mode 100644 index 0000000000000..b68f9875fb34b --- /dev/null +++ b/clang/lib/Index/ClangIndexRecordWriter.h @@ -0,0 +1,55 @@ +//===--- ClangIndexRecordWriter.h - Index record serialization ------------===// +// +// 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_INDEX_CLANGINDEXRECORDWRITER_H +#define LLVM_CLANG_LIB_INDEX_CLANGINDEXRECORDWRITER_H + +#include "IndexRecordHasher.h" +#include "clang/Index/IndexRecordWriter.h" +#include "clang/Index/IndexingAction.h" +#include "clang/Index/CodegenNameGenerator.h" +#include "llvm/ADT/SmallString.h" + +namespace clang { + class ASTContext; + class Decl; + +namespace index { + class FileIndexRecord; + +class ClangIndexRecordWriter { + IndexRecordWriter Impl; + + ASTContext &Ctx; + RecordingOptions RecordOpts; + + std::unique_ptr CGNameGen; + llvm::BumpPtrAllocator Allocator; + llvm::DenseMap USRByDecl; + IndexRecordHasher Hasher; + +public: + ClangIndexRecordWriter(ASTContext &Ctx, RecordingOptions Opts); + ~ClangIndexRecordWriter(); + + ASTContext &getASTContext() { return Ctx; } + CodegenNameGenerator *getCGNameGen() { return CGNameGen.get(); } + + bool writeRecord(StringRef Filename, const FileIndexRecord &Record, + std::string &Error, std::string *RecordFile = nullptr); + StringRef getUSR(const Decl *D); + +private: + StringRef getUSRNonCached(const Decl *D); +}; + +} // end namespace index +} // end namespace clang + +#endif diff --git a/clang/lib/Index/FileIndexRecord.cpp b/clang/lib/Index/FileIndexRecord.cpp new file mode 100644 index 0000000000000..98d66b67c0780 --- /dev/null +++ b/clang/lib/Index/FileIndexRecord.cpp @@ -0,0 +1,59 @@ +//===--- FileIndexRecord.cpp - Index data per file ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FileIndexRecord.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Path.h" + +using namespace clang; +using namespace clang::index; + +void FileIndexRecord::addDeclOccurence(SymbolRoleSet Roles, + unsigned Offset, + const Decl *D, + ArrayRef Relations) { + assert(D->isCanonicalDecl()); + + auto IsNextOccurence = [&]()->bool { + if (Decls.empty()) + return true; + auto &Last = Decls.back(); + return Last.Offset < Offset; + }; + + if (IsNextOccurence()) { + Decls.emplace_back(Roles, Offset, D, Relations); + return; + } + + DeclOccurrence NewInfo(Roles, Offset, D, Relations); + auto It = std::upper_bound(Decls.begin(), Decls.end(), NewInfo); + Decls.insert(It, std::move(NewInfo)); +} + +void FileIndexRecord::print(llvm::raw_ostream &OS) { + OS << "DECLS BEGIN ---\n"; + for (auto &DclInfo : Decls) { + auto D = DclInfo.Dcl; + SourceManager &SM = D->getASTContext().getSourceManager(); + SourceLocation Loc = SM.getFileLoc(D->getLocation()); + PresumedLoc PLoc = SM.getPresumedLoc(Loc); + OS << llvm::sys::path::filename(PLoc.getFilename()) << ':' << PLoc.getLine() + << ':' << PLoc.getColumn(); + + if (auto ND = dyn_cast(D)) { + OS << ' ' << ND->getNameAsString(); + } + + OS << '\n'; + } + OS << "DECLS END ---\n"; +} diff --git a/clang/lib/Index/FileIndexRecord.h b/clang/lib/Index/FileIndexRecord.h new file mode 100644 index 0000000000000..a663173c7d12d --- /dev/null +++ b/clang/lib/Index/FileIndexRecord.h @@ -0,0 +1,69 @@ +//===--- FileIndexRecord.h - Index data per file --------------------------===// +// +// 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_INDEX_FILEINDEXRECORD_H +#define LLVM_CLANG_LIB_INDEX_FILEINDEXRECORD_H + +#include "clang/Index/IndexSymbol.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include + +namespace clang { + class IdentifierInfo; + +namespace index { + +class FileIndexRecord { +public: + struct DeclOccurrence { + SymbolRoleSet Roles; + unsigned Offset; + const Decl *Dcl; + SmallVector Relations; + + DeclOccurrence(SymbolRoleSet R, + unsigned Offset, + const Decl *D, + ArrayRef Relations) + : Roles(R), + Offset(Offset), + Dcl(D), + Relations(Relations.begin(), Relations.end()) {} + + friend bool operator <(const DeclOccurrence &LHS, const DeclOccurrence &RHS) { + return LHS.Offset < RHS.Offset; + } + }; + +private: + FileID FID; + bool IsSystem; + std::vector Decls; + +public: + FileIndexRecord(FileID FID, bool isSystem) : FID(FID), IsSystem(isSystem) {} + + ArrayRef getDeclOccurrences() const { return Decls; } + + FileID getFileID() const { return FID; } + bool isSystem() const { return IsSystem; } + + void addDeclOccurence(SymbolRoleSet Roles, + unsigned Offset, + const Decl *D, + ArrayRef Relations); + void print(llvm::raw_ostream &OS); +}; + +} // end namespace index +} // end namespace clang + +#endif diff --git a/clang/lib/Index/IndexDataStore.cpp b/clang/lib/Index/IndexDataStore.cpp new file mode 100644 index 0000000000000..4140392dfc565 --- /dev/null +++ b/clang/lib/Index/IndexDataStore.cpp @@ -0,0 +1,259 @@ +//===--- IndexDataStore.cpp - Index data store info -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Index/IndexDataStore.h" +#include "IndexDataStoreUtils.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Mutex.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::index; +using namespace clang::index::store; +using namespace llvm; + +static void appendSubDir(StringRef subdir, SmallVectorImpl &StorePathBuf) { + SmallString<10> VersionPath; + raw_svector_ostream(VersionPath) << 'v' << STORE_FORMAT_VERSION; + + sys::path::append(StorePathBuf, VersionPath); + sys::path::append(StorePathBuf, subdir); +} + +void store::appendInteriorUnitPath(StringRef UnitName, + SmallVectorImpl &PathBuf) { + sys::path::append(PathBuf, UnitName); +} + +void store::appendUnitSubDir(SmallVectorImpl &StorePathBuf) { + return appendSubDir("units", StorePathBuf); +} + +void store::appendRecordSubDir(SmallVectorImpl &StorePathBuf) { + return appendSubDir("records", StorePathBuf); +} + +void store::appendInteriorRecordPath(StringRef RecordName, + SmallVectorImpl &PathBuf) { + // To avoid putting a huge number of files into the records directory, create + // subdirectories based on the last 2 characters from the hash. + StringRef hash2chars = RecordName.substr(RecordName.size()-2); + sys::path::append(PathBuf, hash2chars); + sys::path::append(PathBuf, RecordName); +} + +//===----------------------------------------------------------------------===// +// IndexDataStore +//===----------------------------------------------------------------------===// + +namespace { + +class UnitEventHandlerData { + mutable sys::Mutex Mtx; + IndexDataStore::UnitEventHandler Handler; + +public: + void setHandler(IndexDataStore::UnitEventHandler handler) { + sys::ScopedLock L(Mtx); + Handler = std::move(handler); + } + IndexDataStore::UnitEventHandler getHandler() const { + sys::ScopedLock L(Mtx); + return Handler; + } +}; + +class IndexDataStoreImpl { + std::string FilePath; + std::shared_ptr TheUnitEventHandlerData; + std::unique_ptr DirWatcher; + +public: + explicit IndexDataStoreImpl(StringRef indexStorePath) + : FilePath(indexStorePath) { + TheUnitEventHandlerData = std::make_shared(); + } + + StringRef getFilePath() const { return FilePath; } + bool foreachUnitName(bool sorted, + llvm::function_ref receiver); + void setUnitEventHandler(IndexDataStore::UnitEventHandler Handler); + bool startEventListening(llvm::function_ref createFn, + bool waitInitialSync, std::string &Error); + void stopEventListening(); + void discardUnit(StringRef UnitName); + void discardRecord(StringRef RecordName); + void purgeStaleData(); +}; + +} // anonymous namespace + +bool IndexDataStoreImpl::foreachUnitName(bool sorted, + llvm::function_ref receiver) { + SmallString<128> UnitPath; + UnitPath = FilePath; + appendUnitSubDir(UnitPath); + + std::vector filenames; + + std::error_code EC; + for (auto It = sys::fs::directory_iterator(UnitPath, EC), + End = sys::fs::directory_iterator(); + !EC && It != End; It.increment(EC)) { + StringRef unitName = sys::path::filename(It->path()); + if (!sorted) { + if (!receiver(unitName)) + return false; + } else { + filenames.push_back(unitName); + } + } + + if (sorted) { + llvm::array_pod_sort(filenames.begin(), filenames.end()); + for (auto &fname : filenames) + if (!receiver(fname)) + return false; + } + return true; +} + +void IndexDataStoreImpl::setUnitEventHandler(IndexDataStore::UnitEventHandler handler) { + TheUnitEventHandlerData->setHandler(std::move(handler)); +} + +bool IndexDataStoreImpl::startEventListening(llvm::function_ref createFn, + bool waitInitialSync, std::string &Error) { + if (DirWatcher) { + Error = "event listener already active"; + return true; + } + + SmallString<128> UnitPath; + UnitPath = FilePath; + appendUnitSubDir(UnitPath); + + auto localUnitEventHandlerData = TheUnitEventHandlerData; + auto OnUnitsChange = [localUnitEventHandlerData](ArrayRef Events, bool isInitial) { + SmallVector UnitEvents; + UnitEvents.reserve(Events.size()); + for (const AbstractDirectoryWatcher::Event &evt : Events) { + IndexDataStore::UnitEventKind K; + StringRef UnitName = sys::path::filename(evt.Filename); + switch (evt.Kind) { + case AbstractDirectoryWatcher::EventKind::Added: + K = IndexDataStore::UnitEventKind::Added; break; + case AbstractDirectoryWatcher::EventKind::Removed: + K = IndexDataStore::UnitEventKind::Removed; break; + case AbstractDirectoryWatcher::EventKind::Modified: + K = IndexDataStore::UnitEventKind::Modified; break; + case AbstractDirectoryWatcher::EventKind::DirectoryDeleted: + K = IndexDataStore::UnitEventKind::DirectoryDeleted; + UnitName = StringRef(); + break; + } + UnitEvents.push_back(IndexDataStore::UnitEvent{K, UnitName, evt.ModTime}); + } + + if (auto handler = localUnitEventHandlerData->getHandler()) { + IndexDataStore::UnitEventNotification EventNote{isInitial, UnitEvents}; + handler(EventNote); + } + }; + + DirWatcher = createFn(UnitPath.str(), OnUnitsChange, waitInitialSync, Error); + if (!DirWatcher) + return true; + + return false; +} + +void IndexDataStoreImpl::stopEventListening() { + DirWatcher.reset(); +} + +void IndexDataStoreImpl::discardUnit(StringRef UnitName) { + SmallString<128> UnitPath; + UnitPath = FilePath; + appendUnitSubDir(UnitPath); + appendInteriorUnitPath(UnitName, UnitPath); + sys::fs::remove(UnitPath); +} + +void IndexDataStoreImpl::discardRecord(StringRef RecordName) { + SmallString<128> RecordPath; + RecordPath = FilePath; + appendRecordSubDir(RecordPath); + appendInteriorRecordPath(RecordName, RecordPath); + sys::fs::remove(RecordPath); +} + +void IndexDataStoreImpl::purgeStaleData() { + // FIXME: Implement. +} + + +std::unique_ptr +IndexDataStore::create(StringRef IndexStorePath, std::string &Error) { + if (!sys::fs::exists(IndexStorePath)) { + raw_string_ostream OS(Error); + OS << "index store path does not exist: " << IndexStorePath; + return nullptr; + } + + return std::unique_ptr( + new IndexDataStore(new IndexDataStoreImpl(IndexStorePath))); +} + +#define IMPL static_cast(Impl) + +IndexDataStore::~IndexDataStore() { + delete IMPL; +} + +StringRef IndexDataStore::getFilePath() const { + return IMPL->getFilePath(); +} + +bool IndexDataStore::foreachUnitName(bool sorted, + llvm::function_ref receiver) { + return IMPL->foreachUnitName(sorted, std::move(receiver)); +} + +unsigned IndexDataStore::getFormatVersion() { + return STORE_FORMAT_VERSION; +} + +void IndexDataStore::setUnitEventHandler(UnitEventHandler Handler) { + return IMPL->setUnitEventHandler(std::move(Handler)); +} + +bool IndexDataStore::startEventListening(llvm::function_ref createFn, + bool waitInitialSync, std::string &Error) { + return IMPL->startEventListening(std::move(createFn), waitInitialSync, Error); +} + +void IndexDataStore::stopEventListening() { + return IMPL->stopEventListening(); +} + +void IndexDataStore::discardUnit(StringRef UnitName) { + IMPL->discardUnit(UnitName); +} + +void IndexDataStore::discardRecord(StringRef RecordName) { + IMPL->discardRecord(RecordName); +} + +void IndexDataStore::purgeStaleData() { + IMPL->purgeStaleData(); +} diff --git a/clang/lib/Index/IndexDataStoreUtils.cpp b/clang/lib/Index/IndexDataStoreUtils.cpp new file mode 100644 index 0000000000000..0d9e754cf95fa --- /dev/null +++ b/clang/lib/Index/IndexDataStoreUtils.cpp @@ -0,0 +1,398 @@ +//===--- IndexDataStoreUtils.cpp - Functions/constants for the data store -===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Index/IndexDataStoreSymbolUtils.h" +#include "IndexDataStoreUtils.h" +#include "llvm/Bitcode/BitstreamWriter.h" + +using namespace clang; +using namespace clang::index; +using namespace clang::index::store; +using namespace llvm; + +void store::emitBlockID(unsigned ID, const char *Name, + BitstreamWriter &Stream, RecordDataImpl &Record) { + Record.clear(); + Record.push_back(ID); + Stream.EmitRecord(bitc::BLOCKINFO_CODE_SETBID, Record); + + // Emit the block name if present. + if (!Name || Name[0] == 0) + return; + Record.clear(); + while (*Name) + Record.push_back(*Name++); + Stream.EmitRecord(bitc::BLOCKINFO_CODE_BLOCKNAME, Record); +} + +void store::emitRecordID(unsigned ID, const char *Name, + BitstreamWriter &Stream, + RecordDataImpl &Record) { + Record.clear(); + Record.push_back(ID); + while (*Name) + Record.push_back(*Name++); + Stream.EmitRecord(bitc::BLOCKINFO_CODE_SETRECORDNAME, Record); +} + +/// Map an indexstore_symbol_kind_t to a SymbolKind, handling unknown values. +SymbolKind index::getSymbolKind(indexstore_symbol_kind_t K) { + switch ((uint64_t)K) { + default: + case INDEXSTORE_SYMBOL_KIND_UNKNOWN: + return SymbolKind::Unknown; + case INDEXSTORE_SYMBOL_KIND_MODULE: + return SymbolKind::Module; + case INDEXSTORE_SYMBOL_KIND_NAMESPACE: + return SymbolKind::Namespace; + case INDEXSTORE_SYMBOL_KIND_NAMESPACEALIAS: + return SymbolKind::NamespaceAlias; + case INDEXSTORE_SYMBOL_KIND_MACRO: + return SymbolKind::Macro; + case INDEXSTORE_SYMBOL_KIND_ENUM: + return SymbolKind::Enum; + case INDEXSTORE_SYMBOL_KIND_STRUCT: + return SymbolKind::Struct; + case INDEXSTORE_SYMBOL_KIND_CLASS: + return SymbolKind::Class; + case INDEXSTORE_SYMBOL_KIND_PROTOCOL: + return SymbolKind::Protocol; + case INDEXSTORE_SYMBOL_KIND_EXTENSION: + return SymbolKind::Extension; + case INDEXSTORE_SYMBOL_KIND_UNION: + return SymbolKind::Union; + case INDEXSTORE_SYMBOL_KIND_TYPEALIAS: + return SymbolKind::TypeAlias; + case INDEXSTORE_SYMBOL_KIND_FUNCTION: + return SymbolKind::Function; + case INDEXSTORE_SYMBOL_KIND_VARIABLE: + return SymbolKind::Variable; + case INDEXSTORE_SYMBOL_KIND_FIELD: + return SymbolKind::Field; + case INDEXSTORE_SYMBOL_KIND_ENUMCONSTANT: + return SymbolKind::EnumConstant; + case INDEXSTORE_SYMBOL_KIND_INSTANCEMETHOD: + return SymbolKind::InstanceMethod; + case INDEXSTORE_SYMBOL_KIND_CLASSMETHOD: + return SymbolKind::ClassMethod; + case INDEXSTORE_SYMBOL_KIND_STATICMETHOD: + return SymbolKind::StaticMethod; + case INDEXSTORE_SYMBOL_KIND_INSTANCEPROPERTY: + return SymbolKind::InstanceProperty; + case INDEXSTORE_SYMBOL_KIND_CLASSPROPERTY: + return SymbolKind::ClassProperty; + case INDEXSTORE_SYMBOL_KIND_STATICPROPERTY: + return SymbolKind::StaticProperty; + case INDEXSTORE_SYMBOL_KIND_CONSTRUCTOR: + return SymbolKind::Constructor; + case INDEXSTORE_SYMBOL_KIND_DESTRUCTOR: + return SymbolKind::Destructor; + case INDEXSTORE_SYMBOL_KIND_CONVERSIONFUNCTION: + return SymbolKind::ConversionFunction; + case INDEXSTORE_SYMBOL_KIND_PARAMETER: + return SymbolKind::Parameter; + case INDEXSTORE_SYMBOL_KIND_COMMENTTAG: + return SymbolKind::CommentTag; + } +} + +SymbolSubKind index::getSymbolSubKind(indexstore_symbol_subkind_t K) { + switch ((uint64_t)K) { + default: + case INDEXSTORE_SYMBOL_SUBKIND_NONE: + return SymbolSubKind::None; + case INDEXSTORE_SYMBOL_SUBKIND_CXXCOPYCONSTRUCTOR: + return SymbolSubKind::CXXCopyConstructor; + case INDEXSTORE_SYMBOL_SUBKIND_CXXMOVECONSTRUCTOR: + return SymbolSubKind::CXXMoveConstructor; + case INDEXSTORE_SYMBOL_SUBKIND_ACCESSORGETTER: + return SymbolSubKind::AccessorGetter; + case INDEXSTORE_SYMBOL_SUBKIND_ACCESSORSETTER: + return SymbolSubKind::AccessorSetter; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORWILLSET: + return SymbolSubKind::SwiftAccessorWillSet; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORDIDSET: + return SymbolSubKind::SwiftAccessorDidSet; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORADDRESSOR: + return SymbolSubKind::SwiftAccessorAddressor; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORMUTABLEADDRESSOR: + return SymbolSubKind::SwiftAccessorMutableAddressor; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFSTRUCT: + return SymbolSubKind::SwiftExtensionOfStruct; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFCLASS: + return SymbolSubKind::SwiftExtensionOfClass; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFENUM: + return SymbolSubKind::SwiftExtensionOfEnum; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFPROTOCOL: + return SymbolSubKind::SwiftExtensionOfProtocol; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTPREFIXOPERATOR: + return SymbolSubKind::SwiftPrefixOperator; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTPOSTFIXOPERATOR: + return SymbolSubKind::SwiftPostfixOperator; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTINFIXOPERATOR: + return SymbolSubKind::SwiftInfixOperator; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTSUBSCRIPT: + return SymbolSubKind::SwiftSubscript; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTASSOCIATEDTYPE: + return SymbolSubKind::SwiftAssociatedType; + case INDEXSTORE_SYMBOL_SUBKIND_SWIFTGENERICTYPEPARAM: + return SymbolSubKind::SwiftGenericTypeParam; + } +} + +/// Map an indexstore_symbol_language_t to a SymbolLanguage, handling unknown +/// values. +SymbolLanguage index::getSymbolLanguage(indexstore_symbol_language_t L) { + switch ((uint64_t)L) { + default: // FIXME: add an unknown language? + case INDEXSTORE_SYMBOL_LANG_C: + return SymbolLanguage::C; + case INDEXSTORE_SYMBOL_LANG_OBJC: + return SymbolLanguage::ObjC; + case INDEXSTORE_SYMBOL_LANG_CXX: + return SymbolLanguage::CXX; + case INDEXSTORE_SYMBOL_LANG_SWIFT: + return SymbolLanguage::Swift; + } +} + +/// Map an indexstore representation to a SymbolPropertySet, handling +/// unknown values. +SymbolPropertySet index::getSymbolProperties(uint64_t Props) { + // FIXME: currently these enums must be kept in sync. + return (uint64_t)Props; +} + +/// Map an indexstore representation to a SymbolRoleSet, handling unknown +/// values. +SymbolRoleSet index::getSymbolRoles(uint64_t Roles) { + // FIXME: currently these enums must be kept in sync. + return (uint64_t)Roles; +} + +/// Map a SymbolLanguage to a indexstore_symbol_language_t. +indexstore_symbol_kind_t index::getIndexStoreKind(SymbolKind K) { + switch (K) { + case SymbolKind::Unknown: + return INDEXSTORE_SYMBOL_KIND_UNKNOWN; + case SymbolKind::Module: + return INDEXSTORE_SYMBOL_KIND_MODULE; + case SymbolKind::Namespace: + return INDEXSTORE_SYMBOL_KIND_NAMESPACE; + case SymbolKind::NamespaceAlias: + return INDEXSTORE_SYMBOL_KIND_NAMESPACEALIAS; + case SymbolKind::Macro: + return INDEXSTORE_SYMBOL_KIND_MACRO; + case SymbolKind::Enum: + return INDEXSTORE_SYMBOL_KIND_ENUM; + case SymbolKind::Struct: + return INDEXSTORE_SYMBOL_KIND_STRUCT; + case SymbolKind::Class: + return INDEXSTORE_SYMBOL_KIND_CLASS; + case SymbolKind::Protocol: + return INDEXSTORE_SYMBOL_KIND_PROTOCOL; + case SymbolKind::Extension: + return INDEXSTORE_SYMBOL_KIND_EXTENSION; + case SymbolKind::Union: + return INDEXSTORE_SYMBOL_KIND_UNION; + case SymbolKind::TypeAlias: + return INDEXSTORE_SYMBOL_KIND_TYPEALIAS; + case SymbolKind::Function: + return INDEXSTORE_SYMBOL_KIND_FUNCTION; + case SymbolKind::Variable: + return INDEXSTORE_SYMBOL_KIND_VARIABLE; + case SymbolKind::Field: + return INDEXSTORE_SYMBOL_KIND_FIELD; + case SymbolKind::EnumConstant: + return INDEXSTORE_SYMBOL_KIND_ENUMCONSTANT; + case SymbolKind::InstanceMethod: + return INDEXSTORE_SYMBOL_KIND_INSTANCEMETHOD; + case SymbolKind::ClassMethod: + return INDEXSTORE_SYMBOL_KIND_CLASSMETHOD; + case SymbolKind::StaticMethod: + return INDEXSTORE_SYMBOL_KIND_STATICMETHOD; + case SymbolKind::InstanceProperty: + return INDEXSTORE_SYMBOL_KIND_INSTANCEPROPERTY; + case SymbolKind::ClassProperty: + return INDEXSTORE_SYMBOL_KIND_CLASSPROPERTY; + case SymbolKind::StaticProperty: + return INDEXSTORE_SYMBOL_KIND_STATICPROPERTY; + case SymbolKind::Constructor: + return INDEXSTORE_SYMBOL_KIND_CONSTRUCTOR; + case SymbolKind::Destructor: + return INDEXSTORE_SYMBOL_KIND_DESTRUCTOR; + case SymbolKind::ConversionFunction: + return INDEXSTORE_SYMBOL_KIND_CONVERSIONFUNCTION; + case SymbolKind::Parameter: + return INDEXSTORE_SYMBOL_KIND_PARAMETER; + case SymbolKind::CommentTag: + return INDEXSTORE_SYMBOL_KIND_COMMENTTAG; + } + llvm_unreachable("unexpected symbol kind"); +} + +indexstore_symbol_subkind_t index::getIndexStoreSubKind(SymbolSubKind K) { + switch (K) { + case SymbolSubKind::None: + return INDEXSTORE_SYMBOL_SUBKIND_NONE; + case SymbolSubKind::CXXCopyConstructor: + return INDEXSTORE_SYMBOL_SUBKIND_CXXCOPYCONSTRUCTOR; + case SymbolSubKind::CXXMoveConstructor: + return INDEXSTORE_SYMBOL_SUBKIND_CXXMOVECONSTRUCTOR; + case SymbolSubKind::AccessorGetter: + return INDEXSTORE_SYMBOL_SUBKIND_ACCESSORGETTER; + case SymbolSubKind::AccessorSetter: + return INDEXSTORE_SYMBOL_SUBKIND_ACCESSORSETTER; + case SymbolSubKind::SwiftAccessorWillSet: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORWILLSET; + case SymbolSubKind::SwiftAccessorDidSet: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORDIDSET; + case SymbolSubKind::SwiftAccessorAddressor: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORADDRESSOR; + case SymbolSubKind::SwiftAccessorMutableAddressor: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORMUTABLEADDRESSOR; + case SymbolSubKind::SwiftExtensionOfStruct: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFSTRUCT; + case SymbolSubKind::SwiftExtensionOfClass: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFCLASS; + case SymbolSubKind::SwiftExtensionOfEnum: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFENUM; + case SymbolSubKind::SwiftExtensionOfProtocol: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFPROTOCOL; + case SymbolSubKind::SwiftPrefixOperator: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTPREFIXOPERATOR; + case SymbolSubKind::SwiftPostfixOperator: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTPOSTFIXOPERATOR; + case SymbolSubKind::SwiftInfixOperator: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTINFIXOPERATOR; + case SymbolSubKind::SwiftSubscript: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTSUBSCRIPT; + case SymbolSubKind::SwiftAssociatedType: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTASSOCIATEDTYPE; + case SymbolSubKind::SwiftGenericTypeParam: + return INDEXSTORE_SYMBOL_SUBKIND_SWIFTGENERICTYPEPARAM; + } + llvm_unreachable("unexpected symbol subkind"); +} + +/// Map a SymbolLanguage to a indexstore_symbol_language_t. +indexstore_symbol_language_t index::getIndexStoreLang(SymbolLanguage L) { + switch (L) { + case SymbolLanguage::C: + return INDEXSTORE_SYMBOL_LANG_C; + case SymbolLanguage::ObjC: + return INDEXSTORE_SYMBOL_LANG_OBJC; + case SymbolLanguage::CXX: + return INDEXSTORE_SYMBOL_LANG_CXX; + case SymbolLanguage::Swift: + return INDEXSTORE_SYMBOL_LANG_SWIFT; + } + llvm_unreachable("unexpected symbol language"); +} + +/// Map a SymbolPropertySet to its indexstore representation. +uint64_t index::getIndexStoreProperties(SymbolPropertySet Props) { + uint64_t storeProp = 0; + applyForEachSymbolProperty(Props, [&](SymbolProperty prop) { + switch (prop) { + case SymbolProperty::Generic: + storeProp |= INDEXSTORE_SYMBOL_PROPERTY_GENERIC; + break; + case SymbolProperty::TemplatePartialSpecialization: + storeProp |= INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_PARTIAL_SPECIALIZATION; + break; + case SymbolProperty::TemplateSpecialization: + storeProp |= INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_SPECIALIZATION; + break; + case SymbolProperty::UnitTest: + storeProp |= INDEXSTORE_SYMBOL_PROPERTY_UNITTEST; + break; + case SymbolProperty::IBAnnotated: + storeProp |= INDEXSTORE_SYMBOL_PROPERTY_IBANNOTATED; + break; + case SymbolProperty::IBOutletCollection: + storeProp |= INDEXSTORE_SYMBOL_PROPERTY_IBOUTLETCOLLECTION; + break; + case SymbolProperty::GKInspectable: + storeProp |= INDEXSTORE_SYMBOL_PROPERTY_GKINSPECTABLE; + break; + case SymbolProperty::Local: + storeProp |= INDEXSTORE_SYMBOL_PROPERTY_LOCAL; + break; + } + }); + return storeProp; +} + +/// Map a SymbolRoleSet to its indexstore representation. +uint64_t index::getIndexStoreRoles(SymbolRoleSet Roles) { + uint64_t storeRoles = 0; + applyForEachSymbolRole(Roles, [&](SymbolRole role) { + switch (role) { + case SymbolRole::Declaration: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_DECLARATION; + break; + case SymbolRole::Definition: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_DEFINITION; + break; + case SymbolRole::Reference: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REFERENCE; + break; + case SymbolRole::Read: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_READ; + break; + case SymbolRole::Write: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_WRITE; + break; + case SymbolRole::Call: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_CALL; + break; + case SymbolRole::Dynamic: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_DYNAMIC; + break; + case SymbolRole::AddressOf: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_ADDRESSOF; + break; + case SymbolRole::Implicit: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_IMPLICIT; + break; + case SymbolRole::RelationChildOf: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_CHILDOF; + break; + case SymbolRole::RelationBaseOf: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_BASEOF; + break; + case SymbolRole::RelationOverrideOf: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_OVERRIDEOF; + break; + case SymbolRole::RelationReceivedBy: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_RECEIVEDBY; + break; + case SymbolRole::RelationCalledBy: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_CALLEDBY; + break; + case SymbolRole::RelationExtendedBy: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_EXTENDEDBY; + break; + case SymbolRole::RelationAccessorOf: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_ACCESSOROF; + break; + case SymbolRole::RelationContainedBy: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_CONTAINEDBY; + break; + case SymbolRole::RelationIBTypeOf: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_IBTYPEOF; + break; + case SymbolRole::RelationSpecializationOf: + storeRoles |= INDEXSTORE_SYMBOL_ROLE_REL_SPECIALIZATIONOF; + break; + } + }); + return storeRoles; +} diff --git a/clang/lib/Index/IndexDataStoreUtils.h b/clang/lib/Index/IndexDataStoreUtils.h new file mode 100644 index 0000000000000..ad310e191605e --- /dev/null +++ b/clang/lib/Index/IndexDataStoreUtils.h @@ -0,0 +1,116 @@ +//===--- IndexDataStoreUtils.h - Functions/constants for the data store ---===// +// +// 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_INDEX_INDEXDATASTOREUTILS_H +#define LLVM_CLANG_LIB_INDEX_INDEXDATASTOREUTILS_H + +#include "llvm/Bitcode/BitCodes.h" +#include "clang/Basic/LLVM.h" + +namespace llvm { + class BitstreamWriter; +} + +namespace clang { +namespace index { +namespace store { + +static const unsigned STORE_FORMAT_VERSION = 5; + +void appendUnitSubDir(SmallVectorImpl &StorePathBuf); +void appendInteriorUnitPath(StringRef UnitName, + SmallVectorImpl &PathBuf); +void appendRecordSubDir(SmallVectorImpl &StorePathBuf); +void appendInteriorRecordPath(StringRef RecordName, + SmallVectorImpl &PathBuf); + +enum RecordBitRecord { + REC_VERSION = 0, + REC_DECLINFO = 1, + REC_DECLOFFSETS = 2, + REC_DECLOCCURRENCE = 3, +}; + +enum RecordBitBlock { + REC_VERSION_BLOCK_ID = llvm::bitc::FIRST_APPLICATION_BLOCKID, + REC_DECLS_BLOCK_ID, + REC_DECLOFFSETS_BLOCK_ID, + REC_DECLOCCURRENCES_BLOCK_ID, +}; + +enum UnitBitRecord { + UNIT_VERSION = 0, + UNIT_INFO = 1, + UNIT_DEPENDENCY = 2, + UNIT_INCLUDE = 3, + UNIT_PATH = 4, + UNIT_PATH_BUFFER = 5, + UNIT_MODULE = 6, + UNIT_MODULE_BUFFER = 7, +}; + +enum UnitBitBlock { + UNIT_VERSION_BLOCK_ID = llvm::bitc::FIRST_APPLICATION_BLOCKID, + UNIT_INFO_BLOCK_ID, + UNIT_DEPENDENCIES_BLOCK_ID, + UNIT_INCLUDES_BLOCK_ID, + UNIT_PATHS_BLOCK_ID, + UNIT_MODULES_BLOCK_ID, +}; + +enum UnitDependencyKind { + UNIT_DEPEND_KIND_FILE = 0, + UNIT_DEPEND_KIND_RECORD = 1, + UNIT_DEPEND_KIND_UNIT = 2, +}; +static const unsigned UnitDependencyKindBitNum = 2; + +enum UnitFilePathPrefixKind { + UNIT_PATH_PREFIX_NONE = 0, + UNIT_PATH_PREFIX_WORKDIR = 1, + UNIT_PATH_PREFIX_SYSROOT = 2, +}; +static const unsigned UnitFilePathPrefixKindBitNum = 2; + +typedef SmallVector RecordData; +typedef SmallVectorImpl RecordDataImpl; + +struct BitPathComponent { + size_t Offset = 0; + size_t Size = 0; + BitPathComponent(size_t Offset, size_t Size) : Offset(Offset), Size(Size) {} + BitPathComponent() = default; +}; + +struct DirBitPath { + UnitFilePathPrefixKind PrefixKind = UNIT_PATH_PREFIX_NONE; + BitPathComponent Dir; + DirBitPath(UnitFilePathPrefixKind Kind, + BitPathComponent Dir) : PrefixKind(Kind), Dir(Dir) {} + DirBitPath() = default; +}; + +struct FileBitPath : DirBitPath { + BitPathComponent Filename; + FileBitPath(UnitFilePathPrefixKind Kind, BitPathComponent Dir, + BitPathComponent Filename) : DirBitPath(Kind, Dir), Filename(Filename) {} + FileBitPath() = default; +}; + +void emitBlockID(unsigned ID, const char *Name, + llvm::BitstreamWriter &Stream, RecordDataImpl &Record); + +void emitRecordID(unsigned ID, const char *Name, + llvm::BitstreamWriter &Stream, RecordDataImpl &Record); + +} // end namespace store +} // end namespace index +} // end namespace clang + +#endif diff --git a/clang/lib/Index/IndexRecordHasher.cpp b/clang/lib/Index/IndexRecordHasher.cpp new file mode 100644 index 0000000000000..dd3c11ddfc797 --- /dev/null +++ b/clang/lib/Index/IndexRecordHasher.cpp @@ -0,0 +1,468 @@ +//===--- IndexRecordHasher.cpp - Index record hashing ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IndexRecordHasher.h" +#include "FileIndexRecord.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclVisitor.h" +#include "llvm/Support/Path.h" + +#define INITIAL_HASH 5381 +#define COMBINE_HASH(...) (Hash = hash_combine(Hash, __VA_ARGS__)) + +using namespace clang; +using namespace clang::index; +using namespace llvm; + +static hash_code computeHash(const TemplateArgument &Arg, + IndexRecordHasher &Hasher); + +namespace { +class DeclHashVisitor : public ConstDeclVisitor { + IndexRecordHasher &Hasher; + +public: + DeclHashVisitor(IndexRecordHasher &Hasher) : Hasher(Hasher) {} + + hash_code VisitDecl(const Decl *D) { + return VisitDeclContext(D->getDeclContext()); + } + + hash_code VisitNamedDecl(const NamedDecl *D) { + hash_code Hash = VisitDecl(D); + if (auto *attr = D->getExternalSourceSymbolAttr()) { + COMBINE_HASH(hash_value(attr->getDefinedIn())); + } + return COMBINE_HASH(Hasher.hash(D->getDeclName())); + } + + hash_code VisitTagDecl(const TagDecl *D) { + if (D->getDeclName().isEmpty()) { + if (const TypedefNameDecl *TD = D->getTypedefNameForAnonDecl()) + return Visit(TD); + + hash_code Hash = VisitDeclContext(D->getDeclContext()); + if (D->isEmbeddedInDeclarator() && !D->isFreeStanding()) { + COMBINE_HASH(hashLoc(D->getLocation(), /*IncludeOffset=*/true)); + } else + COMBINE_HASH('a'); + return Hash; + } + + hash_code Hash = VisitTypeDecl(D); + return COMBINE_HASH('T'); + } + + hash_code VisitClassTemplateSpecializationDecl(const ClassTemplateSpecializationDecl *D) { + hash_code Hash = VisitCXXRecordDecl(D); + const TemplateArgumentList &Args = D->getTemplateArgs(); + COMBINE_HASH('>'); + for (unsigned I = 0, N = Args.size(); I != N; ++I) { + COMBINE_HASH(computeHash(Args.get(I), Hasher)); + } + return Hash; + } + + hash_code VisitObjCContainerDecl(const ObjCContainerDecl *D) { + hash_code Hash = VisitNamedDecl(D); + return COMBINE_HASH('I'); + } + + hash_code VisitObjCImplDecl(const ObjCImplDecl *D) { + if (auto *ID = D->getClassInterface()) + return VisitObjCInterfaceDecl(ID); + else + return 0; + } + + hash_code VisitObjCCategoryDecl(const ObjCCategoryDecl *D) { + // FIXME: Differentiate between category and the interface ? + if (auto *ID = D->getClassInterface()) + return VisitObjCInterfaceDecl(ID); + else + return 0; + } + + hash_code VisitFunctionDecl(const FunctionDecl *D) { + hash_code Hash = VisitNamedDecl(D); + ASTContext &Ctx = Hasher.getASTContext(); + if ((!Ctx.getLangOpts().CPlusPlus && !D->hasAttr()) + || D->isExternC()) + return Hash; + + for (auto param : D->parameters()) { + COMBINE_HASH(Hasher.hash(param->getType())); + } + return Hash; + } + + hash_code VisitDeclContext(const DeclContext *DC) { + // FIXME: Add location if this is anonymous namespace ? + DC = DC->getRedeclContext(); + const Decl *D = cast(DC)->getCanonicalDecl(); + if (auto *ND = dyn_cast(D)) + return Hasher.hash(ND); + else + return 0; + } + + hash_code hashLoc(SourceLocation Loc, bool IncludeOffset) { + if (Loc.isInvalid()) { + return 0; + } + hash_code Hash = INITIAL_HASH; + const SourceManager &SM = Hasher.getASTContext().getSourceManager(); + Loc = SM.getFileLoc(Loc); + const std::pair &Decomposed = SM.getDecomposedLoc(Loc); + const FileEntry *FE = SM.getFileEntryForID(Decomposed.first); + if (FE) { + COMBINE_HASH(llvm::sys::path::filename(FE->getName())); + } else { + // This case really isn't interesting. + return 0; + } + if (IncludeOffset) { + // Use the offest into the FileID to represent the location. Using + // a line/column can cause us to look back at the original source file, + // which is expensive. + COMBINE_HASH(Decomposed.second); + } + return Hash; + } +}; +} + +hash_code IndexRecordHasher::hashRecord(const FileIndexRecord &Record) { + hash_code Hash = INITIAL_HASH; + for (auto &Info : Record.getDeclOccurrences()) { + COMBINE_HASH(Info.Roles, Info.Offset, hash(Info.Dcl)); + for (auto &Rel : Info.Relations) { + COMBINE_HASH(hash(Rel.RelatedSymbol)); + } + } + return Hash; +} + +hash_code IndexRecordHasher::hash(const Decl *D) { + assert(D->isCanonicalDecl()); + + if (isa(D) || isa(D)) { + return tryCache(D, D); + } else if (auto *NS = dyn_cast(D)) { + if (NS->isAnonymousNamespace()) + return hash_value(StringRef("@aN")); + return tryCache(D, D); + } else { + // There's a balance between caching results and not growing the cache too + // much. Measurements showed that avoiding caching all decls is beneficial + // particularly when including all of Cocoa. + return hashImpl(D); + } +} + +hash_code IndexRecordHasher::hash(QualType NonCanTy) { + CanQualType CanTy = Ctx.getCanonicalType(NonCanTy); + return hash(CanTy); +} + +hash_code IndexRecordHasher::hash(CanQualType CT) { + // Do some hashing without going to the cache, for example we can avoid + // storing the hash for both the type and its const-qualified version. + hash_code Hash = INITIAL_HASH; + + auto asCanon = [](QualType Ty) -> CanQualType { + return CanQualType::CreateUnsafe(Ty); + }; + + while (true) { + Qualifiers Q = CT.getQualifiers(); + CT = CT.getUnqualifiedType(); + const Type *T = CT.getTypePtr(); + unsigned qVal = 0; + if (Q.hasConst()) + qVal |= 0x1; + if (Q.hasVolatile()) + qVal |= 0x2; + if (Q.hasRestrict()) + qVal |= 0x4; + if(qVal) + COMBINE_HASH(qVal); + + // Hash in ObjC GC qualifiers? + + if (const BuiltinType *BT = dyn_cast(T)) { + return COMBINE_HASH(BT->getKind()); + } + if (const PointerType *PT = dyn_cast(T)) { + COMBINE_HASH('*'); + CT = asCanon(PT->getPointeeType()); + continue; + } + if (const ReferenceType *RT = dyn_cast(T)) { + COMBINE_HASH('&'); + CT = asCanon(RT->getPointeeType()); + continue; + } + if (const BlockPointerType *BT = dyn_cast(T)) { + COMBINE_HASH('B'); + CT = asCanon(BT->getPointeeType()); + continue; + } + if (const ObjCObjectPointerType *OPT = dyn_cast(T)) { + COMBINE_HASH('*'); + CT = asCanon(OPT->getPointeeType()); + continue; + } + if (const TagType *TT = dyn_cast(T)) { + return COMBINE_HASH('$', hash(TT->getDecl()->getCanonicalDecl())); + } + if (const ObjCInterfaceType *OIT = dyn_cast(T)) { + return COMBINE_HASH('$', hash(OIT->getDecl()->getCanonicalDecl())); + } + if (const ObjCObjectType *OIT = dyn_cast(T)) { + for (auto *Prot : OIT->getProtocols()) + COMBINE_HASH(hash(Prot)); + CT = asCanon(OIT->getBaseType()); + continue; + } + if (const TemplateTypeParmType *TTP = dyn_cast(T)) { + return COMBINE_HASH('t', TTP->getDepth(), TTP->getIndex()); + } + if (const InjectedClassNameType *InjT = dyn_cast(T)) { + CT = asCanon(InjT->getInjectedSpecializationType().getCanonicalType()); + continue; + } + + break; + } + + return COMBINE_HASH(tryCache(CT.getAsOpaquePtr(), CT)); +} + +hash_code IndexRecordHasher::hash(DeclarationName Name) { + assert(!Name.isEmpty()); + // Measurements for using cache or not here, showed significant slowdown when + // using the cache for all DeclarationNames when parsing Cocoa, and minor + // improvement or no difference for a couple of C++ single translation unit + // files. So we avoid caching DeclarationNames. + return hashImpl(Name); +} + +hash_code IndexRecordHasher::hash(const NestedNameSpecifier *NNS) { + assert(NNS); + // Measurements for the C++ single translation unit files did not show much + // difference here; choosing to cache them currently. + return tryCache(NNS, NNS); +} + +template +hash_code IndexRecordHasher::tryCache(const void *Ptr, T Obj) { + auto It = HashByPtr.find(Ptr); + if (It != HashByPtr.end()) + return It->second; + + hash_code Hash = hashImpl(Obj); + // hashImpl() may call into tryCache recursively and mutate + // HashByPtr, so we use find() earlier and insert the hash with another + // lookup here instead of calling insert() earlier and utilizing the iterator + // that insert() returns. + HashByPtr[Ptr] = Hash; + return Hash; +} + +hash_code IndexRecordHasher::hashImpl(const Decl *D) { + return DeclHashVisitor(*this).Visit(D); +} + +static hash_code computeHash(const IdentifierInfo *II) { + return hash_value(II->getName()); +} + +static hash_code computeHash(Selector Sel) { + unsigned N = Sel.getNumArgs(); + if (N == 0) + ++N; + hash_code Hash = INITIAL_HASH; + for (unsigned I = 0; I != N; ++I) + if (IdentifierInfo *II = Sel.getIdentifierInfoForSlot(I)) + COMBINE_HASH(computeHash(II)); + return Hash; +} + +static hash_code computeHash(TemplateName Name, IndexRecordHasher &Hasher) { + hash_code Hash = INITIAL_HASH; + if (TemplateDecl *Template = Name.getAsTemplateDecl()) { + if (TemplateTemplateParmDecl *TTP + = dyn_cast(Template)) { + return COMBINE_HASH('t', TTP->getDepth(), TTP->getIndex()); + } + + return COMBINE_HASH(Hasher.hash(Template)); + } + + // FIXME: Hash dependent template names. + return Hash; +} + +static hash_code computeHash(const TemplateArgument &Arg, + IndexRecordHasher &Hasher) { + hash_code Hash = INITIAL_HASH; + + switch (Arg.getKind()) { + case TemplateArgument::Null: + break; + + case TemplateArgument::Declaration: + COMBINE_HASH(Hasher.hash(Arg.getAsDecl())); + break; + + case TemplateArgument::NullPtr: + break; + + case TemplateArgument::TemplateExpansion: + COMBINE_HASH('P'); // pack expansion of... + // Fall through + case TemplateArgument::Template: + COMBINE_HASH(computeHash(Arg.getAsTemplateOrTemplatePattern(), Hasher)); + break; + + case TemplateArgument::Expression: + // FIXME: Hash expressions. + break; + + case TemplateArgument::Pack: + COMBINE_HASH('p'); + for (const auto &P : Arg.pack_elements()) + COMBINE_HASH(computeHash(P, Hasher)); + break; + + case TemplateArgument::Type: + COMBINE_HASH(Hasher.hash(Arg.getAsType())); + break; + + case TemplateArgument::Integral: + COMBINE_HASH('V', Hasher.hash(Arg.getIntegralType()), Arg.getAsIntegral()); + break; + } + + return Hash; +} + +hash_code IndexRecordHasher::hashImpl(CanQualType CQT) { + hash_code Hash = INITIAL_HASH; + + auto asCanon = [](QualType Ty) -> CanQualType { + return CanQualType::CreateUnsafe(Ty); + }; + + const Type *T = CQT.getTypePtr(); + + if (const PackExpansionType *Expansion = dyn_cast(T)) { + return COMBINE_HASH('P', hash(asCanon(Expansion->getPattern()))); + } + if (const RValueReferenceType *RT = dyn_cast(T)) { + return COMBINE_HASH('%', hash(asCanon(RT->getPointeeType()))); + } + if (const FunctionProtoType *FT = dyn_cast(T)) { + COMBINE_HASH('F', hash(asCanon(FT->getReturnType()))); + for (const auto &I : FT->param_types()) + COMBINE_HASH(hash(asCanon(I))); + return COMBINE_HASH(FT->isVariadic()); + } + if (const ComplexType *CT = dyn_cast(T)) { + return COMBINE_HASH('<', hash(asCanon(CT->getElementType()))); + } + if (const TemplateSpecializationType *Spec + = dyn_cast(T)) { + COMBINE_HASH('>', computeHash(Spec->getTemplateName(), *this)); + for (unsigned I = 0, N = Spec->getNumArgs(); I != N; ++I) + COMBINE_HASH(computeHash(Spec->getArg(I), *this)); + return Hash; + } + if (const DependentNameType *DNT = dyn_cast(T)) { + COMBINE_HASH('^'); + if (const NestedNameSpecifier *NNS = DNT->getQualifier()) + COMBINE_HASH(hash(NNS)); + return COMBINE_HASH(computeHash(DNT->getIdentifier())); + } + + // Unhandled type. + return Hash; +} + +hash_code IndexRecordHasher::hashImpl(DeclarationName Name) { + hash_code Hash = INITIAL_HASH; + COMBINE_HASH(Name.getNameKind()); + + switch (Name.getNameKind()) { + case DeclarationName::Identifier: + COMBINE_HASH(computeHash(Name.getAsIdentifierInfo())); + break; + case DeclarationName::ObjCZeroArgSelector: + case DeclarationName::ObjCOneArgSelector: + case DeclarationName::ObjCMultiArgSelector: + COMBINE_HASH(computeHash(Name.getObjCSelector())); + break; + case DeclarationName::CXXConstructorName: + case DeclarationName::CXXDestructorName: + case DeclarationName::CXXConversionFunctionName: + break; + case DeclarationName::CXXOperatorName: + COMBINE_HASH(Name.getCXXOverloadedOperator()); + break; + case DeclarationName::CXXLiteralOperatorName: + COMBINE_HASH(computeHash(Name.getCXXLiteralIdentifier())); + case DeclarationName::CXXUsingDirective: + break; + case DeclarationName::CXXDeductionGuideName: + COMBINE_HASH(computeHash(Name.getCXXDeductionGuideTemplate() + ->getDeclName().getAsIdentifierInfo())); + break; + } + + return Hash; +} + +hash_code IndexRecordHasher::hashImpl(const NestedNameSpecifier *NNS) { + hash_code Hash = INITIAL_HASH; + if (auto *Pre = NNS->getPrefix()) + COMBINE_HASH(hash(Pre)); + + COMBINE_HASH(NNS->getKind()); + + switch (NNS->getKind()) { + case NestedNameSpecifier::Identifier: + COMBINE_HASH(computeHash(NNS->getAsIdentifier())); + break; + + case NestedNameSpecifier::Namespace: + COMBINE_HASH(hash(NNS->getAsNamespace()->getCanonicalDecl())); + break; + + case NestedNameSpecifier::NamespaceAlias: + COMBINE_HASH(hash(NNS->getAsNamespaceAlias()->getCanonicalDecl())); + break; + + case NestedNameSpecifier::Global: + break; + + case NestedNameSpecifier::Super: + break; + + case NestedNameSpecifier::TypeSpecWithTemplate: + // Fall through to hash the type. + + case NestedNameSpecifier::TypeSpec: + COMBINE_HASH(hash(QualType(NNS->getAsType(), 0))); + break; + } + + return Hash; +} diff --git a/clang/lib/Index/IndexRecordHasher.h b/clang/lib/Index/IndexRecordHasher.h new file mode 100644 index 0000000000000..af3acccff5296 --- /dev/null +++ b/clang/lib/Index/IndexRecordHasher.h @@ -0,0 +1,58 @@ +//===--- IndexRecordHasher.h - Index record hashing -----------------------===// +// +// 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_INDEX_INDEXRECORDHASHER_H +#define LLVM_CLANG_LIB_INDEX_INDEXRECORDHASHER_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Hashing.h" + +namespace clang { + class ASTContext; + class Decl; + class DeclarationName; + class NestedNameSpecifier; + class QualType; + class Type; + template class CanQual; + typedef CanQual CanQualType; + +namespace index { + class FileIndexRecord; + +class IndexRecordHasher { + ASTContext &Ctx; + llvm::DenseMap HashByPtr; + +public: + explicit IndexRecordHasher(ASTContext &Ctx) : Ctx(Ctx) {} + ASTContext &getASTContext() { return Ctx; } + + llvm::hash_code hashRecord(const FileIndexRecord &Record); + llvm::hash_code hash(const Decl *D); + llvm::hash_code hash(QualType Ty); + llvm::hash_code hash(CanQualType Ty); + llvm::hash_code hash(DeclarationName Name); + llvm::hash_code hash(const NestedNameSpecifier *NNS); + +private: + template + llvm::hash_code tryCache(const void *Ptr, T Obj); + + llvm::hash_code hashImpl(const Decl *D); + llvm::hash_code hashImpl(CanQualType Ty); + llvm::hash_code hashImpl(DeclarationName Name); + llvm::hash_code hashImpl(const NestedNameSpecifier *NNS); +}; + +} // end namespace index +} // end namespace clang + +#endif diff --git a/clang/lib/Index/IndexRecordReader.cpp b/clang/lib/Index/IndexRecordReader.cpp new file mode 100644 index 0000000000000..bd2ec1f571dba --- /dev/null +++ b/clang/lib/Index/IndexRecordReader.cpp @@ -0,0 +1,407 @@ +//===--- IndexRecordReader.cpp - Index record deserialization -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Index/IndexRecordReader.h" +#include "IndexDataStoreUtils.h" +#include "BitstreamVisitor.h" +#include "clang/Index/IndexDataStoreSymbolUtils.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Bitcode/BitstreamReader.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::index; +using namespace clang::index::store; +using namespace llvm; + +struct IndexRecordReader::Implementation { + BumpPtrAllocator Allocator; + std::unique_ptr Buffer; + llvm::BitstreamCursor DeclCursor; + llvm::BitstreamCursor OccurCursor; + ArrayRef DeclOffsets; + const IndexRecordDecl **Decls; + + void setDeclOffsets(ArrayRef Offs) { + DeclOffsets = Offs; + Decls = Allocator.Allocate(Offs.size()); + memset(Decls, 0, sizeof(IndexRecordDecl*)*Offs.size()); + } + + unsigned getNumDecls() const { return DeclOffsets.size(); } + + const IndexRecordDecl *getDeclByID(unsigned DeclID) { + if (DeclID == 0) + return nullptr; + return getDecl(DeclID-1); + } + + const IndexRecordDecl *getDecl(unsigned Index) { + assert(Index < getNumDecls()); + if (const IndexRecordDecl *D = Decls[Index]) + return D; + + IndexRecordDecl *D = Allocator.Allocate(); + readDecl(Index, *D); + Decls[Index] = D; + return D; + } + + /// Goes through the decls and populates a vector of record decls, based on + /// what the given function returns. + /// + /// The advantage of this function is to allocate memory only for the record + /// decls that the caller is interested in. + bool searchDecls(llvm::function_ref Checker, + llvm::function_ref Receiver) { + for (unsigned I = 0, E = getNumDecls(); I != E; ++I) { + if (const IndexRecordDecl *D = Decls[I]) { + DeclSearchReturn Ret = Checker(*D); + if (Ret.AcceptDecl) + Receiver(D); + if (!Ret.ContinueSearch) + return false; + continue; + } + + IndexRecordDecl LocalD; + readDecl(I, LocalD); + DeclSearchReturn Ret = Checker(LocalD); + if (Ret.AcceptDecl) { + IndexRecordDecl *D = Allocator.Allocate(); + *D = LocalD; + Decls[I] = D; + Receiver(D); + } + if (!Ret.ContinueSearch) + return false; + } + return true; + } + + void readDecl(unsigned Index, IndexRecordDecl &RecD) { + RecordData Record; + StringRef Blob; + DeclCursor.JumpToBit(DeclOffsets[Index]); + unsigned Code = DeclCursor.ReadCode(); + unsigned RecID = DeclCursor.readRecord(Code, Record, &Blob); + assert(RecID == REC_DECLINFO); + (void)RecID; + + unsigned I = 0; + RecD.DeclID = Index+1; + RecD.SymInfo.Kind = getSymbolKind((indexstore_symbol_kind_t)read(Record, I)); + RecD.SymInfo.SubKind = getSymbolSubKind((indexstore_symbol_subkind_t)read(Record, I)); + RecD.SymInfo.Lang = + getSymbolLanguage((indexstore_symbol_language_t)read(Record, I)); + RecD.SymInfo.Properties = getSymbolProperties(read(Record, I)); + RecD.Roles = getSymbolRoles(read(Record, I)); + RecD.RelatedRoles = getSymbolRoles(read(Record, I)); + size_t NameLen = read(Record, I); + size_t USRLen = read(Record, I); + RecD.Name = Blob.substr(0, NameLen); + RecD.USR = Blob.substr(NameLen, USRLen); + RecD.CodeGenName = Blob.substr(NameLen+USRLen); + } + + /// Reads occurrence data. + /// \param DeclsFilter if non-empty indicates the list of decls that we want + /// to get occurrences for. If empty then indicates that we want occurrences + /// for all decls. + /// \param RelatedDeclsFilter Same as \c DeclsFilter but for related decls. + /// \returns true if the occurrence info was filled out, false if occurrence + /// was ignored. + bool readOccurrence(RecordDataImpl &Record, StringRef Blob, + ArrayRef DeclsFilter, + ArrayRef RelatedDeclsFilter, + IndexRecordOccurrence &RecOccur) { + + auto isDeclIDContained = [](unsigned DeclID, + ArrayRef Ds) -> bool { + if (Ds.empty()) + return true; // empty means accept all. + auto pred = [DeclID](const IndexRecordDecl *D) { return D->DeclID == DeclID; }; + return std::find_if(Ds.begin(), Ds.end(), pred) != Ds.end(); + }; + + unsigned I = 0; + unsigned DeclID = read(Record, I); + if (!isDeclIDContained(DeclID, DeclsFilter)) + return false; + + if (!RelatedDeclsFilter.empty()) { + unsigned RelI = I+3; + unsigned NumRelated = read(Record, RelI); + bool FoundRelated = false; + while (NumRelated--) { + ++RelI; // roles; + unsigned RelDID = read(Record, RelI); + if (isDeclIDContained(RelDID, RelatedDeclsFilter)) { + FoundRelated = true; + break; + } + } + if (!FoundRelated) + return false; + } + + RecOccur.Dcl = getDeclByID(DeclID); + RecOccur.Roles = getSymbolRoles(read(Record, I)); + RecOccur.Line = read(Record, I); + RecOccur.Column = read(Record, I); + + unsigned NumRelated = read(Record, I); + while (NumRelated--) { + SymbolRoleSet RelRoles = getSymbolRoles(read(Record, I)); + const IndexRecordDecl *RelD = getDeclByID(read(Record, I)); + RecOccur.Relations.emplace_back(RelRoles, RelD); + } + + return true; + } + + bool foreachDecl(bool NoCache, + function_ref Receiver) { + for (unsigned I = 0, E = getNumDecls(); I != E; ++I) { + if (const IndexRecordDecl *D = Decls[I]) { + if (!Receiver(D)) + return false; + continue; + } + + if (NoCache) { + IndexRecordDecl LocalD; + readDecl(I, LocalD); + if (!Receiver(&LocalD)) + return false; + } else { + if (!Receiver(getDecl(I))) + return false; + } + } + return true; + } + + bool foreachOccurrence(ArrayRef DeclsFilter, + ArrayRef RelatedDeclsFilter, + function_ref Receiver) { + class OccurBitVisitor : public BitstreamVisitor { + IndexRecordReader::Implementation &Reader; + ArrayRef DeclsFilter; + ArrayRef RelatedDeclsFilter; + function_ref Receiver; + + public: + OccurBitVisitor(llvm::BitstreamCursor &Stream, + IndexRecordReader::Implementation &Reader, + ArrayRef DeclsFilter, + ArrayRef RelatedDeclsFilter, + function_ref Receiver) + : BitstreamVisitor(Stream), + Reader(Reader), + DeclsFilter(DeclsFilter), + RelatedDeclsFilter(RelatedDeclsFilter), + Receiver(std::move(Receiver)) {} + + StreamVisit visitRecord(unsigned BlockID, unsigned RecID, + RecordDataImpl &Record, StringRef Blob) { + assert(RecID == REC_DECLOCCURRENCE); + IndexRecordOccurrence RecOccur; + if (Reader.readOccurrence(Record, Blob, DeclsFilter, RelatedDeclsFilter, + RecOccur)) + if (!Receiver(RecOccur)) + return StreamVisit::Abort; + return StreamVisit::Continue; + } + }; + + SavedStreamPosition SavedPosition(OccurCursor); + OccurBitVisitor Visitor(OccurCursor, *this, DeclsFilter, RelatedDeclsFilter, + Receiver); + std::string Error; + return Visitor.visit(Error); + } + + bool foreachOccurrenceInLineRange(unsigned lineStart, unsigned lineCount, + llvm::function_ref receiver) { + // FIXME: Use binary search and make this more efficient. + unsigned lineEnd = lineStart+lineCount; + return foreachOccurrence(None, None, [&](const IndexRecordOccurrence &occur) -> bool { + if (occur.Line > lineEnd) + return false; // we're done. + if (occur.Line >= lineStart) { + if (!receiver(occur)) + return false; + } + return true; + }); + } + + static uint64_t read(RecordDataImpl &Record, unsigned &I) { + return Record[I++]; + } +}; + +namespace { + +class IndexBitstreamVisitor : public BitstreamVisitor { + IndexRecordReader::Implementation &Reader; + +public: + IndexBitstreamVisitor(llvm::BitstreamCursor &Stream, + IndexRecordReader::Implementation &Reader) + : BitstreamVisitor(Stream), Reader(Reader) {} + + StreamVisit visitBlock(unsigned ID) { + switch ((RecordBitBlock)ID) { + case REC_VERSION_BLOCK_ID: + case REC_DECLOFFSETS_BLOCK_ID: + return StreamVisit::Continue; + + case REC_DECLS_BLOCK_ID: + Reader.DeclCursor = Stream; + if (Reader.DeclCursor.EnterSubBlock(ID)) { + *Error = "malformed block record"; + return StreamVisit::Abort; + } + readBlockAbbrevs(Reader.DeclCursor); + return StreamVisit::Skip; + + case REC_DECLOCCURRENCES_BLOCK_ID: + Reader.OccurCursor = Stream; + if (Reader.OccurCursor.EnterSubBlock(ID)) { + *Error = "malformed block record"; + return StreamVisit::Abort; + } + readBlockAbbrevs(Reader.OccurCursor); + return StreamVisit::Skip; + } + + // Some newly introduced block in a minor version update that we cannot + // handle. + return StreamVisit::Skip; + } + + StreamVisit visitRecord(unsigned BlockID, unsigned RecID, + RecordDataImpl &Record, StringRef Blob) { + switch (BlockID) { + case REC_VERSION_BLOCK_ID: { + unsigned StoreFormatVersion = Record[0]; + if (StoreFormatVersion != STORE_FORMAT_VERSION) { + llvm::raw_string_ostream OS(*Error); + OS << "Store format version mismatch: " << StoreFormatVersion; + OS << " , expected: " << STORE_FORMAT_VERSION; + return StreamVisit::Abort; + } + break; + } + case REC_DECLOFFSETS_BLOCK_ID: + assert(RecID == REC_DECLOFFSETS); + Reader.setDeclOffsets(makeArrayRef((uint32_t*)Blob.data(), Record[0])); + break; + + case REC_DECLS_BLOCK_ID: + case REC_DECLOCCURRENCES_BLOCK_ID: + llvm_unreachable("shouldn't visit this block'"); + } + return StreamVisit::Continue; + } +}; + +} // anonymous namespace + +std::unique_ptr +IndexRecordReader::createWithRecordFilename(StringRef RecordFilename, + StringRef StorePath, + std::string &Error) { + SmallString<128> PathBuf = StorePath; + appendRecordSubDir(PathBuf); + appendInteriorRecordPath(RecordFilename, PathBuf); + return createWithFilePath(PathBuf.str(), Error); +} + +std::unique_ptr +IndexRecordReader::createWithFilePath(StringRef FilePath, std::string &Error) { + auto ErrOrBuf = MemoryBuffer::getFile(FilePath, /*FileSize=*/-1, + /*RequiresNullTerminator=*/false); + if (!ErrOrBuf) { + raw_string_ostream(Error) << "failed opening index record '" + << FilePath << "': " << ErrOrBuf.getError().message(); + return nullptr; + } + return createWithBuffer(std::move(*ErrOrBuf), Error); +} + +std::unique_ptr +IndexRecordReader::createWithBuffer(std::unique_ptr Buffer, + std::string &Error) { + + std::unique_ptr Reader; + Reader.reset(new IndexRecordReader()); + auto &Impl = Reader->Impl; + Impl.Buffer = std::move(Buffer); + llvm::BitstreamCursor Stream(*Impl.Buffer); + + // Sniff for the signature. + if (Stream.Read(8) != 'I' || + Stream.Read(8) != 'D' || + Stream.Read(8) != 'X' || + Stream.Read(8) != 'R') { + Error = "not a serialized index record file"; + return nullptr; + } + + IndexBitstreamVisitor BitVisitor(Stream, Impl); + if (!BitVisitor.visit(Error)) + return nullptr; + + return Reader; +} + +IndexRecordReader::IndexRecordReader() + : Impl(*new Implementation()) { + +} + +IndexRecordReader::~IndexRecordReader() { + delete &Impl; +} + +bool IndexRecordReader::searchDecls( + llvm::function_ref Checker, + llvm::function_ref Receiver) { + return Impl.searchDecls(std::move(Checker), std::move(Receiver)); +} + +bool IndexRecordReader::foreachDecl(bool NoCache, + function_ref Receiver) { + return Impl.foreachDecl(NoCache, std::move(Receiver)); +} + +bool IndexRecordReader::foreachOccurrence( + ArrayRef DeclsFilter, + ArrayRef RelatedDeclsFilter, + function_ref Receiver) { + return Impl.foreachOccurrence(DeclsFilter, RelatedDeclsFilter, + std::move(Receiver)); +} + +bool IndexRecordReader::foreachOccurrence( + llvm::function_ref Receiver) { + return foreachOccurrence(None, None, std::move(Receiver)); +} + +bool IndexRecordReader::foreachOccurrenceInLineRange(unsigned lineStart, + unsigned lineCount, + llvm::function_ref Receiver) { + return Impl.foreachOccurrenceInLineRange(lineStart, lineCount, Receiver); +} diff --git a/clang/lib/Index/IndexRecordWriter.cpp b/clang/lib/Index/IndexRecordWriter.cpp new file mode 100644 index 0000000000000..c4e6d50716a96 --- /dev/null +++ b/clang/lib/Index/IndexRecordWriter.cpp @@ -0,0 +1,366 @@ +//===--- IndexRecordWriter.cpp - Index record serialization ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Index/IndexRecordWriter.h" +#include "IndexDataStoreUtils.h" +#include "indexstore/indexstore.h" +#include "clang/Index/IndexDataStoreSymbolUtils.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Bitcode/BitstreamWriter.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::index; +using namespace clang::index::store; +using namespace llvm; + +using writer::OpaqueDecl; + +namespace { +struct DeclInfo { + OpaqueDecl D; + SymbolRoleSet Roles; + SymbolRoleSet RelatedRoles; +}; + +struct OccurrenceInfo { + unsigned DeclID; + OpaqueDecl D; + SymbolRoleSet Roles; + unsigned Line; + unsigned Column; + SmallVector, 4> Related; +}; + +struct RecordState { + std::string RecordPath; + SmallString<512> Buffer; + BitstreamWriter Stream; + + DenseMap IndexForDecl; + std::vector Decls; + std::vector Occurrences; + + RecordState(std::string &&RecordPath) + : RecordPath(std::move(RecordPath)), Stream(Buffer) {} +}; +} // end anonymous namespace + +static void writeBlockInfo(BitstreamWriter &Stream) { + RecordData Record; + + Stream.EnterBlockInfoBlock(); +#define BLOCK(X) emitBlockID(X ## _ID, #X, Stream, Record) +#define RECORD(X) emitRecordID(X, #X, Stream, Record) + + BLOCK(REC_VERSION_BLOCK); + RECORD(REC_VERSION); + + BLOCK(REC_DECLS_BLOCK); + RECORD(REC_DECLINFO); + + BLOCK(REC_DECLOFFSETS_BLOCK); + RECORD(REC_DECLOFFSETS); + + BLOCK(REC_DECLOCCURRENCES_BLOCK); + RECORD(REC_DECLOCCURRENCE); + +#undef RECORD +#undef BLOCK + Stream.ExitBlock(); +} + +static void writeVersionInfo(BitstreamWriter &Stream) { + using namespace llvm::sys; + + Stream.EnterSubblock(REC_VERSION_BLOCK_ID, 3); + + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(REC_VERSION)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Store format version + unsigned AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + + RecordData Record; + Record.push_back(REC_VERSION); + Record.push_back(STORE_FORMAT_VERSION); + Stream.EmitRecordWithAbbrev(AbbrevCode, Record); + + Stream.ExitBlock(); +} + +template +static StringRef data(const std::vector &v) { + if (v.empty()) + return StringRef(); + return StringRef(reinterpret_cast(&v[0]), sizeof(T) * v.size()); +} + +template static StringRef data(const SmallVectorImpl &v) { + return StringRef(reinterpret_cast(v.data()), + sizeof(T) * v.size()); +} + +static void writeDecls(BitstreamWriter &Stream, ArrayRef Decls, + ArrayRef Occurrences, + writer::SymbolWriterCallback GetSymbolForDecl) { + SmallVector DeclOffsets; + DeclOffsets.reserve(Decls.size()); + + //===--------------------------------------------------------------------===// + // DECLS_BLOCK_ID + //===--------------------------------------------------------------------===// + + Stream.EnterSubblock(REC_DECLS_BLOCK_ID, 3); + + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(REC_DECLINFO)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 5)); // Kind + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 5)); // SubKind + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 5)); // Language + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, SymbolPropertyBitNum)); // Properties + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, SymbolRoleBitNum)); // Roles + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, SymbolRoleBitNum)); // Related Roles + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Length of name in block + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Length of USR in block + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Name + USR + CodeGen symbol name + unsigned AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + +#ifndef NDEBUG + StringSet<> USRSet; +#endif + + RecordData Record; + llvm::SmallString<256> Blob; + llvm::SmallString<256> Scratch; + for (auto &Info : Decls) { + DeclOffsets.push_back(Stream.GetCurrentBitNo()); + Blob.clear(); + Scratch.clear(); + + writer::Symbol SymInfo = GetSymbolForDecl(Info.D, Scratch); + assert(SymInfo.SymInfo.Kind != SymbolKind::Unknown); + assert(!SymInfo.USR.empty() && "Recorded decl without USR!"); + + Blob += SymInfo.Name; + Blob += SymInfo.USR; + Blob += SymInfo.CodeGenName; + +#ifndef NDEBUG + bool IsNew = USRSet.insert(SymInfo.USR).second; + if (!IsNew) { + llvm::errs() << "Index: Duplicate USR! " << SymInfo.USR << "\n"; + // FIXME: print more information so it's easier to find the declaration. + } +#endif + + Record.clear(); + Record.push_back(REC_DECLINFO); + Record.push_back(getIndexStoreKind(SymInfo.SymInfo.Kind)); + Record.push_back(getIndexStoreSubKind(SymInfo.SymInfo.SubKind)); + Record.push_back(getIndexStoreLang(SymInfo.SymInfo.Lang)); + Record.push_back(getIndexStoreProperties(SymInfo.SymInfo.Properties)); + Record.push_back(getIndexStoreRoles(Info.Roles)); + Record.push_back(getIndexStoreRoles(Info.RelatedRoles)); + Record.push_back(SymInfo.Name.size()); + Record.push_back(SymInfo.USR.size()); + Stream.EmitRecordWithBlob(AbbrevCode, Record, Blob); + } + + Stream.ExitBlock(); + + //===--------------------------------------------------------------------===// + // DECLOFFSETS_BLOCK_ID + //===--------------------------------------------------------------------===// + + Stream.EnterSubblock(REC_DECLOFFSETS_BLOCK_ID, 3); + + Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(REC_DECLOFFSETS)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 32)); // Number of Decls + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Offsets array + AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + + Record.clear(); + Record.push_back(REC_DECLOFFSETS); + Record.push_back(DeclOffsets.size()); + Stream.EmitRecordWithBlob(AbbrevCode, Record, data(DeclOffsets)); + + Stream.ExitBlock(); + + //===--------------------------------------------------------------------===// + // DECLOCCURRENCES_BLOCK_ID + //===--------------------------------------------------------------------===// + + Stream.EnterSubblock(REC_DECLOCCURRENCES_BLOCK_ID, 3); + + Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(REC_DECLOCCURRENCE)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // Decl ID + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, SymbolRoleBitNum)); // Roles + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 12)); // Line + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // Column + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // Num related + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); // Related Roles/IDs + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 16)); // Roles or ID + AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + + for (auto &Occur : Occurrences) { + Record.clear(); + Record.push_back(REC_DECLOCCURRENCE); + Record.push_back(Occur.DeclID); + Record.push_back(getIndexStoreRoles(Occur.Roles)); + Record.push_back(Occur.Line); + Record.push_back(Occur.Column); + Record.push_back(Occur.Related.size()); + for (auto &Rel : Occur.Related) { + Record.push_back(getIndexStoreRoles(Rel.first.Roles)); + Record.push_back(Rel.second); + } + Stream.EmitRecordWithAbbrev(AbbrevCode, Record); + } + Stream.ExitBlock(); +} + +IndexRecordWriter::IndexRecordWriter(StringRef IndexPath) + : RecordsPath(IndexPath) { + store::appendRecordSubDir(RecordsPath); +} + +IndexRecordWriter::Result +IndexRecordWriter::beginRecord(StringRef Filename, hash_code RecordHash, + std::string &Error, std::string *OutRecordFile) { + using namespace llvm::sys; + assert(!Record && "called beginRecord before calling endRecord on previous"); + + std::string RecordName; + { + llvm::raw_string_ostream RN(RecordName); + RN << path::filename(Filename); + RN << "-" << APInt(64, RecordHash).toString(36, /*Signed=*/false); + } + SmallString<256> RecordPath = RecordsPath.str(); + appendInteriorRecordPath(RecordName, RecordPath); + + if (OutRecordFile) + *OutRecordFile = RecordName; + + if (std::error_code EC = + fs::access(RecordPath.c_str(), fs::AccessMode::Exist)) { + if (EC != errc::no_such_file_or_directory) { + llvm::raw_string_ostream Err(Error); + Err << "could not access record '" << RecordPath + << "': " << EC.message(); + return Result::Failure; + } + } else { + return Result::AlreadyExists; + } + + // Write the record header. + auto *State = new RecordState(RecordPath.str()); + Record = State; + llvm::BitstreamWriter &Stream = State->Stream; + Stream.Emit('I', 8); + Stream.Emit('D', 8); + Stream.Emit('X', 8); + Stream.Emit('R', 8); + + writeBlockInfo(Stream); + writeVersionInfo(Stream); + + return Result::Success; +} + +IndexRecordWriter::Result +IndexRecordWriter::endRecord(std::string &Error, + writer::SymbolWriterCallback GetSymbolForDecl) { + assert(Record && "called endRecord without calling beginRecord"); + auto &State = *static_cast(Record); + Record = nullptr; + struct ScopedDelete { + RecordState *S; + ScopedDelete(RecordState *S) : S(S) {} + ~ScopedDelete() { delete S; } + } Deleter(&State); + + if (!State.Decls.empty()) { + writeDecls(State.Stream, State.Decls, State.Occurrences, GetSymbolForDecl); + } + + if (std::error_code EC = sys::fs::create_directory(sys::path::parent_path(State.RecordPath))) { + llvm::raw_string_ostream Err(Error); + Err << "failed to create directory '" << sys::path::parent_path(State.RecordPath) << "': " << EC.message(); + return Result::Failure; + } + + // Create a unique file to write to so that we can move the result into place + // atomically. If this process crashes we don't want to interfere with any + // other concurrent processes. + SmallString<128> TempPath(State.RecordPath); + TempPath += "-temp-%%%%%%%%"; + int TempFD; + if (sys::fs::createUniqueFile(TempPath.str(), TempFD, TempPath)) { + llvm::raw_string_ostream Err(Error); + Err << "failed to create temporary file: " << TempPath; + return Result::Failure; + } + + raw_fd_ostream OS(TempFD, /*shouldClose=*/true); + OS.write(State.Buffer.data(), State.Buffer.size()); + OS.close(); + + // Atomically move the unique file into place. + if (std::error_code EC = + sys::fs::rename(TempPath.c_str(), State.RecordPath.c_str())) { + llvm::raw_string_ostream Err(Error); + Err << "failed to rename '" << TempPath << "' to '" << State.RecordPath << "': " << EC.message(); + return Result::Failure; + } + + return Result::Success; +} + +void IndexRecordWriter::addOccurrence( + OpaqueDecl D, SymbolRoleSet Roles, unsigned Line, unsigned Column, + ArrayRef Related) { + assert(Record && "called addOccurrence without calling beginRecord"); + auto &State = *static_cast(Record); + + auto insertDecl = [&](OpaqueDecl D, SymbolRoleSet Roles, + SymbolRoleSet RelatedRoles) -> unsigned { + auto Insert = + State.IndexForDecl.insert(std::make_pair(D, State.Decls.size())); + unsigned Index = Insert.first->second; + + if (Insert.second) { + State.Decls.push_back(DeclInfo{D, Roles, RelatedRoles}); + } else { + State.Decls[Index].Roles |= Roles; + State.Decls[Index].RelatedRoles |= RelatedRoles; + } + return Index + 1; + }; + + unsigned DeclID = insertDecl(D, Roles, SymbolRoleSet()); + + decltype(OccurrenceInfo::Related) RelatedDecls; + RelatedDecls.reserve(Related.size()); + for (auto &Rel : Related) { + unsigned ID = insertDecl(Rel.RelatedSymbol, SymbolRoleSet(), Rel.Roles); + RelatedDecls.emplace_back(Rel, ID); + } + + State.Occurrences.push_back( + OccurrenceInfo{DeclID, D, Roles, Line, Column, std::move(RelatedDecls)}); +} diff --git a/clang/lib/Index/IndexUnitReader.cpp b/clang/lib/Index/IndexUnitReader.cpp new file mode 100644 index 0000000000000..12a905616f16b --- /dev/null +++ b/clang/lib/Index/IndexUnitReader.cpp @@ -0,0 +1,516 @@ +//===--- IndexUnitReader.cpp - Index unit deserialization -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Index/IndexUnitReader.h" +#include "IndexDataStoreUtils.h" +#include "BitstreamVisitor.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Bitcode/BitstreamReader.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +#include + +using namespace clang; +using namespace clang::index; +using namespace clang::index::store; +using namespace llvm; + +namespace { + +typedef function_ref DependencyReceiver; +typedef function_ref IncludeReceiver; + +class IndexUnitReaderImpl { + sys::TimePoint<> ModTime; + std::unique_ptr MemBuf; + +public: + StringRef ProviderIdentifier; + StringRef ProviderVersion; + llvm::BitstreamCursor DependCursor; + llvm::BitstreamCursor IncludeCursor; + bool IsSystemUnit; + bool IsModuleUnit; + bool IsDebugCompilation; + StringRef WorkingDir; + StringRef OutputFile; + StringRef SysrootPath; + StringRef ModuleName; + SmallString<128> MainFilePath; + StringRef Target; + std::vector Paths; + StringRef PathsBuffer; + + struct ModuleInfo { + unsigned NameOffset; + unsigned NameSize; + }; + std::vector Modules; + StringRef ModuleNamesBuffer; + + bool init(std::unique_ptr Buf, sys::TimePoint<> ModTime, + std::string &Error); + + StringRef getProviderIdentifier() const { return ProviderIdentifier; } + StringRef getProviderVersion() const { return ProviderVersion; } + + sys::TimePoint<> getModificationTime() const { return ModTime; } + StringRef getWorkingDirectory() const { return WorkingDir; } + StringRef getOutputFile() const { return OutputFile; } + StringRef getSysrootPath() const { return SysrootPath; } + StringRef getTarget() const { return Target; } + + StringRef getModuleName() const { return ModuleName; } + StringRef getMainFilePath() const { return MainFilePath.str(); } + bool hasMainFile() const { return !MainFilePath.empty(); } + bool isSystemUnit() const { return IsSystemUnit; } + bool isModuleUnit() const { return IsModuleUnit; } + bool isDebugCompilation() const { return IsDebugCompilation; } + + /// Unit dependencies are provided ahead of record ones, record ones + /// ahead of the file ones. + bool foreachDependency(DependencyReceiver Receiver); + + bool foreachInclude(IncludeReceiver Receiver); + + StringRef getPathFromBuffer(size_t Offset, size_t Size) { + return PathsBuffer.substr(Offset, Size); + } + + void constructFilePath(SmallVectorImpl &Path, int PathIndex); + + StringRef getModuleName(int ModuleIndex); +}; + +class IndexUnitBitstreamVisitor : public BitstreamVisitor { + IndexUnitReaderImpl &Reader; + size_t WorkDirOffset; + size_t WorkDirSize; + size_t OutputFileOffset; + size_t OutputFileSize; + size_t SysrootOffset; + size_t SysrootSize; + int MainPathIndex; + +public: + IndexUnitBitstreamVisitor(llvm::BitstreamCursor &Stream, + IndexUnitReaderImpl &Reader) + : BitstreamVisitor(Stream), Reader(Reader) {} + + StreamVisit visitBlock(unsigned ID) { + switch ((UnitBitBlock)ID) { + case UNIT_VERSION_BLOCK_ID: + case UNIT_INFO_BLOCK_ID: + case UNIT_PATHS_BLOCK_ID: + case UNIT_MODULES_BLOCK_ID: + return StreamVisit::Continue; + + case UNIT_DEPENDENCIES_BLOCK_ID: + Reader.DependCursor = Stream; + if (Reader.DependCursor.EnterSubBlock(ID)) { + *Error = "malformed unit dependencies block record"; + return StreamVisit::Abort; + } + readBlockAbbrevs(Reader.DependCursor); + return StreamVisit::Skip; + case UNIT_INCLUDES_BLOCK_ID: + Reader.IncludeCursor = Stream; + if (Reader.IncludeCursor.EnterSubBlock(ID)) { + *Error = "malformed unit includes block record"; + return StreamVisit::Abort; + } + readBlockAbbrevs(Reader.IncludeCursor); + return StreamVisit::Skip; + } + + // Some newly introduced block in a minor version update that we cannot + // handle. + return StreamVisit::Skip; + } + + StreamVisit visitRecord(unsigned BlockID, unsigned RecID, + RecordDataImpl &Record, StringRef Blob) { + switch (BlockID) { + case UNIT_VERSION_BLOCK_ID: { + unsigned StoreFormatVersion = Record[0]; + if (StoreFormatVersion != STORE_FORMAT_VERSION) { + llvm::raw_string_ostream OS(*Error); + OS << "Store format version mismatch: " << StoreFormatVersion; + OS << " , expected: " << STORE_FORMAT_VERSION; + return StreamVisit::Abort; + } + break; + } + + case UNIT_INFO_BLOCK_ID: { + assert(RecID == UNIT_INFO); + unsigned I = 0; + Reader.IsSystemUnit = Record[I++]; + + // Save these to lookup them up after we get the paths buffer. + WorkDirOffset = Record[I++]; + WorkDirSize = Record[I++]; + OutputFileOffset = Record[I++]; + OutputFileSize = Record[I++]; + SysrootOffset = Record[I++]; + SysrootSize = Record[I++]; + MainPathIndex = (int)Record[I++] - 1; + Reader.IsDebugCompilation = Record[I++]; + Reader.IsModuleUnit = Record[I++]; + + size_t moduleNameSize = Record[I++]; + size_t providerIdentifierSize = Record[I++]; + size_t providerVersionSize = Record[I++]; + I++; // Reserved for ProviderDataVersion. + Reader.ModuleName = Blob.substr(0, moduleNameSize); + Blob = Blob.drop_front(moduleNameSize); + Reader.ProviderIdentifier = Blob.substr(0, providerIdentifierSize); + Blob = Blob.drop_front(providerIdentifierSize); + Reader.ProviderVersion = Blob.substr(0, providerVersionSize); + Reader.Target = Blob.drop_front(providerVersionSize); + break; + } + + case UNIT_PATHS_BLOCK_ID: + switch (RecID) { + case UNIT_PATH: + { + unsigned I = 0; + UnitFilePathPrefixKind Kind = (UnitFilePathPrefixKind)Record[I++]; + size_t DirOffset = Record[I++]; + size_t DirSize = Record[I++]; + size_t FilenameOffset = Record[I++]; + size_t FilenameSize = Record[I++]; + + Reader.Paths.emplace_back(Kind, BitPathComponent(DirOffset, DirSize), + BitPathComponent(FilenameOffset, FilenameSize)); + } + break; + case UNIT_PATH_BUFFER: + Reader.PathsBuffer = Blob; + Reader.WorkingDir = Reader.getPathFromBuffer(WorkDirOffset, WorkDirSize); + Reader.OutputFile = Reader.getPathFromBuffer(OutputFileOffset, OutputFileSize); + Reader.SysrootPath = Reader.getPathFromBuffer(SysrootOffset, SysrootSize); + + // now we can populate the main file's path + Reader.constructFilePath(Reader.MainFilePath, MainPathIndex); + break; + default: + llvm_unreachable("shouldn't visit this record"); + } + break; + + case UNIT_MODULES_BLOCK_ID: + switch (RecID) { + case UNIT_MODULE: + { + unsigned I = 0; + unsigned NameOffset = Record[I++]; + unsigned NameSize = Record[I++]; + Reader.Modules.push_back({NameOffset, NameSize}); + } + break; + case UNIT_MODULE_BUFFER: + Reader.ModuleNamesBuffer = Blob; + break; + default: + llvm_unreachable("shouldn't visit this record"); + } + break; + + case UNIT_DEPENDENCIES_BLOCK_ID: + case UNIT_INCLUDES_BLOCK_ID: + llvm_unreachable("shouldn't visit this block'"); + } + return StreamVisit::Continue; + } +}; + +typedef std::function + BlockVisitorCallback; + +class IndexUnitBlockBitstreamVisitor : public BitstreamVisitor { + unsigned RecID; + BlockVisitorCallback Visit; + +public: + IndexUnitBlockBitstreamVisitor(unsigned RecID, + llvm::BitstreamCursor &BlockStream, + BlockVisitorCallback Visit) + : BitstreamVisitor(BlockStream), RecID(RecID), Visit(std::move(Visit)) {} + + StreamVisit visitRecord(unsigned BlockID, unsigned RecID, + RecordDataImpl &Record, StringRef Blob) { + if (RecID != this->RecID) + llvm_unreachable("shouldn't be called with this RecID"); + + if (Visit(Record, Blob)) + return StreamVisit::Continue; + return StreamVisit::Abort; + } +}; + +} // anonymous namespace + +bool IndexUnitReaderImpl::init(std::unique_ptr Buf, + sys::TimePoint<> ModTime, std::string &Error) { + this->ModTime = ModTime; + this->MemBuf = std::move(Buf); + llvm::BitstreamCursor Stream(*MemBuf); + + // Sniff for the signature. + if (Stream.Read(8) != 'I' || + Stream.Read(8) != 'D' || + Stream.Read(8) != 'X' || + Stream.Read(8) != 'U') { + Error = "not a serialized index unit file"; + return true; + } + + IndexUnitBitstreamVisitor BitVisitor(Stream, *this); + return !BitVisitor.visit(Error); +} + +/// Unit dependencies are provided ahead of record ones, record ones +/// ahead of the file ones. +bool IndexUnitReaderImpl::foreachDependency(DependencyReceiver Receiver) { + store::SavedStreamPosition SavedDepPosition(DependCursor); + IndexUnitBlockBitstreamVisitor Visitor(UNIT_DEPENDENCY, DependCursor, + [&](RecordDataImpl& Record, StringRef Blob) { + unsigned I = 0; + UnitDependencyKind UnitDepKind = (UnitDependencyKind)Record[I++]; + bool IsSystem = Record[I++]; + int PathIndex = (int)Record[I++] - 1; + int ModuleIndex = (int)Record[I++] - 1; + time_t ModTime = (time_t)Record[I++]; + size_t FileSize = Record[I++]; + StringRef Name = Blob; + + IndexUnitReader::DependencyKind DepKind; + switch (UnitDepKind) { + case UNIT_DEPEND_KIND_UNIT: + DepKind = IndexUnitReader::DependencyKind::Unit; break; + case UNIT_DEPEND_KIND_RECORD: + DepKind = IndexUnitReader::DependencyKind::Record; break; + case UNIT_DEPEND_KIND_FILE: + DepKind = IndexUnitReader::DependencyKind::File; break; + } + + SmallString<512> PathBuf; + this->constructFilePath(PathBuf, PathIndex); + StringRef ModuleName = this->getModuleName(ModuleIndex); + + return Receiver(IndexUnitReader::DependencyInfo{DepKind, IsSystem, Name, + PathBuf.str(), ModuleName, FileSize, ModTime}); + }); + + std::string Error; + return Visitor.visit(Error); +} + +bool IndexUnitReaderImpl::foreachInclude(IncludeReceiver Receiver) { + store::SavedStreamPosition SavedIncPosition(IncludeCursor); + IndexUnitBlockBitstreamVisitor Visitor(UNIT_INCLUDE, IncludeCursor, + [&](RecordDataImpl& Record, StringRef Blob) { + unsigned I = 0; + int SourcePathIndex = (int)Record[I++] - 1; + unsigned Line = Record[I++]; + int TargetPathIndex = (int)Record[I++] - 1; + + SmallString<512> SourceBuf, TargetBuf; + this->constructFilePath(SourceBuf, SourcePathIndex); + this->constructFilePath(TargetBuf, TargetPathIndex); + return Receiver(IndexUnitReader::IncludeInfo{SourceBuf.str(), Line, TargetBuf.str()}); + }); + + std::string Error; + return Visitor.visit(Error); +} + + +void IndexUnitReaderImpl::constructFilePath(SmallVectorImpl &PathBuf, + int PathIndex) { + + if (PathIndex < 0) return; + FileBitPath &Path = Paths[PathIndex]; + StringRef Prefix; + switch (Path.PrefixKind) { + case UNIT_PATH_PREFIX_NONE: + break; + case UNIT_PATH_PREFIX_WORKDIR: + Prefix = getWorkingDirectory(); + break; + case UNIT_PATH_PREFIX_SYSROOT: + Prefix = getSysrootPath(); + break; + } + PathBuf.append(Prefix.begin(), Prefix.end()); + sys::path::append(PathBuf, + getPathFromBuffer(Path.Dir.Offset, Path.Dir.Size), + getPathFromBuffer(Path.Filename.Offset, Path.Filename.Size)); +} + +StringRef IndexUnitReaderImpl::getModuleName(int ModuleIndex) { + if (ModuleIndex < 0) + return StringRef(); + auto &ModInfo = Modules[ModuleIndex]; + return StringRef(ModuleNamesBuffer.data()+ModInfo.NameOffset, ModInfo.NameSize); +} + + +//===----------------------------------------------------------------------===// +// IndexUnitReader +//===----------------------------------------------------------------------===// + +std::unique_ptr +IndexUnitReader::createWithUnitFilename(StringRef UnitFilename, + StringRef StorePath, + std::string &Error) { + SmallString<128> PathBuf = StorePath; + appendUnitSubDir(PathBuf); + sys::path::append(PathBuf, UnitFilename); + return createWithFilePath(PathBuf.str(), Error); +} + +std::unique_ptr +IndexUnitReader::createWithFilePath(StringRef FilePath, std::string &Error) { + int FD; + std::error_code EC = sys::fs::openFileForRead(FilePath, FD); + if (EC) { + raw_string_ostream(Error) << "Failed opening '" << FilePath << "': " + << EC.message(); + return nullptr; + } + + assert(FD != -1); + struct AutoFDClose { + int FD; + AutoFDClose(int FD) : FD(FD) {} + ~AutoFDClose() { + ::close(FD); + } + } AutoFDClose(FD); + + sys::fs::file_status FileStat; + EC = sys::fs::status(FD, FileStat); + if (EC) { + Error = EC.message(); + return nullptr; + } + + auto ErrOrBuf = MemoryBuffer::getOpenFile(FD, FilePath, /*FileSize=*/-1, + /*RequiresNullTerminator=*/false); + if (!ErrOrBuf) { + raw_string_ostream(Error) << "Failed opening '" << FilePath << "': " + << ErrOrBuf.getError().message(); + return nullptr; + } + + std::unique_ptr Impl(new IndexUnitReaderImpl()); + bool Err = Impl->init(std::move(*ErrOrBuf), FileStat.getLastModificationTime(), + Error); + if (Err) + return nullptr; + + std::unique_ptr Reader; + Reader.reset(new IndexUnitReader(Impl.release())); + return Reader; +} + +Optional> +IndexUnitReader::getModificationTimeForUnit(StringRef UnitFilename, + StringRef StorePath, + std::string &Error) { + SmallString<128> PathBuf = StorePath; + appendUnitSubDir(PathBuf); + sys::path::append(PathBuf, UnitFilename); + + sys::fs::file_status FileStat; + std::error_code EC = sys::fs::status(PathBuf.str(), FileStat); + if (EC) { + Error = EC.message(); + return None; + } + return FileStat.getLastModificationTime(); +} + +#define IMPL static_cast(Impl) + +IndexUnitReader::~IndexUnitReader() { + delete IMPL; +} + +StringRef IndexUnitReader::getProviderIdentifier() const { + return IMPL->getProviderIdentifier(); +} + +StringRef IndexUnitReader::getProviderVersion() const { + return IMPL->getProviderVersion(); +} + +llvm::sys::TimePoint<> IndexUnitReader::getModificationTime() const { + return IMPL->getModificationTime(); +} + +StringRef IndexUnitReader::getWorkingDirectory() const { + return IMPL->getWorkingDirectory(); +} + +StringRef IndexUnitReader::getOutputFile() const { + return IMPL->getOutputFile(); +} + +StringRef IndexUnitReader::getSysrootPath() const { + return IMPL->getSysrootPath(); +} + +StringRef IndexUnitReader::getMainFilePath() const { + return IMPL->getMainFilePath(); +} + +StringRef IndexUnitReader::getModuleName() const { + return IMPL->getModuleName(); +} + +StringRef IndexUnitReader::getTarget() const { + return IMPL->getTarget(); +} + +bool IndexUnitReader::hasMainFile() const { + return IMPL->hasMainFile(); +} + +bool IndexUnitReader::isSystemUnit() const { + return IMPL->isSystemUnit(); +} + +bool IndexUnitReader::isModuleUnit() const { + return IMPL->isModuleUnit(); +} + +bool IndexUnitReader::isDebugCompilation() const { + return IMPL->isDebugCompilation(); +} + +/// \c Index is the index in the \c getDependencies array. +/// Unit dependencies are provided ahead of record ones. +bool IndexUnitReader::foreachDependency(DependencyReceiver Receiver) { + return IMPL->foreachDependency(std::move(Receiver)); +} + +bool IndexUnitReader::foreachInclude(IncludeReceiver Receiver) { + return IMPL->foreachInclude(std::move(Receiver)); +} diff --git a/clang/lib/Index/IndexUnitWriter.cpp b/clang/lib/Index/IndexUnitWriter.cpp new file mode 100644 index 0000000000000..7c981ae9750d8 --- /dev/null +++ b/clang/lib/Index/IndexUnitWriter.cpp @@ -0,0 +1,628 @@ +//===--- IndexUnitWriter.cpp - Index unit serialization -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Index/IndexUnitWriter.h" +#include "IndexDataStoreUtils.h" +#include "clang/Basic/FileManager.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Bitcode/BitstreamWriter.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::index; +using namespace clang::index::store; +using namespace llvm; + + +class IndexUnitWriter::PathStorage { + std::string WorkDir; + std::string SysrootPath; + SmallString<512> PathsBuf; + StringMap Dirs; + std::vector FileBitPaths; + DenseMap FileToIndex; + +public: + PathStorage(StringRef workDir, StringRef sysrootPath) { + WorkDir = workDir; + if (sysrootPath == "/") + sysrootPath = StringRef(); + SysrootPath = sysrootPath; + } + + StringRef getPathsBuffer() const { return PathsBuf.str(); } + + ArrayRef getBitPaths() const { return FileBitPaths; } + + int getPathIndex(const FileEntry *FE) { + if (!FE) + return -1; + auto Pair = FileToIndex.insert(std::make_pair(FE, FileBitPaths.size())); + bool IsNew = Pair.second; + size_t Index = Pair.first->getSecond(); + + if (IsNew) { + StringRef Filename = sys::path::filename(FE->getName()); + DirBitPath Dir = getDirBitPath(sys::path::parent_path(FE->getName())); + FileBitPaths.emplace_back(Dir.PrefixKind, Dir.Dir, + BitPathComponent(getPathOffset(Filename), + Filename.size())); + } + return Index; + } + + size_t getPathOffset(StringRef Path) { + if (Path.empty()) + return 0; + size_t offset = PathsBuf.size(); + PathsBuf += Path; + return offset; + } + +private: + DirBitPath getDirBitPath(StringRef dirStr) { + auto pair = Dirs.insert(std::make_pair(dirStr, DirBitPath())); + bool isNew = pair.second; + auto &dirPath = pair.first->second; + + if (isNew) { + if (isPathInDir(SysrootPath, dirStr)) { + dirPath.PrefixKind = UNIT_PATH_PREFIX_SYSROOT; + dirStr = dirStr.drop_front(SysrootPath.size()); + while (!dirStr.empty() && dirStr[0] == '/') + dirStr = dirStr.drop_front(); + } else if (isPathInDir(WorkDir, dirStr)) { + dirPath.PrefixKind = UNIT_PATH_PREFIX_WORKDIR; + dirStr = dirStr.drop_front(WorkDir.size()); + while (!dirStr.empty() && dirStr[0] == '/') + dirStr = dirStr.drop_front(); + } + dirPath.Dir.Offset = getPathOffset(dirStr); + dirPath.Dir.Size = dirStr.size(); + } + return dirPath; + } + + static bool isPathInDir(StringRef dir, StringRef path) { + if (dir.empty() || !path.startswith(dir)) + return false; + StringRef rest = path.drop_front(dir.size()); + return !rest.empty() && sys::path::is_separator(rest.front()); + } +}; + +IndexUnitWriter::IndexUnitWriter(FileManager &FileMgr, + StringRef StorePath, + StringRef ProviderIdentifier, + StringRef ProviderVersion, + StringRef OutputFile, + StringRef ModuleName, + const FileEntry *MainFile, + bool IsSystem, + bool IsModuleUnit, + bool IsDebugCompilation, + StringRef TargetTriple, + StringRef SysrootPath, + writer::ModuleInfoWriterCallback GetInfoForModule) +: FileMgr(FileMgr) { + this->UnitsPath = StorePath; + store::appendUnitSubDir(this->UnitsPath); + this->ProviderIdentifier = ProviderIdentifier; + this->ProviderVersion = ProviderVersion; + this->OutputFile = OutputFile; + this->ModuleName = ModuleName; + this->MainFile = MainFile; + this->IsSystemUnit = IsSystem; + this->IsModuleUnit = IsModuleUnit; + this->IsDebugCompilation = IsDebugCompilation; + this->TargetTriple = TargetTriple; + this->SysrootPath = SysrootPath; + this->GetInfoForModuleFn = GetInfoForModule; +} + +IndexUnitWriter::~IndexUnitWriter() {} + +int IndexUnitWriter::addModule(writer::OpaqueModule Mod) { + if (!Mod) + return -1; + + auto Pair = IndexByModule.insert(std::make_pair(Mod, Modules.size())); + bool WasInserted = Pair.second; + if (WasInserted) { + Modules.push_back(Mod); + } + return Pair.first->second; +} + +int IndexUnitWriter::addFileDependency(const FileEntry *File, bool IsSystem, + writer::OpaqueModule Mod) { + assert(File); + auto Pair = IndexByFile.insert(std::make_pair(File, Files.size())); + bool WasInserted = Pair.second; + if (WasInserted) { + Files.push_back(FileEntryData{File, IsSystem, addModule(Mod), {}}); + } + return Pair.first->second; +} + +void IndexUnitWriter::addRecordFile(StringRef RecordFile, const FileEntry *File, + bool IsSystem, writer::OpaqueModule Mod) { + int Dep = File ? addFileDependency(File, IsSystem, /*module=*/nullptr) : -1; + Records.push_back(RecordOrUnitData{RecordFile, Dep, addModule(Mod), IsSystem}); +} + +void IndexUnitWriter::addASTFileDependency(const FileEntry *File, bool IsSystem, + writer::OpaqueModule Mod, + bool withoutUnitName) { + assert(File); + if (!SeenASTFiles.insert(File).second) + return; + + SmallString<64> UnitName; + if (!withoutUnitName) + getUnitNameForOutputFile(File->getName(), UnitName); + addUnitDependency(UnitName.str(), File, IsSystem, Mod); +} + +void IndexUnitWriter::addUnitDependency(StringRef UnitFile, + const FileEntry *File, bool IsSystem, + writer::OpaqueModule Mod) { + int Dep = File ? addFileDependency(File, IsSystem, /*module=*/nullptr) : -1; + ASTFileUnits.emplace_back(RecordOrUnitData{UnitFile, Dep, addModule(Mod), IsSystem}); +} + +bool IndexUnitWriter::addInclude(const FileEntry *Source, unsigned Line, + const FileEntry *Target) { + // FIXME: This will ignore includes of headers that resolve to module imports + // because the 'target' header has not been added as a file dependency earlier + // so it is missing from \c IndexByFile. + + auto It = IndexByFile.find(Source); + if (It == IndexByFile.end()) + return false; + int SourceIndex = It->getSecond(); + It = IndexByFile.find(Target); + if (It == IndexByFile.end()) + return false; + int TargetIndex = It->getSecond(); + Files[SourceIndex].Includes.emplace_back(FileInclude{TargetIndex, Line}); + return true; +}; + +void IndexUnitWriter::getUnitNameForOutputFile(StringRef FilePath, + SmallVectorImpl &Str) { + SmallString<256> AbsPath(FilePath); + FileMgr.makeAbsolutePath(AbsPath); + return getUnitNameForAbsoluteOutputFile(AbsPath, Str); +} + +void IndexUnitWriter::getUnitPathForOutputFile(StringRef FilePath, + SmallVectorImpl &Str) { + Str.append(UnitsPath.begin(), UnitsPath.end()); + Str.push_back('/'); + return getUnitNameForOutputFile(FilePath, Str); +} + +Optional IndexUnitWriter::isUnitUpToDateForOutputFile(StringRef FilePath, + Optional TimeCompareFilePath, + std::string &Error) { + SmallString<256> UnitPath; + getUnitPathForOutputFile(FilePath, UnitPath); + + llvm::sys::fs::file_status UnitStat; + if (std::error_code EC = llvm::sys::fs::status(UnitPath.c_str(), UnitStat)) { + if (EC != llvm::errc::no_such_file_or_directory) { + llvm::raw_string_ostream Err(Error); + Err << "could not access path '" << UnitPath + << "': " << EC.message(); + return None; + } + return false; + } + + if (!TimeCompareFilePath.hasValue()) + return true; + + llvm::sys::fs::file_status CompareStat; + if (std::error_code EC = llvm::sys::fs::status(*TimeCompareFilePath, CompareStat)) { + if (EC != llvm::errc::no_such_file_or_directory) { + llvm::raw_string_ostream Err(Error); + Err << "could not access path '" << *TimeCompareFilePath + << "': " << EC.message(); + return None; + } + return true; + } + + // Return true (unit is up-to-date) if the file to compare is older than the + // unit file. + return CompareStat.getLastModificationTime() <= UnitStat.getLastModificationTime(); +} + +void IndexUnitWriter::getUnitNameForAbsoluteOutputFile(StringRef FilePath, + SmallVectorImpl &Str) { + StringRef Fname = sys::path::filename(FilePath); + Str.append(Fname.begin(), Fname.end()); + Str.push_back('-'); + llvm::hash_code PathHashVal = llvm::hash_value(FilePath); + llvm::APInt(64, PathHashVal).toString(Str, 36, /*Signed=*/false); +} + +static void writeBlockInfo(BitstreamWriter &Stream) { + RecordData Record; + + Stream.EnterBlockInfoBlock(); +#define BLOCK(X) emitBlockID(X ## _ID, #X, Stream, Record) +#define RECORD(X) emitRecordID(X, #X, Stream, Record) + + BLOCK(UNIT_VERSION_BLOCK); + RECORD(UNIT_VERSION); + + BLOCK(UNIT_INFO_BLOCK); + RECORD(UNIT_INFO); + + BLOCK(UNIT_DEPENDENCIES_BLOCK); + RECORD(UNIT_DEPENDENCY); + + BLOCK(UNIT_INCLUDES_BLOCK); + RECORD(UNIT_INCLUDE); + + BLOCK(UNIT_PATHS_BLOCK); + RECORD(UNIT_PATH); + RECORD(UNIT_PATH_BUFFER); + + BLOCK(UNIT_MODULES_BLOCK); + RECORD(UNIT_MODULE); + RECORD(UNIT_MODULE_BUFFER); + +#undef RECORD +#undef BLOCK + Stream.ExitBlock(); +} + +static void writeVersionInfo(BitstreamWriter &Stream) { + using namespace llvm::sys; + + Stream.EnterSubblock(UNIT_VERSION_BLOCK_ID, 3); + + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(UNIT_VERSION)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Store format version + unsigned AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + + RecordData Record; + Record.push_back(UNIT_VERSION); + Record.push_back(STORE_FORMAT_VERSION); + Stream.EmitRecordWithAbbrev(AbbrevCode, Record); + + Stream.ExitBlock(); +} + +bool IndexUnitWriter::write(std::string &Error) { + using namespace llvm::sys; + + // Determine the working directory. + SmallString<128> CWDPath; + if (!FileMgr.getFileSystemOpts().WorkingDir.empty()) { + CWDPath = FileMgr.getFileSystemOpts().WorkingDir; + if (!path::is_absolute(CWDPath)) { + fs::make_absolute(CWDPath); + } + } else { + std::error_code EC = sys::fs::current_path(CWDPath); + if (EC) { + llvm::raw_string_ostream Err(Error); + Err << "failed to determine current working directory: " << EC.message(); + return true; + } + } + WorkDir = CWDPath.str(); + + SmallString<512> Buffer; + BitstreamWriter Stream(Buffer); + Stream.Emit('I', 8); + Stream.Emit('D', 8); + Stream.Emit('X', 8); + Stream.Emit('U', 8); + + PathStorage PathStore(WorkDir, SysrootPath); + + writeBlockInfo(Stream); + writeVersionInfo(Stream); + writeUnitInfo(Stream, PathStore); + writeDependencies(Stream, PathStore); + writeIncludes(Stream, PathStore); + writePaths(Stream, PathStore); + writeModules(Stream); + + SmallString<256> UnitPath; + getUnitPathForOutputFile(OutputFile, UnitPath); + + SmallString<128> TempPath; + TempPath = path::parent_path(UnitsPath); + TempPath += '/'; + TempPath += path::filename(UnitPath); + TempPath += "-%%%%%%%%"; + int TempFD; + if (llvm::sys::fs::createUniqueFile(TempPath.str(), TempFD, TempPath)) { + llvm::raw_string_ostream Err(Error); + Err << "failed to create temporary file: " << TempPath; + return true; + } + + raw_fd_ostream OS(TempFD, /*shouldClose=*/true); + OS.write(Buffer.data(), Buffer.size()); + OS.close(); + + std::error_code EC = fs::rename(/*from=*/TempPath.c_str(), /*to=*/UnitPath.c_str()); + if (EC) { + llvm::raw_string_ostream Err(Error); + Err << "failed to rename '" << TempPath << "' to '" << UnitPath << "': " << EC.message(); + return true; + } + + return false; +} + +void IndexUnitWriter::writeUnitInfo(llvm::BitstreamWriter &Stream, + PathStorage &PathStore) { + Stream.EnterSubblock(UNIT_INFO_BLOCK_ID, 3); + + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(UNIT_INFO)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsSystemUnit + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // WorkDir offset + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // WorkDir size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // OutputFile offset + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // OutputFile size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // Sysroot offset + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // Sysroot size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // Main path id + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsDebugCompilation + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsModuleUnit + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 5)); // Module name size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 5)); // ProviderIdentifier size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 5)); // ProviderVersion size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 5)); // ProviderDataVersion + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Module name + ProviderIdentifier + ProviderVersion + target triple + unsigned AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + + RecordData Record; + Record.push_back(UNIT_INFO); + Record.push_back(IsSystemUnit); + Record.push_back(PathStore.getPathOffset(WorkDir)); + Record.push_back(WorkDir.size()); + Record.push_back(PathStore.getPathOffset(OutputFile)); + Record.push_back(OutputFile.size()); + Record.push_back(PathStore.getPathOffset(SysrootPath)); + Record.push_back(SysrootPath.size()); + Record.push_back(PathStore.getPathIndex(MainFile) + 1); // Make 1-based with 0=invalid + Record.push_back(IsDebugCompilation); + Record.push_back(IsModuleUnit); + Record.push_back(ModuleName.size()); + Record.push_back(ProviderIdentifier.size()); + Record.push_back(ProviderVersion.size()); + // ProviderDataVersion is reserved. Not sure it is a good to idea to have + // clients consider the specifics of a 'provider data version', but reserving + // to avoid store format version change in case there is a use case in the + // future. + Record.push_back(0); // ProviderDataVersion + SmallString<128> InfoStrings; + InfoStrings += ModuleName; + InfoStrings += ProviderIdentifier; + InfoStrings += ProviderVersion; + InfoStrings += TargetTriple; + Stream.EmitRecordWithBlob(AbbrevCode, Record, InfoStrings); + + Stream.ExitBlock(); +} + +void IndexUnitWriter::writeDependencies(llvm::BitstreamWriter &Stream, + PathStorage &PathStore) { + std::vector FileUsedForRecordOrUnit; + FileUsedForRecordOrUnit.resize(Files.size()); + + Stream.EnterSubblock(UNIT_DEPENDENCIES_BLOCK_ID, 3); + + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(UNIT_DEPENDENCY)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, UnitDependencyKindBitNum)); // Dependency kind + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsSystem + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // PathIndex (1-based, 0 = none) + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // ModuleIndex (1-based, 0 = none) + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 32)); // time_t + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 16)); // file size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Name + unsigned AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + + RecordData Record; + + auto addRecordOrUnitData = [&](UnitDependencyKind K, const RecordOrUnitData &Data) { + Record.push_back(UNIT_DEPENDENCY); + Record.push_back(K); + Record.push_back(Data.IsSystem); + if (Data.FileIndex != -1) { + Record.push_back(PathStore.getPathIndex(Files[Data.FileIndex].File) + 1); + FileUsedForRecordOrUnit[Data.FileIndex] = true; + } else { + Record.push_back(0); + } + if (Data.ModuleIndex != -1) { + Record.push_back(Data.ModuleIndex + 1); + } else { + Record.push_back(0); + } + if (Data.FileIndex != -1) { + Record.push_back(Files[Data.FileIndex].File->getModificationTime()); + Record.push_back(Files[Data.FileIndex].File->getSize()); + } else { + Record.push_back(0); + Record.push_back(0); + } + Stream.EmitRecordWithBlob(AbbrevCode, Record, Data.Name); + }; + + for (auto &ASTData : ASTFileUnits) { + Record.clear(); + addRecordOrUnitData(UNIT_DEPEND_KIND_UNIT, ASTData); + } + for (auto &recordData : Records) { + Record.clear(); + addRecordOrUnitData(UNIT_DEPEND_KIND_RECORD, recordData); + } + size_t FileIndex = 0; + for (auto &File : Files) { + if (FileUsedForRecordOrUnit[FileIndex++]) + continue; + Record.clear(); + Record.push_back(UNIT_DEPENDENCY); + Record.push_back(UNIT_DEPEND_KIND_FILE); + Record.push_back(File.IsSystem); + Record.push_back(PathStore.getPathIndex(File.File) + 1); + if (File.ModuleIndex != -1) { + Record.push_back(File.ModuleIndex + 1); + } else { + Record.push_back(0); + } + Record.push_back(File.File->getModificationTime()); + Record.push_back(File.File->getSize()); + Stream.EmitRecordWithBlob(AbbrevCode, Record, StringRef()); + } + + Stream.ExitBlock(); +} + +void IndexUnitWriter::writeIncludes(llvm::BitstreamWriter &Stream, + PathStorage &PathStore) { + Stream.EnterSubblock(UNIT_INCLUDES_BLOCK_ID, 3); + + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(UNIT_INCLUDE)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // source path index (1-based, 0 = no path) + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 12)); // source include line + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // target path index (1-based, 0 = no path) + unsigned AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + + RecordData Record; + + for (auto &Including : Files) { + for(auto &Included: Including.Includes) { + Record.clear(); + Record.push_back(UNIT_INCLUDE); + Record.push_back(PathStore.getPathIndex(Including.File) + 1); + Record.push_back(Included.Line); + Record.push_back(PathStore.getPathIndex(Files[Included.Index].File) + 1); + Stream.EmitRecordWithAbbrev(AbbrevCode, Record); + } + } + Stream.ExitBlock(); +} + +void IndexUnitWriter::writePaths(llvm::BitstreamWriter &Stream, + PathStorage &PathStore) { + Stream.EnterSubblock(UNIT_PATHS_BLOCK_ID, 3); + + auto PathAbbrev = std::make_shared(); + PathAbbrev->Add(BitCodeAbbrevOp(UNIT_PATH)); + PathAbbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, UnitFilePathPrefixKindBitNum)); // Path prefix kind + PathAbbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // DirPath offset + PathAbbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // DirPath size + PathAbbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 10)); // Filename offset + PathAbbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Filename size + unsigned PathAbbrevCode = Stream.EmitAbbrev(std::move(PathAbbrev)); + + auto PathBufferAbbrev = std::make_shared(); + PathBufferAbbrev->Add(BitCodeAbbrevOp(UNIT_PATH_BUFFER)); + PathBufferAbbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Paths buffer + unsigned PathBufferAbbrevCode = Stream.EmitAbbrev(PathBufferAbbrev); + + RecordData Record; + for(auto &BitPath: PathStore.getBitPaths()) { + Record.push_back(UNIT_PATH); + Record.push_back(BitPath.PrefixKind); + Record.push_back(BitPath.Dir.Offset); + Record.push_back(BitPath.Dir.Size); + Record.push_back(BitPath.Filename.Offset); + Record.push_back(BitPath.Filename.Size); + Stream.EmitRecordWithAbbrev(PathAbbrevCode, Record); + Record.clear(); + } + + Record.push_back(UNIT_PATH_BUFFER); + Stream.EmitRecordWithBlob(PathBufferAbbrevCode, Record, PathStore.getPathsBuffer()); + + Stream.ExitBlock(); +} + +void IndexUnitWriter::writeModules(llvm::BitstreamWriter &Stream) { + Stream.EnterSubblock(UNIT_MODULES_BLOCK_ID, 3); + + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(UNIT_MODULE)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 9)); // Module name offset + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Module name size + unsigned AbbrevCode = Stream.EmitAbbrev(std::move(Abbrev)); + + auto BufferAbbrev = std::make_shared(); + BufferAbbrev->Add(BitCodeAbbrevOp(UNIT_MODULE_BUFFER)); + BufferAbbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Module names buffer + unsigned BufferAbbrevCode = Stream.EmitAbbrev(BufferAbbrev); + + SmallString<512> ModuleNamesBuf; + + RecordData Record; + for (auto &Mod : Modules) { + SmallString<64> ModuleName; + StringRef name = GetInfoForModuleFn(Mod, ModuleName).Name; + size_t offset = ModuleNamesBuf.size(); + ModuleNamesBuf += name; + + Record.push_back(UNIT_MODULE); + Record.push_back(offset); + Record.push_back(name.size()); + Stream.EmitRecordWithAbbrev(AbbrevCode, Record); + Record.clear(); + } + + Record.push_back(UNIT_MODULE_BUFFER); + Stream.EmitRecordWithBlob(BufferAbbrevCode, Record, ModuleNamesBuf.str()); + + Stream.ExitBlock(); +} + +bool IndexUnitWriter::initIndexDirectory(StringRef StorePath, + std::string &Error) { + using namespace llvm::sys; + SmallString<128> SubPath = StorePath; + store::appendRecordSubDir(SubPath); + std::error_code EC = fs::create_directories(SubPath); + if (EC) { + llvm::raw_string_ostream Err(Error); + Err << "failed to create directory '" << SubPath << "': " << EC.message(); + return true; + } + + SubPath = StorePath; + store::appendUnitSubDir(SubPath); + EC = fs::create_directory(SubPath); + if (EC) { + llvm::raw_string_ostream Err(Error); + Err << "failed to create directory '" << SubPath << "': " << EC.message(); + return true; + } + + return false; +} diff --git a/clang/lib/Index/IndexingAction.cpp b/clang/lib/Index/IndexingAction.cpp index cac24d4b9c4c1..32da3624f3a94 100644 --- a/clang/lib/Index/IndexingAction.cpp +++ b/clang/lib/Index/IndexingAction.cpp @@ -9,9 +9,16 @@ #include "clang/Index/IndexingAction.h" #include "clang/Index/IndexDataConsumer.h" +#include "FileIndexRecord.h" #include "IndexingContext.h" +#include "ClangIndexRecordWriter.h" +#include "IndexDataStoreUtils.h" +#include "clang/Index/IndexUnitWriter.h" +#include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/MultiplexConsumer.h" +#include "clang/Frontend/Utils.h" #include "clang/Lex/Preprocessor.h" #include "clang/Serialization/ASTReader.h" @@ -80,7 +87,8 @@ class IndexActionBase { : DataConsumer(std::move(dataConsumer)), IndexCtx(Opts, *DataConsumer) {} - std::unique_ptr createIndexASTConsumer() { + std::unique_ptr createIndexASTConsumer(CompilerInstance &CI) { + IndexCtx.setSysrootPath(CI.getHeaderSearchOpts().Sysroot); return llvm::make_unique(IndexCtx); } @@ -98,7 +106,7 @@ class IndexAction : public ASTFrontendAction, IndexActionBase { protected: std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { - return createIndexASTConsumer(); + return createIndexASTConsumer(CI); } void EndSourceFileAction() override { @@ -108,7 +116,7 @@ class IndexAction : public ASTFrontendAction, IndexActionBase { }; class WrappingIndexAction : public WrapperFrontendAction, IndexActionBase { - bool IndexActionFailed = false; + bool CreatedASTConsumer = false; public: WrappingIndexAction(std::unique_ptr WrappedAction, @@ -128,21 +136,20 @@ class WrappingIndexAction : public WrapperFrontendAction, IndexActionBase { void WrappingIndexAction::EndSourceFileAction() { // Invoke wrapped action's method. WrapperFrontendAction::EndSourceFileAction(); - if (!IndexActionFailed) + if (CreatedASTConsumer) finish(); } std::unique_ptr WrappingIndexAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { auto OtherConsumer = WrapperFrontendAction::CreateASTConsumer(CI, InFile); - if (!OtherConsumer) { - IndexActionFailed = true; + if (!OtherConsumer) return nullptr; - } + CreatedASTConsumer = true; std::vector> Consumers; Consumers.push_back(std::move(OtherConsumer)); - Consumers.push_back(createIndexASTConsumer()); + Consumers.push_back(createIndexASTConsumer(CI)); return llvm::make_unique(std::move(Consumers)); } @@ -191,3 +198,628 @@ void index::indexModuleFile(serialization::ModuleFile &Mod, } DataConsumer->finish(); } + +//===----------------------------------------------------------------------===// +// Index Data Recording +//===----------------------------------------------------------------------===// + +namespace { + +class IndexDataRecorder : public IndexDataConsumer { + IndexingContext *IndexCtx = nullptr; + const Preprocessor *PP = nullptr; + typedef llvm::DenseMap> RecordByFileTy; + RecordByFileTy RecordByFile; + +public: + void init(IndexingContext *idxCtx, const CompilerInstance &CI) { + IndexCtx = idxCtx; + PP = &CI.getPreprocessor(); + initialize(CI.getASTContext()); + } + + RecordByFileTy::const_iterator record_begin() const { return RecordByFile.begin(); } + RecordByFileTy::const_iterator record_end() const { return RecordByFile.end(); } + bool record_empty() const { return RecordByFile.empty(); } + +private: + bool handleDeclOccurence(const Decl *D, SymbolRoleSet Roles, + ArrayRef Relations, + FileID FID, unsigned Offset, + ASTNodeInfo ASTNode) override { + // Ignore the predefines buffer. + if (FID == PP->getPredefinesFileID()) + return true; + + FileIndexRecord &Rec = getFileIndexRecord(FID); + Rec.addDeclOccurence(Roles, Offset, D, Relations); + return true; + } + + FileIndexRecord &getFileIndexRecord(FileID FID) { + auto &Entry = RecordByFile[FID]; + if (!Entry) { + Entry.reset(new FileIndexRecord(FID, IndexCtx->isSystemFile(FID))); + } + return *Entry; + } +}; + +struct IncludeLocation { + const FileEntry *Source; + const FileEntry *Target; + unsigned Line; +}; + +class IncludePPCallbacks : public PPCallbacks { + IndexingContext &IndexCtx; + RecordingOptions RecordOpts; + std::vector &Includes; + SourceManager &SourceMgr; + +public: + IncludePPCallbacks(IndexingContext &indexCtx, RecordingOptions recordOpts, + std::vector &IncludesForFile, + SourceManager &SourceMgr) : + IndexCtx(indexCtx), RecordOpts(recordOpts), + Includes(IncludesForFile), SourceMgr(SourceMgr) {} + +private: + void addInclude(SourceLocation From, const FileEntry *To) { + assert(To); + if (RecordOpts.RecordIncludes == RecordingOptions::IncludesRecordingKind::None) + return; + + std::pair LocInfo = SourceMgr.getDecomposedExpansionLoc(From); + switch (RecordOpts.RecordIncludes) { + case RecordingOptions::IncludesRecordingKind::None: + llvm_unreachable("should have already checked in the beginning"); + case RecordingOptions::IncludesRecordingKind::UserOnly: + if (IndexCtx.isSystemFile(LocInfo.first)) + return; // Ignore includes of system headers. + break; + case RecordingOptions::IncludesRecordingKind::All: + break; + } + auto *FE = SourceMgr.getFileEntryForID(LocInfo.first); + if (!FE) + return; + auto lineNo = SourceMgr.getLineNumber(LocInfo.first, LocInfo.second); + Includes.push_back({FE, To, lineNo}); + } + + virtual void InclusionDirective(SourceLocation HashLoc, + const Token &IncludeTok, + StringRef FileName, + bool IsAngled, + CharSourceRange FilenameRange, + const FileEntry *File, + StringRef SearchPath, + StringRef RelativePath, + const Module *Imported) override { + if (HashLoc.isFileID() && File && File->isValid()) + addInclude(HashLoc, File); + } +}; + +class IndexDependencyProvider { +public: + virtual ~IndexDependencyProvider() {} + + virtual void visitFileDependencies(const CompilerInstance &CI, + llvm::function_ref visitor) = 0; + virtual void visitIncludes( + llvm::function_ref visitor) = 0; + virtual void visitModuleImports(const CompilerInstance &CI, + llvm::function_ref visitor) = 0; +}; + +class SourceFilesIndexDependencyCollector : public DependencyCollector, public IndexDependencyProvider { + IndexingContext &IndexCtx; + RecordingOptions RecordOpts; + llvm::SetVector Entries; + llvm::BitVector IsSystemByUID; + std::vector Includes; + SourceManager *SourceMgr = nullptr; + std::string SysrootPath; + +public: + SourceFilesIndexDependencyCollector(IndexingContext &indexCtx, RecordingOptions recordOpts) + : IndexCtx(indexCtx), RecordOpts(recordOpts) {} + + virtual void attachToPreprocessor(Preprocessor &PP) override { + DependencyCollector::attachToPreprocessor(PP); + PP.addPPCallbacks(llvm::make_unique(IndexCtx, + RecordOpts, + Includes, + PP.getSourceManager())); + } + + void setSourceManager(SourceManager *SourceMgr) { + this->SourceMgr = SourceMgr; + } + void setSysrootPath(StringRef sysroot) { SysrootPath = sysroot; } + + void visitFileDependencies(const CompilerInstance &CI, + llvm::function_ref visitor) override { + for (auto *FE : getEntries()) { + visitor(FE, isSystemFile(FE)); + } + } + + void visitIncludes( + llvm::function_ref visitor) override { + for (auto &Include : Includes) { + visitor(Include.Source, Include.Line, Include.Target); + } + } + + void visitModuleImports(const CompilerInstance &CI, + llvm::function_ref visitor) override { + HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo(); + + if (auto Reader = CI.getModuleManager()) { + Reader->getModuleManager().visit([&](serialization::ModuleFile &Mod) -> bool { + bool isSystemMod = false; + if (Mod.isModule()) { + if (auto *M = HS.lookupModule(Mod.ModuleName, /*AllowSearch=*/false)) + isSystemMod = M->IsSystem; + } + if (!isSystemMod || needSystemDependencies()) + visitor(Mod, isSystemMod); + return true; // skip module dependencies. + }); + } + } + +private: + bool isSystemFile(const FileEntry *FE) { + auto UID = FE->getUID(); + return IsSystemByUID.size() > UID && IsSystemByUID[UID]; + } + + ArrayRef getEntries() const { + return Entries.getArrayRef(); + } + + bool needSystemDependencies() override { + return RecordOpts.RecordSystemDependencies; + } + + bool sawDependency(StringRef Filename, bool FromModule, + bool IsSystem, bool IsModuleFile, bool IsMissing) override { + bool sawIt = DependencyCollector::sawDependency(Filename, FromModule, + IsSystem, IsModuleFile, + IsMissing); + if (auto *FE = SourceMgr->getFileManager().getFile(Filename)) { + if (sawIt) + Entries.insert(FE); + // Record system-ness for all files that we pass through. + if (IsSystemByUID.size() < FE->getUID()+1) + IsSystemByUID.resize(FE->getUID()+1); + IsSystemByUID[FE->getUID()] = IsSystem || isInSysroot(Filename); + } + return sawIt; + } + + bool isInSysroot(StringRef Filename) { + return !SysrootPath.empty() && Filename.startswith(SysrootPath); + } +}; + +class IndexRecordActionBase { +protected: + RecordingOptions RecordOpts; + IndexDataRecorder Recorder; + IndexingContext IndexCtx; + SourceFilesIndexDependencyCollector DepCollector; + + IndexRecordActionBase(IndexingOptions IndexOpts, RecordingOptions recordOpts) + : RecordOpts(std::move(recordOpts)), + IndexCtx(IndexOpts, Recorder), + DepCollector(IndexCtx, RecordOpts) { + } + + std::unique_ptr + createIndexASTConsumer(CompilerInstance &CI) { + IndexCtx.setSysrootPath(CI.getHeaderSearchOpts().Sysroot); + Recorder.init(&IndexCtx, CI); + + Preprocessor &PP = CI.getPreprocessor(); + DepCollector.setSourceManager(&CI.getSourceManager()); + DepCollector.setSysrootPath(IndexCtx.getSysrootPath()); + DepCollector.attachToPreprocessor(PP); + + return llvm::make_unique(IndexCtx); + } + + void finish(CompilerInstance &CI); +}; + +class IndexRecordAction : public ASTFrontendAction, IndexRecordActionBase { +public: + IndexRecordAction(IndexingOptions IndexOpts, RecordingOptions RecordOpts) + : IndexRecordActionBase(std::move(IndexOpts), std::move(RecordOpts)) {} + +protected: + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override { + return createIndexASTConsumer(CI); + } + + void EndSourceFileAction() override { + FrontendAction::EndSourceFileAction(); + finish(getCompilerInstance()); + } +}; + +class WrappingIndexRecordAction : public WrapperFrontendAction, IndexRecordActionBase { + bool CreatedASTConsumer = false; + +public: + WrappingIndexRecordAction(std::unique_ptr WrappedAction, + IndexingOptions IndexOpts, + RecordingOptions RecordOpts) + : WrapperFrontendAction(std::move(WrappedAction)), + IndexRecordActionBase(std::move(IndexOpts), std::move(RecordOpts)) {} + +protected: + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override { + auto OtherConsumer = WrapperFrontendAction::CreateASTConsumer(CI, InFile); + if (!OtherConsumer) + return nullptr; + + CreatedASTConsumer = true; + std::vector> Consumers; + Consumers.push_back(std::move(OtherConsumer)); + Consumers.push_back(createIndexASTConsumer(CI)); + return llvm::make_unique(std::move(Consumers)); + } + + void EndSourceFileAction() override { + // Invoke wrapped action's method. + WrapperFrontendAction::EndSourceFileAction(); + if (CreatedASTConsumer) + finish(getCompilerInstance()); + } +}; + +} // anonymous namespace + +static std::string getClangVersion() { + // Try picking the version from an Apple Clang tag. + std::string RepositoryPath = getClangRepositoryPath(); + StringRef BuildNumber = StringRef(RepositoryPath); + size_t DashOffset = BuildNumber.find('-'); + if (BuildNumber.startswith("clang") && DashOffset != StringRef::npos) { + BuildNumber = BuildNumber.substr(DashOffset + 1); + return BuildNumber; + } + // Fallback to the generic version. + return CLANG_VERSION_STRING; +} + +static void writeUnitData(const CompilerInstance &CI, + IndexDataRecorder &Recorder, + IndexDependencyProvider &DepProvider, + IndexingOptions IndexOpts, + RecordingOptions RecordOpts, + StringRef OutputFile, + const FileEntry *RootFile, + Module *UnitModule, + StringRef SysrootPath); + +void IndexRecordActionBase::finish(CompilerInstance &CI) { + // We may emit more diagnostics so do the begin/end source file invocations + // on the diagnostic client. + // FIXME: FrontendAction::EndSourceFile() should probably not call + // CI.getDiagnosticClient().EndSourceFile()' until after it has called + // 'EndSourceFileAction()', so that code executing during EndSourceFileAction() + // can emit diagnostics. If this is fixed, DiagClientBeginEndRAII can go away. + struct DiagClientBeginEndRAII { + CompilerInstance &CI; + DiagClientBeginEndRAII(CompilerInstance &CI) : CI(CI) { + CI.getDiagnosticClient().BeginSourceFile(CI.getLangOpts()); + } + ~DiagClientBeginEndRAII() { + CI.getDiagnosticClient().EndSourceFile(); + } + } diagClientBeginEndRAII(CI); + + SourceManager &SM = CI.getSourceManager(); + DiagnosticsEngine &Diag = CI.getDiagnostics(); + HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo(); + StringRef DataPath = RecordOpts.DataDirPath; + + std::string Error; + if (IndexUnitWriter::initIndexDirectory(DataPath, Error)) { + unsigned DiagID = Diag.getCustomDiagID( + DiagnosticsEngine::Error, "failed creating index directory %0"); + Diag.Report(DiagID) << Error; + return; + } + + std::string OutputFile = CI.getFrontendOpts().OutputFile; + if (OutputFile.empty()) { + OutputFile = CI.getFrontendOpts().Inputs[0].getFile(); + OutputFile += ".o"; + } + + const FileEntry *RootFile = nullptr; + Module *UnitMod = nullptr; + bool isModuleGeneration = CI.getLangOpts().isCompilingModule(); + if (!isModuleGeneration && + CI.getFrontendOpts().ProgramAction != frontend::GeneratePCH) { + RootFile = SM.getFileEntryForID(SM.getMainFileID()); + } + if (isModuleGeneration) { + UnitMod = HS.lookupModule(CI.getLangOpts().CurrentModule, + /*AllowSearch=*/false); + } + + writeUnitData(CI, Recorder, DepCollector, IndexCtx.getIndexOpts(), RecordOpts, + OutputFile, RootFile, UnitMod, + IndexCtx.getSysrootPath()); +} + +/// Checks if the unit file exists for module file, if it doesn't it generates +/// index data for it. +static bool produceIndexDataForModuleFile( + serialization::ModuleFile &Mod, + const CompilerInstance &CI, + IndexingOptions IndexOpts, + RecordingOptions RecordOpts, + IndexUnitWriter &ParentUnitWriter); + +static void writeUnitData(const CompilerInstance &CI, + IndexDataRecorder &Recorder, + IndexDependencyProvider &DepProvider, + IndexingOptions IndexOpts, + RecordingOptions RecordOpts, + StringRef OutputFile, + const FileEntry *RootFile, + Module *UnitModule, + StringRef SysrootPath) { + + SourceManager &SM = CI.getSourceManager(); + DiagnosticsEngine &Diag = CI.getDiagnostics(); + HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo(); + StringRef DataPath = RecordOpts.DataDirPath; + bool IsSystemUnit = UnitModule ? UnitModule->IsSystem : false; + bool IsModuleUnit = UnitModule != nullptr; + bool IsDebugCompilation = CI.getCodeGenOpts().OptimizationLevel == 0; + std::string ModuleName = UnitModule ? UnitModule->getFullModuleName() : std::string(); + + auto getModuleInfo = [](writer::OpaqueModule mod, SmallVectorImpl &Scratch) -> writer::ModuleInfo { + assert(mod); + writer::ModuleInfo info; + std::string fullName = static_cast(mod)->getFullModuleName(); + unsigned offset = Scratch.size(); + Scratch.append(fullName.begin(), fullName.end()); + info.Name = StringRef(Scratch.data()+offset, fullName.size()); + return info; + }; + + auto findModuleForHeader = [&](const FileEntry *FE) -> Module * { + if (!UnitModule) + return nullptr; + if (auto Mod = HS.findModuleForHeader(FE).getModule()) + if (Mod->isSubModuleOf(UnitModule)) + return Mod; + return nullptr; + }; + + IndexUnitWriter UnitWriter(CI.getFileManager(), + DataPath, + "clang", getClangVersion(), + OutputFile, + ModuleName, + RootFile, + IsSystemUnit, + IsModuleUnit, + IsDebugCompilation, + CI.getTargetOpts().Triple, + SysrootPath, + getModuleInfo); + + DepProvider.visitFileDependencies(CI, [&](const FileEntry *FE, bool isSystemFile) { + UnitWriter.addFileDependency(FE, isSystemFile, findModuleForHeader(FE)); + }); + DepProvider.visitIncludes([&](const FileEntry *Source, unsigned Line, const FileEntry *Target) { + UnitWriter.addInclude(Source, Line, Target); + }); + DepProvider.visitModuleImports(CI, [&](serialization::ModuleFile &Mod, bool isSystemMod) { + Module *UnitMod = HS.lookupModule(Mod.ModuleName, /*AllowSearch=*/false); + UnitWriter.addASTFileDependency(Mod.File, isSystemMod, UnitMod); + if (Mod.isModule()) { + produceIndexDataForModuleFile(Mod, CI, IndexOpts, RecordOpts, UnitWriter); + } + }); + + ClangIndexRecordWriter RecordWriter(CI.getASTContext(), RecordOpts); + for (auto I = Recorder.record_begin(), E = Recorder.record_end(); I != E; ++I) { + FileID FID = I->first; + const FileIndexRecord &Rec = *I->second; + const FileEntry *FE = SM.getFileEntryForID(FID); + std::string RecordFile; + std::string Error; + + if (RecordWriter.writeRecord(FE->getName(), Rec, Error, &RecordFile)) { + unsigned DiagID = Diag.getCustomDiagID(DiagnosticsEngine::Error, + "failed writing record '%0': %1"); + Diag.Report(DiagID) << RecordFile << Error; + return; + } + UnitWriter.addRecordFile(RecordFile, FE, Rec.isSystem(), + findModuleForHeader(FE)); + } + + std::string Error; + if (UnitWriter.write(Error)) { + unsigned DiagID = Diag.getCustomDiagID(DiagnosticsEngine::Error, + "failed writing unit data: %0"); + Diag.Report(DiagID) << Error; + return; + } +} + +namespace { +class ModuleFileIndexDependencyCollector : public IndexDependencyProvider { + serialization::ModuleFile &ModFile; + RecordingOptions RecordOpts; + +public: + ModuleFileIndexDependencyCollector(serialization::ModuleFile &Mod, + RecordingOptions recordOpts) + : ModFile(Mod), RecordOpts(recordOpts) {} + + void visitFileDependencies(const CompilerInstance &CI, + llvm::function_ref visitor) override { + auto Reader = CI.getModuleManager(); + Reader->visitInputFiles(ModFile, RecordOpts.RecordSystemDependencies, + /*Complain=*/false, + [&](const serialization::InputFile &IF, bool isSystem) { + auto *FE = IF.getFile(); + if (!FE) + return; + // Ignore module map files, they are not as important to track as source + // files and they may be auto-generated which would create an undesirable + // dependency on an intermediate build byproduct. + if (FE->getName().endswith("module.modulemap")) + return; + + visitor(FE, isSystem); + }); + } + + void visitIncludes( + llvm::function_ref visitor) override { + // FIXME: Module files without a preprocessing record do not have info about + // include locations. Serialize enough data to be able to retrieve such info. + } + + void visitModuleImports(const CompilerInstance &CI, + llvm::function_ref visitor) override { + HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo(); + for (auto *Mod : ModFile.Imports) { + bool isSystemMod = false; + if (auto *M = HS.lookupModule(Mod->ModuleName, /*AllowSearch=*/false)) + isSystemMod = M->IsSystem; + if (!isSystemMod || RecordOpts.RecordSystemDependencies) + visitor(*Mod, isSystemMod); + } + } +}; +} // anonymous namespace. + +static void indexModule(serialization::ModuleFile &Mod, + const CompilerInstance &CI, + IndexingOptions IndexOpts, + RecordingOptions RecordOpts) { + DiagnosticsEngine &Diag = CI.getDiagnostics(); + Diag.Report(Mod.ImportLoc, diag::remark_index_producing_module_file_data) + << Mod.FileName; + + StringRef SysrootPath = CI.getHeaderSearchOpts().Sysroot; + HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo(); + Module *UnitMod = HS.lookupModule(Mod.ModuleName, /*AllowSearch=*/false); + + IndexDataRecorder Recorder; + IndexingContext IndexCtx(IndexOpts, Recorder); + + IndexCtx.setASTContext(CI.getASTContext()); + IndexCtx.setSysrootPath(SysrootPath); + Recorder.init(&IndexCtx, CI); + + for (const Decl *D : CI.getModuleManager()->getModuleFileLevelDecls(Mod)) { + IndexCtx.indexTopLevelDecl(D); + } + Recorder.finish(); + + ModuleFileIndexDependencyCollector DepCollector(Mod, RecordOpts); + writeUnitData(CI, Recorder, DepCollector, IndexOpts, RecordOpts, + Mod.FileName, /*RootFile=*/nullptr, UnitMod, SysrootPath); + +} + +static bool produceIndexDataForModuleFile( + serialization::ModuleFile &Mod, + const CompilerInstance &CI, + IndexingOptions IndexOpts, + RecordingOptions RecordOpts, + IndexUnitWriter &ParentUnitWriter) { + DiagnosticsEngine &Diag = CI.getDiagnostics(); + std::string Error; + // We don't do timestamp check with the PCM file, on purpose. The PCM may get + // touched for various reasons which would cause unnecessary work to emit + // index data. User modules normally will get rebuilt and their index data + // re-emitted, and system modules are generally stable (and they can also can + // get rebuilt along with their index data). + auto IsUptodateOpt = ParentUnitWriter.isUnitUpToDateForOutputFile(Mod.FileName, None, Error); + if (!IsUptodateOpt.hasValue()) { + unsigned DiagID = Diag.getCustomDiagID(DiagnosticsEngine::Error, + "failed file status check: %0"); + Diag.Report(DiagID) << Error; + return false; + } + if (*IsUptodateOpt) + return false; + + indexModule(Mod, CI, IndexOpts, RecordOpts); + return true; +} + +static std::unique_ptr +createIndexDataRecordingAction(IndexingOptions IndexOpts, + RecordingOptions RecordOpts, + std::unique_ptr WrappedAction) { + if (WrappedAction) + return llvm::make_unique(std::move(WrappedAction), + std::move(IndexOpts), + std::move(RecordOpts)); + return llvm::make_unique(std::move(IndexOpts), + std::move(RecordOpts)); +} + +static std::pair +getIndexOptionsFromFrontendOptions(const FrontendOptions &FEOpts) { + index::IndexingOptions IndexOpts; + index::RecordingOptions RecordOpts; + RecordOpts.DataDirPath = FEOpts.IndexStorePath; + if (FEOpts.IndexIgnoreSystemSymbols) { + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::None; + } + RecordOpts.RecordSymbolCodeGenName = FEOpts.IndexRecordCodegenName; + return { IndexOpts, RecordOpts }; +} + +std::unique_ptr +index::createIndexDataRecordingAction(const FrontendOptions &FEOpts, + std::unique_ptr WrappedAction) { + index::IndexingOptions IndexOpts; + index::RecordingOptions RecordOpts; + std::tie(IndexOpts, RecordOpts) = getIndexOptionsFromFrontendOptions(FEOpts); + return ::createIndexDataRecordingAction(IndexOpts, RecordOpts, + std::move(WrappedAction)); +} + +bool index::emitIndexDataForModuleFile(const Module *Mod, + const CompilerInstance &CI, + IndexUnitWriter &ParentUnitWriter) { + index::IndexingOptions IndexOpts; + index::RecordingOptions RecordOpts; + std::tie(IndexOpts, RecordOpts) = getIndexOptionsFromFrontendOptions(CI.getFrontendOpts()); + + auto astReader = CI.getModuleManager(); + serialization::ModuleFile *ModFile = astReader->getModuleManager().lookup(Mod->getASTFile()); + assert(ModFile && "no module file loaded for module ?"); + return produceIndexDataForModuleFile(*ModFile, CI, IndexOpts, RecordOpts, ParentUnitWriter); +} diff --git a/clang/lib/Index/IndexingContext.cpp b/clang/lib/Index/IndexingContext.cpp index 754bc84ff4b2a..d2d2f95a46853 100644 --- a/clang/lib/Index/IndexingContext.cpp +++ b/clang/lib/Index/IndexingContext.cpp @@ -93,12 +93,7 @@ bool IndexingContext::importedModule(const ImportDecl *ImportD) { if (FID.isInvalid()) return true; - bool Invalid = false; - const SrcMgr::SLocEntry &SEntry = SM.getSLocEntry(FID, &Invalid); - if (Invalid || !SEntry.isFile()) - return true; - - if (SEntry.getFile().getFileCharacteristic() != SrcMgr::C_User) { + if (isSystemFile(FID)) { switch (IndexOpts.SystemSymbolFilter) { case IndexingOptions::SystemSymbolFilterKind::None: return true; @@ -161,6 +156,56 @@ bool IndexingContext::shouldIgnoreIfImplicit(const Decl *D) { return true; } +void IndexingContext::setSysrootPath(StringRef path) { + // Ignore sysroot path if it points to root, otherwise every header will be + // treated as system one. + if (path == "/") + path = StringRef(); + SysrootPath = path; +} + +bool IndexingContext::isSystemFile(FileID FID) { + if (LastFileCheck.first == FID) + return LastFileCheck.second; + + auto result = [&](bool res) -> bool { + LastFileCheck = { FID, res }; + return res; + }; + + bool Invalid = false; + const SrcMgr::SLocEntry &SEntry = + Ctx->getSourceManager().getSLocEntry(FID, &Invalid); + if (Invalid || !SEntry.isFile()) + return result(false); + + const SrcMgr::FileInfo &FI = SEntry.getFile(); + if (FI.getFileCharacteristic() != SrcMgr::C_User) + return result(true); + + auto *CC = FI.getContentCache(); + if (!CC) + return result(false); + auto *FE = CC->OrigEntry; + if (!FE) + return result(false); + + if (SysrootPath.empty()) + return result(false); + + // Check if directory is in sysroot so that we can consider system headers + // even the headers found via a user framework search path, pointing inside + // sysroot. + auto dirEntry = FE->getDir(); + auto pair = DirEntries.insert(std::make_pair(dirEntry, false)); + bool &isSystemDir = pair.first->second; + bool wasInserted = pair.second; + if (wasInserted) { + isSystemDir = StringRef(dirEntry->getName()).startswith(SysrootPath); + } + return result(isSystemDir); +} + static const CXXRecordDecl * getDeclContextForTemplateInstationPattern(const Decl *D) { if (const auto *CTSD = @@ -313,7 +358,7 @@ bool IndexingContext::handleDeclOccurrence(const Decl *D, SourceLocation Loc, const Expr *OrigE, const Decl *OrigD, const DeclContext *ContainerDC) { - if (D->isImplicit() && !isa(D)) + if (D->isImplicit() && !(isa(D) || isa(D))) return true; if (!isa(D) || (cast(D)->getDeclName().isEmpty() && @@ -331,12 +376,7 @@ bool IndexingContext::handleDeclOccurrence(const Decl *D, SourceLocation Loc, if (FID.isInvalid()) return true; - bool Invalid = false; - const SrcMgr::SLocEntry &SEntry = SM.getSLocEntry(FID, &Invalid); - if (Invalid || !SEntry.isFile()) - return true; - - if (SEntry.getFile().getFileCharacteristic() != SrcMgr::C_User) { + if (isSystemFile(FID)) { switch (IndexOpts.SystemSymbolFilter) { case IndexingOptions::SystemSymbolFilterKind::None: return true; diff --git a/clang/lib/Index/IndexingContext.h b/clang/lib/Index/IndexingContext.h index 566651c83a75f..70e72e1d28cc7 100644 --- a/clang/lib/Index/IndexingContext.h +++ b/clang/lib/Index/IndexingContext.h @@ -11,9 +11,11 @@ #define LLVM_CLANG_LIB_INDEX_INDEXINGCONTEXT_H #include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/IndexingAction.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" namespace clang { class ASTContext; @@ -29,7 +31,7 @@ namespace clang { class Stmt; class Expr; class TypeLoc; - class SourceLocation; + class DirectoryEntry; namespace index { class IndexDataConsumer; @@ -38,6 +40,13 @@ class IndexingContext { IndexingOptions IndexOpts; IndexDataConsumer &DataConsumer; ASTContext *Ctx = nullptr; + std::string SysrootPath; + + // Records whether a directory entry is system or not. + llvm::DenseMap DirEntries; + // Keeps track of the last check for whether a FileID is system or + // not. This is used to speed up isSystemFile() call. + std::pair LastFileCheck; public: IndexingContext(IndexingOptions IndexOpts, IndexDataConsumer &DataConsumer) @@ -47,6 +56,10 @@ class IndexingContext { IndexDataConsumer &getDataConsumer() { return DataConsumer; } void setASTContext(ASTContext &ctx) { Ctx = &ctx; } + void setSysrootPath(StringRef path); + StringRef getSysrootPath() const { return SysrootPath; } + + bool isSystemFile(FileID FID); bool shouldIndex(const Decl *D); diff --git a/clang/test/Index/Store/Inputs/head.h b/clang/test/Index/Store/Inputs/head.h new file mode 100644 index 0000000000000..6ac174dd19e6c --- /dev/null +++ b/clang/test/Index/Store/Inputs/head.h @@ -0,0 +1,3 @@ + +extern void test1_func(void); +extern void test2_func(void); diff --git a/clang/test/Index/Store/Inputs/json.c.json b/clang/test/Index/Store/Inputs/json.c.json new file mode 100644 index 0000000000000..498022d230855 --- /dev/null +++ b/clang/test/Index/Store/Inputs/json.c.json @@ -0,0 +1,151 @@ +{ + "files": [ + "/test1.o", + "/Inputs/test1.c", + "/Inputs/head.h", + "/test2.o", + "/Inputs/test2.c", + "/test3.o", + "/Inputs/test3.cpp" + ], + "symbols": [ + { + "kind": "function", + "lang": "C", + "usr": "c:@F@test1_func", + "name": "test1_func", + "codegen": "_test1_func", + "roles": "Decl,Def" + }, + { + "kind": "function", + "lang": "C", + "usr": "c:@F@test2_func", + "name": "test2_func", + "codegen": "_test2_func", + "roles": "Decl,Def" + }, + { + "kind": "class", + "lang": "C++", + "usr": "c:@S@Base", + "name": "Base", + "roles": "Def,Ref,RelBase,RelCont" + }, + { + "kind": "class", + "lang": "C++", + "usr": "c:@S@Sub", + "name": "Sub", + "roles": "Def", + "rel-roles": "RelBase,RelCont" + } + ], + "records": [ + { + "occurrences": [ + { + "symbol": 0, + "line": 3, + "col": 6, + "roles": "Def" + } + ] + }, + { + "occurrences": [ + { + "symbol": 0, + "line": 2, + "col": 13, + "roles": "Decl" + }, + { + "symbol": 1, + "line": 3, + "col": 13, + "roles": "Decl" + } + ] + }, + { + "occurrences": [ + { + "symbol": 1, + "line": 3, + "col": 6, + "roles": "Def" + } + ] + }, + { + "occurrences": [ + { + "symbol": 2, + "line": 1, + "col": 7, + "roles": "Def" + }, + { + "symbol": 3, + "line": 2, + "col": 7, + "roles": "Def" + }, + { + "symbol": 2, + "line": 2, + "col": 20, + "roles": "Ref,RelBase,RelCont", + "relations": [ + { + "symbol": 3, + "rel-roles": "RelBase,RelCont" + } + ] + + } + ] + } + ], + "units": [ + { + "triple": "x86_64-apple-macosx10.7.0", + "out-file": 0, + "sources": [ + { + "file": 1, + "records": [0] + }, + { + "file": 2, + "records": [1] + } + ] + }, + { + "triple": "x86_64-apple-macosx10.7.0", + "out-file": 3, + "sources": [ + { + "file": 4, + "records": [2] + }, + { + "file": 2, + "records": [1] + } + ] + }, + { + "triple": "x86_64-apple-macosx10.7.0", + "out-file": 5, + "sources": [ + { + "file": 6, + "records": [3] + } + ] + } + ] +} diff --git a/clang/test/Index/Store/Inputs/module/ModDep.h b/clang/test/Index/Store/Inputs/module/ModDep.h new file mode 100644 index 0000000000000..e96ef5440f43a --- /dev/null +++ b/clang/test/Index/Store/Inputs/module/ModDep.h @@ -0,0 +1,3 @@ +#include "ModTop.h" + +void ModDep_func(ModTopStruct s); diff --git a/clang/test/Index/Store/Inputs/module/ModSystem.h b/clang/test/Index/Store/Inputs/module/ModSystem.h new file mode 100644 index 0000000000000..0419f97804b5d --- /dev/null +++ b/clang/test/Index/Store/Inputs/module/ModSystem.h @@ -0,0 +1,4 @@ + +typedef struct {} ModSystemStruct; + +void ModSystem_func(void); diff --git a/clang/test/Index/Store/Inputs/module/ModTop.h b/clang/test/Index/Store/Inputs/module/ModTop.h new file mode 100644 index 0000000000000..60c56868bbffe --- /dev/null +++ b/clang/test/Index/Store/Inputs/module/ModTop.h @@ -0,0 +1,4 @@ + +typedef struct {} ModTopStruct; + +void ModTop_func(void); diff --git a/clang/test/Index/Store/Inputs/module/ModTopSub1.h b/clang/test/Index/Store/Inputs/module/ModTopSub1.h new file mode 100644 index 0000000000000..e1e3cf3ec54bc --- /dev/null +++ b/clang/test/Index/Store/Inputs/module/ModTopSub1.h @@ -0,0 +1 @@ +void ModTopSub1_func(void); diff --git a/clang/test/Index/Store/Inputs/module/ModTopSub2.h b/clang/test/Index/Store/Inputs/module/ModTopSub2.h new file mode 100644 index 0000000000000..39d37f12f0e1b --- /dev/null +++ b/clang/test/Index/Store/Inputs/module/ModTopSub2.h @@ -0,0 +1 @@ +// This header has no symbols, intended to show up as file dependency. diff --git a/clang/test/Index/Store/Inputs/module/module.modulemap b/clang/test/Index/Store/Inputs/module/module.modulemap new file mode 100644 index 0000000000000..ada2f38ef76e9 --- /dev/null +++ b/clang/test/Index/Store/Inputs/module/module.modulemap @@ -0,0 +1,12 @@ +module ModTop { + header "ModTop.h" + export * + module Sub1 { + header "ModTopSub1.h" + } + module Sub2 { + header "ModTopSub2.h" + } +} +module ModDep { header "ModDep.h" export * } +module ModSystem [system] { header "ModSystem.h" export * } diff --git a/clang/test/Index/Store/Inputs/overlay.yaml b/clang/test/Index/Store/Inputs/overlay.yaml new file mode 100644 index 0000000000000..7b55b30f4bedd --- /dev/null +++ b/clang/test/Index/Store/Inputs/overlay.yaml @@ -0,0 +1,6 @@ +{ + 'version': 0, + 'roots': [{ 'type': 'file', 'name': 'OUT_DIR/using-overlay.h', + 'external-contents': 'INPUT_DIR/using-overlay.h' + }] +} diff --git a/clang/test/Index/Store/Inputs/print-unit.h b/clang/test/Index/Store/Inputs/print-unit.h new file mode 100644 index 0000000000000..62039c47219b5 --- /dev/null +++ b/clang/test/Index/Store/Inputs/print-unit.h @@ -0,0 +1,2 @@ +#include "head.h" +#include "using-overlay.h" diff --git a/clang/test/Index/Store/Inputs/sys/another.h b/clang/test/Index/Store/Inputs/sys/another.h new file mode 100644 index 0000000000000..555b99b0ce36f --- /dev/null +++ b/clang/test/Index/Store/Inputs/sys/another.h @@ -0,0 +1,2 @@ + +extern void sys_another_func(void); diff --git a/clang/test/Index/Store/Inputs/sys/syshead.h b/clang/test/Index/Store/Inputs/sys/syshead.h new file mode 100644 index 0000000000000..8941fd6997af7 --- /dev/null +++ b/clang/test/Index/Store/Inputs/sys/syshead.h @@ -0,0 +1,4 @@ + +#include "another.h" + +extern void sys_test1_func(void); diff --git a/clang/test/Index/Store/Inputs/test1.c b/clang/test/Index/Store/Inputs/test1.c new file mode 100644 index 0000000000000..505711d181d34 --- /dev/null +++ b/clang/test/Index/Store/Inputs/test1.c @@ -0,0 +1,3 @@ +#include "head.h" + +void test1_func(void) {} diff --git a/clang/test/Index/Store/Inputs/test2.c b/clang/test/Index/Store/Inputs/test2.c new file mode 100644 index 0000000000000..333b8aef67d52 --- /dev/null +++ b/clang/test/Index/Store/Inputs/test2.c @@ -0,0 +1,3 @@ +#include "head.h" + +void test2_func(void) {} diff --git a/clang/test/Index/Store/Inputs/test3.cpp b/clang/test/Index/Store/Inputs/test3.cpp new file mode 100644 index 0000000000000..06334a1706b41 --- /dev/null +++ b/clang/test/Index/Store/Inputs/test3.cpp @@ -0,0 +1,2 @@ +class Base {}; +class Sub : public Base {}; diff --git a/clang/test/Index/Store/Inputs/using-overlay.h b/clang/test/Index/Store/Inputs/using-overlay.h new file mode 100644 index 0000000000000..bb361c3d58285 --- /dev/null +++ b/clang/test/Index/Store/Inputs/using-overlay.h @@ -0,0 +1 @@ +void using_overlay(void); diff --git a/clang/test/Index/Store/assembly-invocation.c b/clang/test/Index/Store/assembly-invocation.c new file mode 100644 index 0000000000000..ab9c197a5391b --- /dev/null +++ b/clang/test/Index/Store/assembly-invocation.c @@ -0,0 +1,3 @@ +// Make sure it doesn't crash. +// RUN: %clang -target x86_64-apple-macosx10.7 -S %s -o %t.s +// RUN: env CLANG_PROJECT_INDEX_PATH=%t.idx %clang -target x86_64-apple-macosx10.7 -c %t.s -o %t.o diff --git a/clang/test/Index/Store/external-source-symbol-hash.m b/clang/test/Index/Store/external-source-symbol-hash.m new file mode 100644 index 0000000000000..243616d94854f --- /dev/null +++ b/clang/test/Index/Store/external-source-symbol-hash.m @@ -0,0 +1,47 @@ +// RUN: rm -rf %t.idx +// RUN: %clang_cc1 %s -index-store-path %t.idx -D USE_EXTERNAL +// RUN: c-index-test core -print-record %t.idx | FileCheck %s +// RUN: %clang_cc1 %s -index-store-path %t.idx +// RUN: find %t.idx/*/records -name "external-source-symbol-hash*" | count 2 + +#ifdef USE_EXTERNAL +# define EXT_DECL(mod_name) __attribute__((external_source_symbol(language="Swift", defined_in=mod_name))) +#else +# define EXT_DECL(mod_name) +#endif + +#define NS_ENUM(_name, _type) enum _name:_type _name; enum _name : _type + +// Forward declarations should pick up the attribute from later decls +@protocol P1; +// CHECK: [[@LINE-1]]:11 | protocol/Swift | c:@M@some_module@objc(pl)P1 | Ref | rel: 0 +@class I2; +// CHECK: [[@LINE-1]]:8 | class/Swift | c:@M@other_module@objc(cs)I2 | Ref | rel: 0 +enum E3: int; +// CHECK: [[@LINE-1]]:6 | enum/Swift | c:@M@third_module@E@E3 | Ref | rel: 0 + +void test(id first, I2 *second, enum E3 third) {} +// CHECK: [[@LINE-1]]:14 | protocol/Swift | c:@M@some_module@objc(pl)P1 | Ref,RelCont | rel: 1 +// CHECK: [[@LINE-2]]:25 | class/Swift | c:@M@other_module@objc(cs)I2 | Ref,RelCont | rel: 1 +// CHECK: [[@LINE-3]]:42 | enum/Swift | c:@M@third_module@E@E3 | Ref,RelCont | rel: 1 + +EXT_DECL("some_module") +@protocol P1 +// CHECK: [[@LINE-1]]:11 | protocol/Swift | c:@M@some_module@objc(pl)P1 | Decl | rel: 0 +-(void)method; +// CHECK: [[@LINE-1]]:8 | instance-method/Swift | c:@M@some_module@objc(pl)P1(im)method | Decl,Dyn,RelChild | rel: 1 +@end + +EXT_DECL("other_module") +@interface I2 +// CHECK: [[@LINE-1]]:12 | class/Swift | c:@M@other_module@objc(cs)I2 | Decl | rel: 0 +-(void)method; +// CHECK: [[@LINE-1]]:8 | instance-method/Swift | c:@M@other_module@objc(cs)I2(im)method | Decl,Dyn,RelChild | rel: 1 +@end + + +typedef NS_ENUM(E3, int) { +// CHECK: [[@LINE-1]]:17 | enum/Swift | c:@M@third_module@E@E3 | Def | rel: 0 + firstCase = 1, + // CHECK: [[@LINE-1]]:3 | enumerator/Swift | c:@M@third_module@E@E3@firstCase | Def,RelChild | rel: 1 +} EXT_DECL("third_module"); diff --git a/clang/test/Index/Store/handle-prebuilt-module.m b/clang/test/Index/Store/handle-prebuilt-module.m new file mode 100644 index 0000000000000..f6a0c42a8e8be --- /dev/null +++ b/clang/test/Index/Store/handle-prebuilt-module.m @@ -0,0 +1,25 @@ +// XFAIL: linux + +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -fsyntax-only %s -o %t.o -index-store-path %t/idx1 -fmodules -fmodules-cache-path=%t/mcp -I %S/Inputs/module -Rindex-store 2> %t.err1 +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -fsyntax-only %s -o %t.o -index-store-path %t/idx2 -fmodules -fmodules-cache-path=%t/mcp -I %S/Inputs/module -Rindex-store 2> %t.err2 +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -fsyntax-only %s -o %t.o -index-store-path %t/idx2 -fmodules -fmodules-cache-path=%t/mcp -I %S/Inputs/module -Rindex-store 2> %t.err3 +// RUN: FileCheck -input-file=%t.err1 -check-prefix=CREATING_MODULES %s -allow-empty +// RUN: FileCheck -input-file=%t.err2 -check-prefix=CREATING_INDEX_DATA_FROM_MODULE_FILES %s +// RUN: FileCheck -input-file=%t.err3 -check-prefix=EXISTING_INDEX_DATA_FROM_MODULE_FILES %s -allow-empty +// RUN: c-index-test core -print-unit %t/idx1 > %t/all-units1.txt +// RUN: c-index-test core -print-unit %t/idx2 > %t/all-units2.txt +// RUN: c-index-test core -print-record %t/idx1 > %t/all-records1.txt +// RUN: c-index-test core -print-record %t/idx2 > %t/all-records2.txt +// RUN: diff -u %t/all-units1.txt %t/all-units2.txt +// RUN: diff -u %t/all-records1.txt %t/all-records2.txt + +@import ModDep; + +// CREATING_MODULES-NOT: remark: + +// CREATING_INDEX_DATA_FROM_MODULE_FILES: remark: producing index data for module file {{.*}}ModDep{{.*}}.pcm +// CREATING_INDEX_DATA_FROM_MODULE_FILES: remark: producing index data for module file {{.*}}ModTop{{.*}}.pcm + +// EXISTING_INDEX_DATA_FROM_MODULE_FILES-NOT: remark: diff --git a/clang/test/Index/Store/json-with-module.m b/clang/test/Index/Store/json-with-module.m new file mode 100644 index 0000000000000..1ef69697749a5 --- /dev/null +++ b/clang/test/Index/Store/json-with-module.m @@ -0,0 +1,9 @@ +// RUN: rm -rf %t.idx %t.mcp +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -c %s -o %t.o -index-store-path %t.idx -fmodules -fmodules-cache-path=%t.mcp -Xclang -fdisable-module-hash -I %S/Inputs/module +// RUN: c-index-test core -aggregate-json %t.idx -o %t.json +// RUN: sed -e "s:%S::g" -e "s:%T::g" %t.json > %t.final.json +// RUN: diff -u %s.json %t.final.json + +// XFAIL: linux + +@import ModDep; diff --git a/clang/test/Index/Store/json-with-module.m.json b/clang/test/Index/Store/json-with-module.m.json new file mode 100644 index 0000000000000..bb020cc2ccd2f --- /dev/null +++ b/clang/test/Index/Store/json-with-module.m.json @@ -0,0 +1,151 @@ +{ + "files": [ + "/json-with-module.m.tmp.mcp/ModDep.pcm", + "/json-with-module.m.tmp.mcp/ModTop.pcm", + "/Inputs/module/ModDep.h", + "/Inputs/module/ModTop.h", + "/Inputs/module/ModTopSub1.h", + "/Inputs/module/ModTopSub2.h", + "/json-with-module.m.tmp.o", + "/json-with-module.m", + "/Inputs/module/module.modulemap" + ], + "symbols": [ + { + "kind": "function", + "lang": "C", + "usr": "c:@F@ModDep_func", + "name": "ModDep_func", + "roles": "Decl", + "rel-roles": "RelCont" + }, + { + "kind": "type-alias", + "lang": "C", + "usr": "c:ModTop.h@T@ModTopStruct", + "name": "ModTopStruct", + "roles": "Def,Ref,RelCont" + }, + { + "kind": "struct", + "lang": "C", + "usr": "c:@SA@ModTopStruct", + "name": "", + "roles": "Def" + }, + { + "kind": "function", + "lang": "C", + "usr": "c:@F@ModTop_func", + "name": "ModTop_func", + "roles": "Decl" + }, + { + "kind": "function", + "lang": "C", + "usr": "c:@F@ModTopSub1_func", + "name": "ModTopSub1_func", + "roles": "Decl" + } + ], + "records": [ + { + "occurrences": [ + { + "symbol": 0, + "line": 3, + "col": 6, + "roles": "Decl" + }, + { + "symbol": 1, + "line": 3, + "col": 18, + "roles": "Ref,RelCont", + "relations": [ + { + "symbol": 0, + "rel-roles": "RelCont" + } + ] + + } + ] + }, + { + "occurrences": [ + { + "symbol": 2, + "line": 2, + "col": 9, + "roles": "Def" + }, + { + "symbol": 1, + "line": 2, + "col": 19, + "roles": "Def" + }, + { + "symbol": 3, + "line": 4, + "col": 6, + "roles": "Decl" + } + ] + }, + { + "occurrences": [ + { + "symbol": 4, + "line": 1, + "col": 6, + "roles": "Decl" + } + ] + } + ], + "units": [ + { + "triple": "x86_64-apple-macosx10.7.0", + "out-file": 0, + "unit-dependencies": [1], + "sources": [ + { + "file": 2, + "records": [0] + } + ] + }, + { + "triple": "x86_64-apple-macosx10.7.0", + "out-file": 1, + "sources": [ + { + "file": 3, + "records": [1] + }, + { + "file": 4, + "records": [2] + }, + { + "file": 5 + } + ] + }, + { + "triple": "x86_64-apple-macosx10.7.0", + "out-file": 6, + "unit-dependencies": [0], + "sources": [ + { + "file": 7 + }, + { + "file": 8 + } + ] + } + ] +} diff --git a/clang/test/Index/Store/json-with-pch.c b/clang/test/Index/Store/json-with-pch.c new file mode 100644 index 0000000000000..9ffe80f02c340 --- /dev/null +++ b/clang/test/Index/Store/json-with-pch.c @@ -0,0 +1,10 @@ +// RUN: rm -rf %t.idx +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -x c-header %S/Inputs/head.h -o %t.h.pch -index-store-path %t.idx +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -c %s -o %t.o -index-store-path %t.idx -include %t.h -Werror +// RUN: c-index-test core -aggregate-json %t.idx -o %t.json +// RUN: sed -e "s:%S::g" -e "s:%T::g" %t.json > %t.final.json +// RUN: diff -u %s.json %t.final.json +// XFAIL: linux +int main() { + test1_func(); +} diff --git a/clang/test/Index/Store/json-with-pch.c.json b/clang/test/Index/Store/json-with-pch.c.json new file mode 100644 index 0000000000000..605f33efd9573 --- /dev/null +++ b/clang/test/Index/Store/json-with-pch.c.json @@ -0,0 +1,96 @@ +{ + "files": [ + "/json-with-pch.c.tmp.h.pch", + "/Inputs/head.h", + "/json-with-pch.c.tmp.o", + "/json-with-pch.c" + ], + "symbols": [ + { + "kind": "function", + "lang": "C", + "usr": "c:@F@test1_func", + "name": "test1_func", + "roles": "Decl,Ref,Call,RelCall,RelCont" + }, + { + "kind": "function", + "lang": "C", + "usr": "c:@F@test2_func", + "name": "test2_func", + "roles": "Decl" + }, + { + "kind": "function", + "lang": "C", + "usr": "c:@F@main", + "name": "main", + "roles": "Def", + "rel-roles": "RelCall,RelCont" + } + ], + "records": [ + { + "occurrences": [ + { + "symbol": 0, + "line": 2, + "col": 13, + "roles": "Decl" + }, + { + "symbol": 1, + "line": 3, + "col": 13, + "roles": "Decl" + } + ] + }, + { + "occurrences": [ + { + "symbol": 2, + "line": 8, + "col": 5, + "roles": "Def" + }, + { + "symbol": 0, + "line": 9, + "col": 3, + "roles": "Ref,Call,RelCall,RelCont", + "relations": [ + { + "symbol": 2, + "rel-roles": "RelCall,RelCont" + } + ] + + } + ] + } + ], + "units": [ + { + "triple": "x86_64-apple-macosx10.7.0", + "out-file": 0, + "sources": [ + { + "file": 1, + "records": [0] + } + ] + }, + { + "triple": "x86_64-apple-macosx10.7.0", + "out-file": 2, + "unit-dependencies": [0], + "sources": [ + { + "file": 3, + "records": [1] + } + ] + } + ] +} diff --git a/clang/test/Index/Store/json.c b/clang/test/Index/Store/json.c new file mode 100644 index 0000000000000..c4ea965d36b1c --- /dev/null +++ b/clang/test/Index/Store/json.c @@ -0,0 +1,10 @@ +// RUN: rm -rf %t.idx +// RUN: mkdir -p %t.o +// RUN: env CLANG_PROJECT_INDEX_PATH=%t.idx %clang -target x86_64-apple-macosx10.7 -arch x86_64 -mmacosx-version-min=10.7 -c %S/Inputs/test1.c -o %t.o/test1.o +// RUN: env CLANG_PROJECT_INDEX_PATH=%t.idx %clang -target x86_64-apple-macosx10.7 -arch x86_64 -mmacosx-version-min=10.7 -c %S/Inputs/test2.c -o %t.o/test2.o +// RUN: env CLANG_PROJECT_INDEX_PATH=%t.idx %clang -target x86_64-apple-macosx10.7 -arch x86_64 -mmacosx-version-min=10.7 -c %S/Inputs/test3.cpp -o %t.o/test3.o +// RUN: c-index-test core -aggregate-json %t.idx -o %t.json +// RUN: sed -e "s:%S::g" -e "s:%t.o::g" %t.json > %t.final.json +// RUN: diff -u %S/Inputs/json.c.json %t.final.json + +// XFAIL: linux diff --git a/clang/test/Index/Store/print-record.mm b/clang/test/Index/Store/print-record.mm new file mode 100644 index 0000000000000..ce24983503a6d --- /dev/null +++ b/clang/test/Index/Store/print-record.mm @@ -0,0 +1,28 @@ +// RUN: rm -rf %t.idx +// RUN: %clang_cc1 %s -index-store-path %t.idx +// RUN: c-index-test core -print-record %t.idx | FileCheck %s + +// XFAIL: linux + +@class MyCls; + +@interface MyCls +@end + +// CHECK: [[@LINE+2]]:6 | function/C | c:@F@foo#*$objc(cs)MyCls# | Decl | rel: 0 +// CHECK: [[@LINE+1]]:10 | class/ObjC | c:objc(cs)MyCls | Ref,RelCont | rel: 1 +void foo(MyCls *p); + + +// RANGE-NOT: before_range +void before_range(); + +// RANGE: [[@LINE+1]]:6 | function/C | c:@F@in_range1# | Decl +void in_range1(); +// RANGE: [[@LINE+1]]:6 | function/C | c:@F@in_range2# | Decl +void in_range2(); + +// RANGE-NOT: after_range +void after_range(); + +// RUN: c-index-test core -print-record %t.idx -filepath %s:21:23 | FileCheck -check-prefix=RANGE %s diff --git a/clang/test/Index/Store/print-unit.c b/clang/test/Index/Store/print-unit.c new file mode 100644 index 0000000000000..19254a1665d4e --- /dev/null +++ b/clang/test/Index/Store/print-unit.c @@ -0,0 +1,39 @@ +// XFAIL: linux + +#include "print-unit.h" +#include "syshead.h" + +void foo(int i); + +// RUN: rm -rf %t +// RUN: %clang_cc1 -I %S/Inputs -isystem %S/Inputs/sys -index-store-path %t/idx %s -triple x86_64-apple-macosx10.8 +// RUN: c-index-test core -print-unit %t/idx | FileCheck %s +// RUN: %clang_cc1 -I %S/Inputs -isystem %S/Inputs/sys -index-store-path %t/idx_opt1 %s -triple x86_64-apple-macosx10.8 -O2 +// RUN: c-index-test core -print-unit %t/idx_opt1 | FileCheck %s -check-prefix=OPT +// RUN: %clang_cc1 -I %S/Inputs -isystem %S/Inputs/sys -index-store-path %t/idx_opt2 %s -triple x86_64-apple-macosx10.8 -Os +// RUN: c-index-test core -print-unit %t/idx_opt2 | FileCheck %s -check-prefix=OPT + +// CHECK: print-unit.c.o +// CHECK: provider: clang- +// CHECK: is-system: 0 +// CHECK: has-main: 1 +// CHECK: main-path: {{.*}}/print-unit.c +// CHECK: out-file: {{.*}}/print-unit.c.o +// CHECK: target: x86_64-apple-macosx10.8 +// CHECK: is-debug: 1 +// CHECK: DEPEND START +// CHECK: Record | user | {{.*}}/print-unit.c | print-unit.c- +// CHECK: Record | user | {{.*}}/Inputs/head.h | head.h- +// CHECK: Record | user | {{.*}}/Inputs/using-overlay.h | using-overlay.h- +// CHECK: Record | system | {{.*}}/Inputs/sys/syshead.h | syshead.h- +// CHECK: Record | system | {{.*}}/Inputs/sys/another.h | another.h- +// CHECK: File | user | {{.*}}/Inputs/print-unit.h | | {{[0-9]*$}} +// CHECK: DEPEND END (6) +// CHECK: INCLUDE START +// CHECK: {{.*}}/print-unit.c:3 | {{.*}}/Inputs/print-unit.h +// CHECK: {{.*}}/print-unit.c:4 | {{.*}}/Inputs/sys/syshead.h +// CHECK: {{.*}}/Inputs/print-unit.h:1 | {{.*}}/Inputs/head.h +// CHECK: {{.*}}/Inputs/print-unit.h:2 | {{.*}}/Inputs/using-overlay.h +// CHECK: INCLUDE END (4) + +// OPT: is-debug: 0 diff --git a/clang/test/Index/Store/print-units-with-modules.m b/clang/test/Index/Store/print-units-with-modules.m new file mode 100644 index 0000000000000..cedfe2ceb41e5 --- /dev/null +++ b/clang/test/Index/Store/print-units-with-modules.m @@ -0,0 +1,59 @@ +// RUN: rm -rf %t.idx %t.mcp +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -c %s -o %t.o -index-store-path %t.idx -fmodules -fmodules-cache-path=%t.mcp -Xclang -fdisable-module-hash -I %S/Inputs/module +// RUN: c-index-test core -print-unit %t.idx | FileCheck %s + +// XFAIL: linux + +@import ModDep; +@import ModSystem; + +// CHECK: ModDep.pcm +// CHECK: provider: clang- +// CHECK: is-system: 0 +// CHECK: is-module: 1 +// CHECK: module-name: ModDep +// CHECK: has-main: 0 +// CHECK: main-path: {{$}} +// CHECK: out-file: {{.*}}/ModDep.pcm +// CHECK: DEPEND START +// CHECK: Unit | user | ModTop | {{.*}}/ModTop.pcm | ModTop.pcm +// CHECK: Record | user | ModDep | {{.*}}/Inputs/module/ModDep.h | ModDep.h +// CHECK: DEPEND END (2) + +// CHECK: ModSystem.pcm +// CHECK: is-system: 1 +// CHECK: is-module: 1 +// CHECK: module-name: ModSystem +// CHECK: has-main: 0 +// CHECK: main-path: {{$}} +// CHECK: out-file: {{.*}}/ModSystem.pcm +// CHECK: DEPEND START +// CHECK: Record | system | ModSystem | {{.*}}/Inputs/module/ModSystem.h | ModSystem.h +// CHECK: DEPEND END (1) + +// CHECK: ModTop.pcm +// CHECK: is-system: 0 +// CHECK: is-module: 1 +// CHECK: module-name: ModTop +// CHECK: has-main: 0 +// CHECK: main-path: {{$}} +// CHECK: out-file: {{.*}}/ModTop.pcm +// CHECK: DEPEND START +// CHECK: Record | user | ModTop | {{.*}}/Inputs/module/ModTop.h | ModTop.h +// CHECK: Record | user | ModTop.Sub1 | {{.*}}/Inputs/module/ModTopSub1.h | ModTopSub1.h +// CHECK: File | user | ModTop.Sub2 | {{.*}}/Inputs/module/ModTopSub2.h | | {{[0-9]*$}} +// CHECK: DEPEND END (3) + +// CHECK: print-units-with-modules.m.tmp.o +// CHECK: is-system: 0 +// CHECK: is-module: 0 +// CHECK: module-name: +// CHECK: has-main: 1 +// CHECK: main-path: {{.*}}/print-units-with-modules.m +// CHECK: out-file: {{.*}}/print-units-with-modules.m.tmp.o +// CHECK: DEPEND START +// CHECK: Unit | user | ModDep | {{.*}}/ModDep.pcm | ModDep.pcm +// CHECK: Unit | system | ModSystem | {{.*}}/ModSystem.pcm | ModSystem.pcm +// CHECK: File | user | {{.*}}/print-units-with-modules.m | | {{[0-9]*$}} +// CHECK: File | user | {{.*}}/Inputs/module/module.modulemap | | {{[0-9]*$}} +// CHECK: DEPEND END (4) diff --git a/clang/test/Index/Store/print-units-with-pch.c b/clang/test/Index/Store/print-units-with-pch.c new file mode 100644 index 0000000000000..1e533a2eab7b1 --- /dev/null +++ b/clang/test/Index/Store/print-units-with-pch.c @@ -0,0 +1,29 @@ +// RUN: rm -rf %t.idx +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -x c-header %S/Inputs/head.h -o %t.h.pch -index-store-path %t.idx +// RUN: %clang -arch x86_64 -mmacosx-version-min=10.7 -c %s -o %t.o -index-store-path %t.idx -include %t.h -Werror +// RUN: c-index-test core -print-unit %t.idx | FileCheck %s + +// XFAIL: linux + +int main() { + test1_func(); +} + +// CHECK: print-units-with-pch.c.tmp.h.pch +// CHECK: is-system: 0 +// CHECK: has-main: 0 +// CHECK: main-path: {{$}} +// CHECK: out-file: {{.*}}/print-units-with-pch.c.tmp.h.pch +// CHECK: DEPEND START +// CHECK: Record | user | {{.*}}/Inputs/head.h | head.h +// CHECK: DEPEND END (1) + +// CHECK: print-units-with-pch.c.tmp.o +// CHECK: is-system: 0 +// CHECK: has-main: 1 +// CHECK: main-path: {{.*}}/print-units-with-pch.c +// CHECK: out-file: {{.*}}/print-units-with-pch.c.tmp.o +// CHECK: DEPEND START +// CHECK: Unit | user | {{.*}}/print-units-with-pch.c.tmp.h.pch | print-units-with-pch.c.tmp.h.pch +// CHECK: Record | user | {{.*}}/print-units-with-pch.c | print-units-with-pch.c +// CHECK: DEPEND END (2) diff --git a/clang/test/Index/Store/record-hash-crash.cpp b/clang/test/Index/Store/record-hash-crash.cpp new file mode 100644 index 0000000000000..8239901669c04 --- /dev/null +++ b/clang/test/Index/Store/record-hash-crash.cpp @@ -0,0 +1,12 @@ +// Makes sure it doesn't crash. + +// XFAIL: linux + +// RUN: rm -rf %t +// RUN: %clang_cc1 %s -index-store-path %t/idx -std=c++14 +// RUN: c-index-test core -print-record %t/idx | FileCheck %s + +namespace crash1 { +// CHECK: [[@LINE+1]]:6 | function/C +auto getit() { return []() {}; } +} diff --git a/clang/test/Index/Store/record-hash.cpp b/clang/test/Index/Store/record-hash.cpp new file mode 100644 index 0000000000000..21a4dc4d974ad --- /dev/null +++ b/clang/test/Index/Store/record-hash.cpp @@ -0,0 +1,12 @@ +// XFAIL: linux + +// RUN: rm -rf %t +// RUN: %clang_cc1 %s -index-store-path %t/idx -D THE_TYPE=long +// RUN: %clang_cc1 %s -index-store-path %t/idx -D THE_TYPE=char +// RUN: find %t/idx/*/records -name "record-hash*" | count 2 + +template +class TC {}; + +// This should result in different records, due to the different template parameter type. +void some_func(TC); diff --git a/clang/test/Index/Store/relative-out-path.c b/clang/test/Index/Store/relative-out-path.c new file mode 100644 index 0000000000000..1d47ea039c046 --- /dev/null +++ b/clang/test/Index/Store/relative-out-path.c @@ -0,0 +1,19 @@ +// Needs 'find'. +// REQUIRES: shell + +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: %clang %s -index-store-path %t/idx1 -c -o %t/outfile.o +// RUN: cd %t +// RUN: %clang %s -index-store-path %t/idx2 -c -o outfile.o +// RUN: cd .. +// RUN: %clang %s -index-store-path %t/idx3 -fsyntax-only -o outfile.o -working-directory=%t +// RUN: diff -r -u %t/idx2 %t/idx3 + +// RUN: find %t/idx1 -name '*outfile.o*' > %t/hashes.txt +// RUN: find %t/idx3 -name '*outfile.o*' >> %t/hashes.txt +// RUN: FileCheck %s --input-file=%t/hashes.txt +// CHECK: outfile.o[[OUT_HASH:.*$]] +// CHECK-NEXT: outfile.o[[OUT_HASH]] + +void foo(); diff --git a/clang/test/Index/Store/syntax-only.c b/clang/test/Index/Store/syntax-only.c new file mode 100644 index 0000000000000..53d22bc142464 --- /dev/null +++ b/clang/test/Index/Store/syntax-only.c @@ -0,0 +1,11 @@ +// RUN: rm -rf %t.idx +// RUN: %clang -fsyntax-only %s -index-store-path %t.idx -o %T/syntax-only.c.myoutfile +// RUN: c-index-test core -print-unit %t.idx | FileCheck %s -check-prefix=CHECK-UNIT +// RUN: c-index-test core -print-record %t.idx | FileCheck %s -check-prefix=CHECK-RECORD + +// XFAIL: linux + +// CHECK-UNIT: out-file: {{.*}}/syntax-only.c.myoutfile +// CHECK-RECORD: function/C | foo | c:@F@foo + +void foo(); diff --git a/clang/test/Index/Store/unit-with-vfs.c b/clang/test/Index/Store/unit-with-vfs.c new file mode 100644 index 0000000000000..cbed6261246e0 --- /dev/null +++ b/clang/test/Index/Store/unit-with-vfs.c @@ -0,0 +1,12 @@ +// RUN: sed -e "s:INPUT_DIR:%S/Inputs:g" -e "s:OUT_DIR:%t:g" %S/Inputs/overlay.yaml > %t.yaml +// REQUIRES: shell + +#include "using-overlay.h" + +// RUN: rm -rf %t.idx +// RUN: %clang_cc1 %s -index-store-path %t.idx -I %t -ivfsoverlay %t.yaml +// RUN: c-index-test core -print-unit %t.idx | FileCheck %s + +// XFAIL: linux + +// CHECK: Record | user | {{.*}}test/Index/Store/Inputs/using-overlay.h diff --git a/clang/test/Index/Store/unit-workdir-prefix.c b/clang/test/Index/Store/unit-workdir-prefix.c new file mode 100644 index 0000000000000..e7a3a7102f33b --- /dev/null +++ b/clang/test/Index/Store/unit-workdir-prefix.c @@ -0,0 +1,30 @@ +// XFAIL: linux + +#include "header.h" + +void foo(void) { + bar(); +} + +// RUN: rm -rf %t +// RUN: mkdir -p %t/Directory +// RUN: mkdir -p %t/Directory.surprise +// RUN: mkdir -p %t/sdk +// RUN: mkdir -p %t/sdk_other +// RUN: echo "void bar(void);" > %t/sdk_other/header.h +// RUN: cp %s %t/Directory.surprise/main.c +// +// RUN: %clang_cc1 -isystem %t/sdk_other -isysroot %t/sdk -index-store-path %t/idx %t/Directory.surprise/main.c -triple x86_64-apple-macosx10.8 -working-directory %t/Directory +// RUN: c-index-test core -print-unit %t/idx | FileCheck %s + +// CHECK: main.c.o +// CHECK: provider: clang- +// CHECK: is-system: 0 +// CHECK: has-main: 1 +// CHECK: main-path: {{.*}}Directory.surprise{{/|\\}}main.c +// CHECK: out-file: {{.*}}Directory.surprise{{/|\\}}main.c.o +// CHECK: target: x86_64-apple-macosx10.8 +// CHECK: is-debug: 1 +// CHECK: DEPEND START +// CHECK: Record | user | {{.*}}Directory.surprise{{/|\\}}main.c | main.c- +// CHECK: Record | system | {{.*}}sdk_other{{/|\\}}header.h | header.h- diff --git a/clang/test/Index/Store/using-libstdcpp-arc.mm b/clang/test/Index/Store/using-libstdcpp-arc.mm new file mode 100644 index 0000000000000..9738c869838e5 --- /dev/null +++ b/clang/test/Index/Store/using-libstdcpp-arc.mm @@ -0,0 +1,10 @@ +// Test to make sure we don't crash, rdar://30816887. + +// RUN: rm -rf %t.idx +// RUN: %clang_cc1 %s -index-store-path %t.idx -fobjc-arc -fobjc-arc-cxxlib=libstdc++ +// RUN: c-index-test core -print-record %t.idx | FileCheck %s + +// XFAIL: linux + +// CHECK: [[@LINE+1]]:6 | function/C +void test1(void); diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt index ec3307b1415ca..b4d5f759ec3ed 100644 --- a/clang/tools/CMakeLists.txt +++ b/clang/tools/CMakeLists.txt @@ -9,6 +9,7 @@ add_clang_subdirectory(clang-import-test) add_clang_subdirectory(clang-offload-bundler) add_clang_subdirectory(c-index-test) +add_clang_subdirectory(IndexStore) if(CLANG_ENABLE_ARCMT) add_clang_subdirectory(arcmt-test) diff --git a/clang/tools/IndexStore/CMakeLists.txt b/clang/tools/IndexStore/CMakeLists.txt new file mode 100644 index 0000000000000..8ad6499117616 --- /dev/null +++ b/clang/tools/IndexStore/CMakeLists.txt @@ -0,0 +1,94 @@ +include(CheckIncludeFiles) + +set(SOURCES + IndexStore.cpp + + ADDITIONAL_HEADERS + ../../include/indexstore/indexstore.h + ../../include/indexstore/IndexStoreCXX.h + ) + +set(LIBS + clangDirectoryWatcher + clangIndex +) + +set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/IndexStore.exports) + +set(ENABLE_SHARED SHARED) + +if(WIN32) + set(output_name "libIndexStore") +else() + set(output_name "IndexStore") +endif() + +# FIXME: needs to be ported to non-Apple platforms. +if(APPLE) + +add_clang_library(IndexStore ${ENABLE_SHARED} ${ENABLE_STATIC} + OUTPUT_NAME ${output_name} + ${SOURCES} + + LINK_LIBS + ${LIBS} + + LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + Core + Support + ) + +set(INDEXSTORE_LIBRARY_VERSION "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}") + +if(ENABLE_SHARED) + if(WIN32) + set_target_properties(IndexStore + PROPERTIES + VERSION ${INDEXSTORE_LIBRARY_VERSION} + DEFINE_SYMBOL _CINDEX_LIB_) + elseif(APPLE) + set(INDEXSTORE_LINK_FLAGS " -Wl,-compatibility_version -Wl,1") + set(INDEXSTORE_LINK_FLAGS "${INDEXSTORE_LINK_FLAGS} -Wl,-current_version -Wl,${INDEXSTORE_LIBRARY_VERSION}") + + check_include_files("CoreServices/CoreServices.h" HAVE_CORESERVICES_H) + if(HAVE_CORESERVICES_H) + set(INDEXSTORE_LINK_FLAGS "${INDEXSTORE_LINK_FLAGS} -framework CoreServices") + endif() + + set_property(TARGET IndexStore APPEND_STRING PROPERTY + LINK_FLAGS ${INDEXSTORE_LINK_FLAGS}) + else() + set_target_properties(IndexStore + PROPERTIES + VERSION ${INDEXSTORE_LIBRARY_VERSION} + DEFINE_SYMBOL _CINDEX_LIB_) + endif() +endif() + +if (LLVM_INSTALL_TOOLCHAIN_ONLY) + install(TARGETS IndexStore + COMPONENT IndexStore + LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX} + ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX} + RUNTIME DESTINATION bin) + + if (NOT CMAKE_CONFIGURATION_TYPES) + add_custom_target(install-IndexStore + DEPENDS IndexStore + COMMAND "${CMAKE_COMMAND}" + -DCMAKE_INSTALL_COMPONENT=IndexStore + -P "${CMAKE_BINARY_DIR}/cmake_install.cmake") + endif() +endif() + +set(INDEXSTORE_HEADERS_INSTALL_DESTINATION "local/include") + +install(DIRECTORY ../../include/indexstore + COMPONENT IndexStore + DESTINATION "${INDEXSTORE_HEADERS_INSTALL_DESTINATION}" + FILES_MATCHING + PATTERN "*.h" + PATTERN ".svn" EXCLUDE + ) +endif() diff --git a/clang/tools/IndexStore/IndexStore.cpp b/clang/tools/IndexStore/IndexStore.cpp new file mode 100644 index 0000000000000..422652499c994 --- /dev/null +++ b/clang/tools/IndexStore/IndexStore.cpp @@ -0,0 +1,647 @@ +//===- IndexStore.cpp - Index store API -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the API for the index store. +// +//===----------------------------------------------------------------------===// + +#include "indexstore/indexstore.h" +#include "clang/Index/IndexDataStore.h" +#include "clang/Index/IndexDataStoreSymbolUtils.h" +#include "clang/Index/IndexRecordReader.h" +#include "clang/Index/IndexUnitReader.h" +#include "clang/Index/IndexUnitWriter.h" +#include "clang/DirectoryWatcher/DirectoryWatcher.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Chrono.h" +#include + +using namespace clang; +using namespace clang::index; +using namespace llvm; + +static indexstore_string_ref_t toIndexStoreString(StringRef str) { + return indexstore_string_ref_t{ str.data(), str.size() }; +} + +static timespec toTimeSpec(sys::TimePoint<> tp) { + std::chrono::seconds sec = std::chrono::time_point_cast( + tp).time_since_epoch(); + std::chrono::nanoseconds nsec = + std::chrono::time_point_cast(tp - sec) + .time_since_epoch(); + timespec ts; + ts.tv_sec = sec.count(); + ts.tv_nsec = nsec.count(); + return ts; +} + +namespace { + +struct IndexStoreError { + std::string Error; +}; + +} // anonymous namespace + +const char * +indexstore_error_get_description(indexstore_error_t err) { + return static_cast(err)->Error.c_str(); +} + +void +indexstore_error_dispose(indexstore_error_t err) { + delete static_cast(err); +} + +unsigned +indexstore_format_version(void) { + return IndexDataStore::getFormatVersion(); +} + +indexstore_t +indexstore_store_create(const char *store_path, indexstore_error_t *c_error) { + std::unique_ptr store; + std::string error; + store = IndexDataStore::create(store_path, error); + if (!store) { + if (c_error) + *c_error = new IndexStoreError{ error }; + return nullptr; + } + return store.release(); +} + +void +indexstore_store_dispose(indexstore_t store) { + delete static_cast(store); +} + +#if INDEXSTORE_HAS_BLOCKS +bool +indexstore_store_units_apply(indexstore_t c_store, unsigned sorted, + bool(^applier)(indexstore_string_ref_t unit_name)) { + IndexDataStore *store = static_cast(c_store); + return store->foreachUnitName(sorted, [&](StringRef unitName) -> bool { + return applier(toIndexStoreString(unitName)); + }); +} + +size_t +indexstore_unit_event_notification_get_events_count(indexstore_unit_event_notification_t c_evtnote) { + auto *evtnote = static_cast(c_evtnote); + return evtnote->Events.size(); +} + +indexstore_unit_event_t +indexstore_unit_event_notification_get_event(indexstore_unit_event_notification_t c_evtnote, size_t index) { + auto *evtnote = static_cast(c_evtnote); + return (indexstore_unit_event_t)&evtnote->Events[index]; +} + +bool +indexstore_unit_event_notification_is_initial(indexstore_unit_event_notification_t c_evtnote) { + auto *evtnote = static_cast(c_evtnote); + return evtnote->IsInitial; +} + +indexstore_unit_event_kind_t +indexstore_unit_event_get_kind(indexstore_unit_event_t c_evt) { + auto *evt = static_cast(c_evt); + indexstore_unit_event_kind_t k; + switch (evt->Kind) { + case IndexDataStore::UnitEventKind::Added: + k = INDEXSTORE_UNIT_EVENT_ADDED; break; + case IndexDataStore::UnitEventKind::Removed: + k = INDEXSTORE_UNIT_EVENT_REMOVED; break; + case IndexDataStore::UnitEventKind::Modified: + k = INDEXSTORE_UNIT_EVENT_MODIFIED; break; + case IndexDataStore::UnitEventKind::DirectoryDeleted: + k = INDEXSTORE_UNIT_EVENT_DIRECTORY_DELETED; break; + } + return k; +} + +indexstore_string_ref_t +indexstore_unit_event_get_unit_name(indexstore_unit_event_t c_evt) { + auto *evt = static_cast(c_evt); + return toIndexStoreString(evt->UnitName); +} + +timespec +indexstore_unit_event_get_modification_time(indexstore_unit_event_t c_evt) { + auto *evt = static_cast(c_evt); + return evt->ModTime; +} + +void +indexstore_store_set_unit_event_handler(indexstore_t c_store, + indexstore_unit_event_handler_t blk_handler) { + IndexDataStore *store = static_cast(c_store); + if (!blk_handler) { + store->setUnitEventHandler(nullptr); + return; + } + + class BlockWrapper { + indexstore_unit_event_handler_t blk_handler; + public: + BlockWrapper(indexstore_unit_event_handler_t handler) { + blk_handler = Block_copy(handler); + } + BlockWrapper(const BlockWrapper &other) { + blk_handler = Block_copy(other.blk_handler); + } + ~BlockWrapper() { + Block_release(blk_handler); + } + + void operator()(indexstore_unit_event_notification_t evt_note) const { + blk_handler(evt_note); + } + }; + + BlockWrapper handler(blk_handler); + + store->setUnitEventHandler([handler](IndexDataStore::UnitEventNotification evtNote) { + handler(&evtNote); + }); +} +#endif + +bool +indexstore_store_start_unit_event_listening(indexstore_t c_store, + indexstore_unit_event_listen_options_t *client_opts, + size_t listen_options_struct_size, + indexstore_error_t *c_error) { + IndexDataStore *store = static_cast(c_store); + indexstore_unit_event_listen_options_t listen_opts; + memset(&listen_opts, 0, sizeof(listen_opts)); + unsigned clientOptSize = listen_options_struct_size < sizeof(listen_opts) + ? listen_options_struct_size : sizeof(listen_opts); + memcpy(&listen_opts, client_opts, clientOptSize); + + std::string error; + auto createFn = [](StringRef Path, AbstractDirectoryWatcher::EventReceiver Receiver, bool waitInitialSync, std::string &Error) + -> std::unique_ptr { + return DirectoryWatcher::create(Path, std::move(Receiver), waitInitialSync, Error); + }; + bool err = store->startEventListening(createFn, listen_opts.wait_initial_sync, error); + if (err && c_error) + *c_error = new IndexStoreError{ error }; + return err; +} + +void +indexstore_store_stop_unit_event_listening(indexstore_t c_store) { + IndexDataStore *store = static_cast(c_store); + store->stopEventListening(); +} + +void +indexstore_store_discard_unit(indexstore_t c_store, const char *unit_name) { + IndexDataStore *store = static_cast(c_store); + store->discardUnit(unit_name); +} + +void +indexstore_store_discard_record(indexstore_t c_store, const char *record_name) { + IndexDataStore *store = static_cast(c_store); + store->discardRecord(record_name); +} + +void +indexstore_store_purge_stale_data(indexstore_t c_store) { + IndexDataStore *store = static_cast(c_store); + store->purgeStaleData(); +} + +indexstore_symbol_kind_t +indexstore_symbol_get_kind(indexstore_symbol_t sym) { + return getIndexStoreKind(static_cast(sym)->SymInfo.Kind); +} + +indexstore_symbol_subkind_t +indexstore_symbol_get_subkind(indexstore_symbol_t sym) { + return getIndexStoreSubKind(static_cast(sym)->SymInfo.SubKind); +} + +indexstore_symbol_language_t +indexstore_symbol_get_language(indexstore_symbol_t sym) { + return getIndexStoreLang(static_cast(sym)->SymInfo.Lang); +} + +uint64_t +indexstore_symbol_get_properties(indexstore_symbol_t sym) { + return getIndexStoreProperties(static_cast(sym)->SymInfo.Properties); +} + +uint64_t +indexstore_symbol_get_roles(indexstore_symbol_t sym) { + return getIndexStoreRoles(static_cast(sym)->Roles); +} + +uint64_t +indexstore_symbol_get_related_roles(indexstore_symbol_t sym) { + return getIndexStoreRoles(static_cast(sym)->RelatedRoles); +} + +indexstore_string_ref_t +indexstore_symbol_get_name(indexstore_symbol_t sym) { + auto *D = static_cast(sym); + return toIndexStoreString(D->Name); +} + +indexstore_string_ref_t +indexstore_symbol_get_usr(indexstore_symbol_t sym) { + auto *D = static_cast(sym); + return toIndexStoreString(D->USR); +} + +indexstore_string_ref_t +indexstore_symbol_get_codegen_name(indexstore_symbol_t sym) { + auto *D = static_cast(sym); + return toIndexStoreString(D->CodeGenName); +} + +uint64_t +indexstore_symbol_relation_get_roles(indexstore_symbol_relation_t sym_rel) { + return getIndexStoreRoles(static_cast(sym_rel)->Roles); +} + +indexstore_symbol_t +indexstore_symbol_relation_get_symbol(indexstore_symbol_relation_t sym_rel) { + return (indexstore_symbol_t)static_cast(sym_rel)->Dcl; +} + +indexstore_symbol_t +indexstore_occurrence_get_symbol(indexstore_occurrence_t occur) { + return (indexstore_symbol_t)static_cast(occur)->Dcl; +} + +#if INDEXSTORE_HAS_BLOCKS +bool +indexstore_occurrence_relations_apply(indexstore_occurrence_t occur, + bool(^applier)(indexstore_symbol_relation_t symbol_rel)) { + auto *recOccur = static_cast(occur); + for (auto &rel : recOccur->Relations) { + if (!applier(&rel)) + return false; + } + return true; +} +#endif + +uint64_t +indexstore_occurrence_get_roles(indexstore_occurrence_t occur) { + return static_cast(occur)->Roles; +} + +void +indexstore_occurrence_get_line_col(indexstore_occurrence_t occur, + unsigned *line, unsigned *column) { + auto *recOccur = static_cast(occur); + if (line) + *line = recOccur->Line; + if (column) + *column = recOccur->Column; +} + +typedef void *indexstore_record_reader_t; + +indexstore_record_reader_t +indexstore_record_reader_create(indexstore_t c_store, const char *record_name, + indexstore_error_t *c_error) { + IndexDataStore *store = static_cast(c_store); + std::unique_ptr reader; + std::string error; + reader = IndexRecordReader::createWithRecordFilename(record_name, + store->getFilePath(), + error); + if (!reader) { + if (c_error) + *c_error = new IndexStoreError{ error }; + return nullptr; + } + return reader.release(); +} + +void +indexstore_record_reader_dispose(indexstore_record_reader_t rdr) { + auto *reader = static_cast(rdr); + delete reader; +} + +#if INDEXSTORE_HAS_BLOCKS +/// Goes through the symbol data and passes symbols to \c receiver, for the +/// symbol data that \c filter returns true on. +/// +/// This allows allocating memory only for the record symbols that the caller is +/// interested in. +bool +indexstore_record_reader_search_symbols(indexstore_record_reader_t rdr, + bool(^filter)(indexstore_symbol_t symbol, bool *stop), + void(^receiver)(indexstore_symbol_t symbol)) { + auto *reader = static_cast(rdr); + + auto filterFn = [&](const IndexRecordDecl &D) -> IndexRecordReader::DeclSearchReturn { + bool stop = false; + bool accept = filter((indexstore_symbol_t)&D, &stop); + return { accept, !stop }; + }; + auto receiverFn = [&](const IndexRecordDecl *D) { + receiver((indexstore_symbol_t)D); + }; + + return reader->searchDecls(filterFn, receiverFn); +} + +bool +indexstore_record_reader_symbols_apply(indexstore_record_reader_t rdr, + bool nocache, + bool(^applier)(indexstore_symbol_t symbol)) { + auto *reader = static_cast(rdr); + auto receiverFn = [&](const IndexRecordDecl *D) -> bool { + return applier((indexstore_symbol_t)D); + }; + return reader->foreachDecl(nocache, receiverFn); +} + +bool +indexstore_record_reader_occurrences_apply(indexstore_record_reader_t rdr, + bool(^applier)(indexstore_occurrence_t occur)) { + auto *reader = static_cast(rdr); + auto receiverFn = [&](const IndexRecordOccurrence &RO) -> bool { + return applier((indexstore_occurrence_t)&RO); + }; + return reader->foreachOccurrence(receiverFn); +} + +bool +indexstore_record_reader_occurrences_in_line_range_apply(indexstore_record_reader_t rdr, + unsigned line_start, + unsigned line_count, + bool(^applier)(indexstore_occurrence_t occur)) { + auto *reader = static_cast(rdr); + auto receiverFn = [&](const IndexRecordOccurrence &RO) -> bool { + return applier((indexstore_occurrence_t)&RO); + }; + return reader->foreachOccurrenceInLineRange(line_start, line_count, receiverFn); +} + +/// \param symbols if non-zero \c symbols_count, indicates the list of symbols +/// that we want to get occurrences for. An empty array indicates that we want +/// occurrences for all symbols. +/// \param related_symbols Same as \c symbols but for related symbols. +bool +indexstore_record_reader_occurrences_of_symbols_apply(indexstore_record_reader_t rdr, + indexstore_symbol_t *symbols, size_t symbols_count, + indexstore_symbol_t *related_symbols, size_t related_symbols_count, + bool(^applier)(indexstore_occurrence_t occur)) { + auto *reader = static_cast(rdr); + auto receiverFn = [&](const IndexRecordOccurrence &RO) -> bool { + return applier((indexstore_occurrence_t)&RO); + }; + return reader->foreachOccurrence({(IndexRecordDecl**)symbols, symbols_count}, + {(IndexRecordDecl**)related_symbols, related_symbols_count}, + receiverFn); +} +#endif + +size_t +indexstore_store_get_unit_name_from_output_path(indexstore_t store, + const char *output_path, + char *name_buf, + size_t buf_size) { + SmallString<256> unitName; + IndexUnitWriter::getUnitNameForAbsoluteOutputFile(output_path, unitName); + size_t nameLen = unitName.size(); + strlcpy(name_buf, unitName.c_str(), buf_size); + return nameLen; +} + +bool +indexstore_store_get_unit_modification_time(indexstore_t c_store, + const char *unit_name, + int64_t *seconds, + int64_t *nanoseconds, + indexstore_error_t *c_error) { + IndexDataStore *store = static_cast(c_store); + std::string error; + // FIXME: This provides mod time with second-only accuracy. + auto optModTime = IndexUnitReader::getModificationTimeForUnit(unit_name, + store->getFilePath(), error); + if (!optModTime) { + if (c_error) + *c_error = new IndexStoreError{ error }; + return true; + } + + timespec ts = toTimeSpec(*optModTime); + if (seconds) + *seconds = ts.tv_sec; + if (nanoseconds) + *nanoseconds = ts.tv_nsec; + + return false; +} + +indexstore_unit_reader_t +indexstore_unit_reader_create(indexstore_t c_store, const char *unit_name, + indexstore_error_t *c_error) { + IndexDataStore *store = static_cast(c_store); + std::unique_ptr reader; + std::string error; + reader = IndexUnitReader::createWithUnitFilename(unit_name, + store->getFilePath(), error); + if (!reader) { + if (c_error) + *c_error = new IndexStoreError{ error }; + return nullptr; + } + return reader.release(); +} + +void +indexstore_unit_reader_dispose(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + delete reader; +} + +indexstore_string_ref_t +indexstore_unit_reader_get_provider_identifier(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return toIndexStoreString(reader->getProviderIdentifier()); +} + +indexstore_string_ref_t +indexstore_unit_reader_get_provider_version(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return toIndexStoreString(reader->getProviderVersion()); +} + +void +indexstore_unit_reader_get_modification_time(indexstore_unit_reader_t rdr, + int64_t *seconds, + int64_t *nanoseconds) { + auto reader = static_cast(rdr); + // FIXME: This provides mod time with second-only accuracy. + sys::TimePoint<> timeVal = reader->getModificationTime(); + timespec ts = toTimeSpec(timeVal); + if (seconds) + *seconds = ts.tv_sec; + if (nanoseconds) + *nanoseconds = ts.tv_nsec; +} + +bool +indexstore_unit_reader_is_system_unit(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return reader->isSystemUnit(); +} + +bool +indexstore_unit_reader_is_module_unit(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return reader->isModuleUnit(); +} + +bool +indexstore_unit_reader_is_debug_compilation(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return reader->isDebugCompilation(); +} + +bool +indexstore_unit_reader_has_main_file(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return reader->hasMainFile(); +} + +indexstore_string_ref_t +indexstore_unit_reader_get_main_file(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return toIndexStoreString(reader->getMainFilePath()); +} + +indexstore_string_ref_t +indexstore_unit_reader_get_module_name(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return toIndexStoreString(reader->getModuleName()); +} + +indexstore_string_ref_t +indexstore_unit_reader_get_working_dir(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return toIndexStoreString(reader->getWorkingDirectory()); +} + +indexstore_string_ref_t +indexstore_unit_reader_get_output_file(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return toIndexStoreString(reader->getOutputFile()); +} + +indexstore_string_ref_t +indexstore_unit_reader_get_sysroot_path(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return toIndexStoreString(reader->getSysrootPath()); +} + +indexstore_string_ref_t +indexstore_unit_reader_get_target(indexstore_unit_reader_t rdr) { + auto reader = static_cast(rdr); + return toIndexStoreString(reader->getTarget()); +} + +indexstore_unit_dependency_kind_t +indexstore_unit_dependency_get_kind(indexstore_unit_dependency_t c_dep) { + auto dep = static_cast(c_dep); + switch (dep->Kind) { + case IndexUnitReader::DependencyKind::Unit: return INDEXSTORE_UNIT_DEPENDENCY_UNIT; + case IndexUnitReader::DependencyKind::Record: return INDEXSTORE_UNIT_DEPENDENCY_RECORD; + case IndexUnitReader::DependencyKind::File: return INDEXSTORE_UNIT_DEPENDENCY_FILE; + } +} + +bool +indexstore_unit_dependency_is_system(indexstore_unit_dependency_t c_dep) { + auto dep = static_cast(c_dep); + return dep->IsSystem; +} + +indexstore_string_ref_t +indexstore_unit_dependency_get_filepath(indexstore_unit_dependency_t c_dep) { + auto dep = static_cast(c_dep); + return toIndexStoreString(dep->FilePath); +} + +indexstore_string_ref_t +indexstore_unit_dependency_get_modulename(indexstore_unit_dependency_t c_dep) { + auto dep = static_cast(c_dep); + return toIndexStoreString(dep->ModuleName); +} + +indexstore_string_ref_t +indexstore_unit_dependency_get_name(indexstore_unit_dependency_t c_dep) { + auto dep = static_cast(c_dep); + return toIndexStoreString(dep->UnitOrRecordName); +} + +time_t +indexstore_unit_dependency_get_modification_time(indexstore_unit_dependency_t c_dep) { + auto dep = static_cast(c_dep); + return dep->ModTime; +} + +size_t +indexstore_unit_dependency_get_file_size(indexstore_unit_dependency_t c_dep) { + auto dep = static_cast(c_dep); + return dep->FileSize; +} + +indexstore_string_ref_t +indexstore_unit_include_get_source_path(indexstore_unit_include_t c_inc) { + auto inc = static_cast(c_inc); + return toIndexStoreString(inc->SourcePath); +} + +indexstore_string_ref_t +indexstore_unit_include_get_target_path(indexstore_unit_include_t c_inc) { + auto inc = static_cast(c_inc); + return toIndexStoreString(inc->TargetPath); +} + +unsigned +indexstore_unit_include_get_source_line(indexstore_unit_include_t c_inc) { + auto inc = static_cast(c_inc); + return inc->SourceLine; +} + +#if INDEXSTORE_HAS_BLOCKS +bool +indexstore_unit_reader_dependencies_apply(indexstore_unit_reader_t rdr, + bool(^applier)(indexstore_unit_dependency_t)) { + auto reader = static_cast(rdr); + return reader->foreachDependency([&](const IndexUnitReader::DependencyInfo &depInfo) -> bool { + return applier((void*)&depInfo); + }); +} + +bool +indexstore_unit_reader_includes_apply(indexstore_unit_reader_t rdr, + bool(^applier)(indexstore_unit_include_t)) { + auto reader = static_cast(rdr); + return reader->foreachInclude([&](const IndexUnitReader::IncludeInfo &incInfo) -> bool { + return applier((void*)&incInfo); + }); +} +#endif diff --git a/clang/tools/IndexStore/IndexStore.exports b/clang/tools/IndexStore/IndexStore.exports new file mode 100644 index 0000000000000..70c174f360315 --- /dev/null +++ b/clang/tools/IndexStore/IndexStore.exports @@ -0,0 +1,69 @@ +indexstore_error_get_description +indexstore_error_dispose +indexstore_format_version +indexstore_store_create +indexstore_store_dispose +indexstore_store_get_unit_modification_time +indexstore_store_get_unit_name_from_output_path +indexstore_store_units_apply +indexstore_store_set_unit_event_handler +indexstore_store_start_unit_event_listening +indexstore_store_stop_unit_event_listening +indexstore_store_discard_unit +indexstore_store_discard_record +indexstore_store_purge_stale_data +indexstore_symbol_get_kind +indexstore_symbol_get_language +indexstore_symbol_get_properties +indexstore_symbol_get_roles +indexstore_symbol_get_related_roles +indexstore_symbol_get_subkind +indexstore_symbol_get_name +indexstore_symbol_get_usr +indexstore_symbol_get_codegen_name +indexstore_symbol_relation_get_roles +indexstore_symbol_relation_get_symbol +indexstore_occurrence_get_symbol +indexstore_occurrence_get_roles +indexstore_occurrence_get_line_col +indexstore_occurrence_relations_apply +indexstore_record_reader_create +indexstore_record_reader_dispose +indexstore_record_reader_search_symbols +indexstore_record_reader_symbols_apply +indexstore_record_reader_occurrences_apply +indexstore_record_reader_occurrences_in_line_range_apply +indexstore_record_reader_occurrences_of_symbols_apply +indexstore_unit_dependency_get_kind +indexstore_unit_dependency_get_filepath +indexstore_unit_dependency_get_file_size +indexstore_unit_dependency_get_modification_time +indexstore_unit_dependency_get_modulename +indexstore_unit_dependency_get_name +indexstore_unit_dependency_is_system +indexstore_unit_event_get_kind +indexstore_unit_event_get_modification_time +indexstore_unit_event_get_unit_name +indexstore_unit_event_notification_get_event +indexstore_unit_event_notification_get_events_count +indexstore_unit_event_notification_is_initial +indexstore_unit_reader_create +indexstore_unit_reader_dispose +indexstore_unit_reader_get_main_file +indexstore_unit_reader_get_modification_time +indexstore_unit_reader_get_module_name +indexstore_unit_reader_get_provider_identifier +indexstore_unit_reader_get_provider_version +indexstore_unit_reader_get_working_dir +indexstore_unit_reader_get_output_file +indexstore_unit_reader_get_sysroot_path +indexstore_unit_reader_get_target +indexstore_unit_reader_dependencies_apply +indexstore_unit_reader_includes_apply +indexstore_unit_reader_has_main_file +indexstore_unit_reader_is_debug_compilation +indexstore_unit_reader_is_module_unit +indexstore_unit_reader_is_system_unit +indexstore_unit_include_get_source_path +indexstore_unit_include_get_target_path +indexstore_unit_include_get_source_line diff --git a/clang/tools/c-index-test/CMakeLists.txt b/clang/tools/c-index-test/CMakeLists.txt index ad990e010eeff..c8d33b9e36f16 100644 --- a/clang/tools/c-index-test/CMakeLists.txt +++ b/clang/tools/c-index-test/CMakeLists.txt @@ -1,3 +1,5 @@ +include(CheckIncludeFiles) + set(LLVM_LINK_COMPONENTS support ) @@ -5,8 +7,15 @@ set(LLVM_LINK_COMPONENTS add_clang_executable(c-index-test c-index-test.c core_main.cpp + JSONAggregation.cpp ) +set(INDEXSTORE_LIB) +set(CINDEXTEST_LIBS) +if(APPLE) + set(INDEXSTORE_LIB IndexStore) +endif() + if(NOT MSVC) set_property( SOURCE c-index-test.c @@ -19,16 +28,20 @@ if (LLVM_BUILD_STATIC) libclang_static clangCodeGen clangIndex + ${CINDEXTEST_LIBS} ) else() target_link_libraries(c-index-test libclang + ${INDEXSTORE_LIB} clangAST clangBasic clangCodeGen + clangDirectoryWatcher clangFrontend clangIndex clangSerialization + ${CINDEXTEST_LIBS} ) endif() @@ -42,6 +55,13 @@ if (CLANG_HAVE_LIBXML) target_link_libraries(c-index-test ${LIBXML2_LIBRARIES}) endif() +if(APPLE) + check_include_files("CoreServices/CoreServices.h" HAVE_CORESERVICES_H) + if(HAVE_CORESERVICES_H) + target_link_libraries(c-index-test "-framework CoreServices") + endif() +endif() + if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY) if(INTERNAL_INSTALL_PREFIX) set(INSTALL_DESTINATION "${INTERNAL_INSTALL_PREFIX}/bin") diff --git a/clang/tools/c-index-test/JSONAggregation.cpp b/clang/tools/c-index-test/JSONAggregation.cpp new file mode 100644 index 0000000000000..c7f4136bde7e6 --- /dev/null +++ b/clang/tools/c-index-test/JSONAggregation.cpp @@ -0,0 +1,409 @@ +//===--- JSONAggregation.cpp - Index data aggregation in JSON format ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "JSONAggregation.h" +#include "indexstore/IndexStoreCXX.h" +#include "clang/Index/IndexDataStoreSymbolUtils.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::index; +using namespace indexstore; +using namespace llvm; + +#if INDEXSTORE_HAS_BLOCKS + +namespace { + +typedef size_t FilePathIndex; +typedef size_t RecordIndex; +typedef size_t SymbolIndex; + +struct UnitSourceInfo { + FilePathIndex FilePath; + SmallVector AssociatedRecords; +}; + +struct UnitInfo { + std::string Name; + SmallVector Sources; + SmallVector UnitDepends; + FilePathIndex OutFile; + StringRef Triple; +}; + +struct SymbolInfo { + SymbolKind Kind; + SymbolLanguage Lang; + StringRef USR; + StringRef Name; + StringRef CodegenName; + SymbolRoleSet Roles = 0; + SymbolRoleSet RelatedRoles = 0; +}; + +struct SymbolRelationInfo { + SymbolIndex RelatedSymbol; + SymbolRoleSet Roles; + SymbolRelationInfo(SymbolIndex relSymbol, SymbolRoleSet roles) + : RelatedSymbol(relSymbol), Roles(roles) {} +}; + +struct SymbolOccurrenceInfo { + SymbolIndex Symbol; + SymbolRoleSet Roles = 0; + std::vector Relations; + unsigned Line; + unsigned Column; +}; + +struct RecordInfo { + SmallVector Occurrences; +}; + +class Aggregator { + IndexStore Store; + + BumpPtrAllocator Allocator; + + StringMap FilePathIndices; + std::vector FilePaths; + StringMap Triples; + + std::vector> Units; + + StringMap RecordIndices; + std::vector> Records; + + StringMap SymbolIndices; + std::vector Symbols; + +public: + explicit Aggregator(IndexStore store) + : Store(std::move(store)), + FilePathIndices(Allocator), + Triples(Allocator), + RecordIndices(Allocator), + SymbolIndices(Allocator) {} + + bool process(); + void processUnit(StringRef name, IndexUnitReader &UnitReader); + void dumpJSON(raw_ostream &OS); + +private: + StringRef copyStr(StringRef str) { + if (str.empty()) + return StringRef(); + char *buf = Allocator.Allocate(str.size()); + std::copy(str.begin(), str.end(), buf); + return StringRef(buf, str.size()); + } + + StringRef getTripleString(StringRef inputTriple) { + return Triples.insert(std::make_pair(inputTriple, 0)).first->first(); + } + + FilePathIndex getFilePathIndex(StringRef path, StringRef workingDir); + RecordIndex getRecordIndex(StringRef recordFile); + SymbolIndex getSymbolIndex(IndexRecordSymbol sym); + std::unique_ptr processRecord(StringRef recordFile); +}; + +} // anonymous namespace + +bool Aggregator::process() { + bool succ = Store.foreachUnit(/*sorted=*/true, [&](StringRef unitName) -> bool { + std::string error; + auto unitReader = IndexUnitReader(Store, unitName, error); + if (!unitReader) { + errs() << "error opening unit file '" << unitName << "': " << error << '\n'; + return false; + } + + processUnit(unitName, unitReader); + return true; + }); + + return !succ; +} + +void Aggregator::processUnit(StringRef name, IndexUnitReader &UnitReader) { + auto workDir = UnitReader.getWorkingDirectory(); + auto unit = llvm::make_unique(); + unit->Name = name; + unit->Triple = getTripleString(UnitReader.getTarget()); + unit->OutFile = getFilePathIndex(UnitReader.getOutputFile(), workDir); + + struct DepInfo { + UnitSourceInfo source; + std::string unitName; + }; + SmallVector Deps; + UnitReader.foreachDependency([&](IndexUnitDependency dep) -> bool { + Deps.resize(Deps.size()+1); + auto &depInfo = Deps.back(); + switch (dep.getKind()) { + case IndexUnitDependency::DependencyKind::Unit: { + depInfo.unitName = dep.getName(); + StringRef filePath = dep.getFilePath(); + if (!filePath.empty()) + depInfo.source.FilePath = getFilePathIndex(filePath, workDir); + break; + } + case IndexUnitDependency::DependencyKind::Record: { + depInfo.source.FilePath = getFilePathIndex(dep.getFilePath(), workDir); + RecordIndex recIndex = getRecordIndex(dep.getName()); + depInfo.source.AssociatedRecords.push_back(recIndex); + break; + } + case IndexUnitDependency::DependencyKind::File: + depInfo.source.FilePath = getFilePathIndex(dep.getFilePath(), workDir); + } + return true; + }); + + unit->Sources.reserve(Deps.size()); + for (auto &dep : Deps) { + if (!dep.unitName.empty()) { + unit->UnitDepends.emplace_back(std::move(dep.unitName)); + } else { + unit->Sources.push_back(std::move(dep.source)); + } + } + + Units.push_back(std::move(unit)); +} + +FilePathIndex Aggregator::getFilePathIndex(StringRef path, StringRef workingDir) { + StringRef absPath; + SmallString<128> absPathBuf; + if (sys::path::is_absolute(path) || workingDir.empty()) { + absPath = path; + } else { + absPathBuf = workingDir; + sys::path::append(absPathBuf, path); + absPath = absPathBuf.str(); + } + + auto pair = FilePathIndices.insert(std::make_pair(absPath, FilePaths.size())); + bool wasInserted = pair.second; + if (wasInserted) { + FilePaths.push_back(pair.first->first()); + } + return pair.first->second; +} + +RecordIndex Aggregator::getRecordIndex(StringRef recordFile) { + auto pair = RecordIndices.insert(std::make_pair(recordFile, Records.size())); + bool wasInserted = pair.second; + if (wasInserted) { + Records.push_back(processRecord(recordFile)); + } + return pair.first->second; +} + +std::unique_ptr Aggregator::processRecord(StringRef recordFile) { + std::string error; + auto recordReader = IndexRecordReader(Store, recordFile, error); + if (!recordReader) { + errs() << "failed reading record file: " << recordFile << '\n'; + ::exit(1); + } + auto record = llvm::make_unique(); + recordReader.foreachOccurrence([&](IndexRecordOccurrence idxOccur) -> bool { + SymbolIndex symIdx = getSymbolIndex(idxOccur.getSymbol()); + SymbolInfo &symInfo = Symbols[symIdx]; + symInfo.Roles |= idxOccur.getRoles(); + SymbolOccurrenceInfo occurInfo; + occurInfo.Symbol = symIdx; + idxOccur.foreachRelation([&](IndexSymbolRelation rel) -> bool { + SymbolIndex relsymIdx = getSymbolIndex(rel.getSymbol()); + SymbolInfo &relsymInfo = Symbols[relsymIdx]; + relsymInfo.RelatedRoles |= rel.getRoles(); + occurInfo.Relations.emplace_back(relsymIdx, rel.getRoles()); + return true; + }); + occurInfo.Roles = idxOccur.getRoles(); + std::tie(occurInfo.Line, occurInfo.Column) = idxOccur.getLineCol(); + record->Occurrences.push_back(std::move(occurInfo)); + return true; + }); + return record; +} + +SymbolIndex Aggregator::getSymbolIndex(IndexRecordSymbol sym) { + auto pair = SymbolIndices.insert(std::make_pair(sym.getUSR(), Symbols.size())); + bool wasInserted = pair.second; + if (wasInserted) { + SymbolInfo symInfo; + symInfo.Kind = getSymbolKind(sym.getKind()); + symInfo.Lang = getSymbolLanguage(sym.getLanguage()); + symInfo.USR = pair.first->first(); + symInfo.Name = copyStr(sym.getName()); + symInfo.CodegenName = copyStr(sym.getCodegenName()); + Symbols.push_back(std::move(symInfo)); + } + return pair.first->second; +} + + +void Aggregator::dumpJSON(raw_ostream &OS) { + OS << "{\n"; + OS.indent(2) << "\"files\": [\n"; + for (unsigned i = 0, e = FilePaths.size(); i != e; ++i) { + OS.indent(4) << '\"' << FilePaths[i] << '\"'; + if (i < e-1) OS << ','; + OS << '\n'; + } + OS.indent(2) << "],\n"; + + OS.indent(2) << "\"symbols\": [\n"; + for (unsigned i = 0, e = Symbols.size(); i != e; ++i) { + OS.indent(4) << "{\n"; + SymbolInfo &symInfo = Symbols[i]; + OS.indent(6) << "\"kind\": \"" << getSymbolKindString(symInfo.Kind) << "\",\n"; + OS.indent(6) << "\"lang\": \"" << getSymbolLanguageString(symInfo.Lang) << "\",\n"; + OS.indent(6) << "\"usr\": \"" << symInfo.USR << "\",\n"; + OS.indent(6) << "\"name\": \"" << symInfo.Name << "\",\n"; + if (!symInfo.CodegenName.empty()) + OS.indent(6) << "\"codegen\": \"" << symInfo.CodegenName << "\",\n"; + OS.indent(6) << "\"roles\": \""; + printSymbolRoles(symInfo.Roles, OS); + OS << '\"'; + if (symInfo.RelatedRoles != 0) { + OS << ",\n"; + OS.indent(6) << "\"rel-roles\": \""; + printSymbolRoles(symInfo.RelatedRoles, OS); + OS << '\"'; + } + OS << '\n'; + OS.indent(4) << "}"; + if (i < e-1) OS << ','; + OS << '\n'; + } + OS.indent(2) << "],\n"; + + OS.indent(2) << "\"records\": [\n"; + for (unsigned i = 0, e = Records.size(); i != e; ++i) { + OS.indent(4) << "{\n"; + RecordInfo &recInfo = *Records[i]; + OS.indent(6) << "\"occurrences\": [\n"; + for (unsigned oi = 0, oe = recInfo.Occurrences.size(); oi != oe; ++oi) { + OS.indent(8) << "{\n"; + SymbolOccurrenceInfo &occurInfo = recInfo.Occurrences[oi]; + OS.indent(10) << "\"symbol\": " << occurInfo.Symbol << ",\n"; + OS.indent(10) << "\"line\": " << occurInfo.Line << ",\n"; + OS.indent(10) << "\"col\": " << occurInfo.Column << ",\n"; + OS.indent(10) << "\"roles\": \""; + printSymbolRoles(occurInfo.Roles, OS); + OS << '\"'; + if (!occurInfo.Relations.empty()) { + OS << ",\n"; + OS.indent(10) << "\"relations\": [\n"; + for (unsigned ri = 0, re = occurInfo.Relations.size(); ri != re; ++ri) { + OS.indent(12) << "{\n"; + SymbolRelationInfo &relInfo = occurInfo.Relations[ri]; + OS.indent(14) << "\"symbol\": " << relInfo.RelatedSymbol << ",\n"; + OS.indent(14) << "\"rel-roles\": \""; + printSymbolRoles(relInfo.Roles, OS); + OS << "\"\n"; + OS.indent(12) << "}"; + if (ri < re-1) OS << ','; + OS << '\n'; + } + OS.indent(10) << "]\n"; + } + OS << '\n'; + OS.indent(8) << "}"; + if (oi < oe-1) OS << ','; + OS << '\n'; + } + OS.indent(6) << "]\n"; + OS.indent(4) << "}"; + if (i < e-1) OS << ','; + OS << '\n'; + } + OS.indent(2) << "],\n"; + + StringMap UnitIndicesByName; + for (unsigned i = 0, e = Units.size(); i != e; ++i) { + UnitInfo &unit = *Units[i]; + UnitIndicesByName[unit.Name] = i; + } + + OS.indent(2) << "\"units\": [\n"; + for (unsigned i = 0, e = Units.size(); i != e; ++i) { + OS.indent(4) << "{\n"; + UnitInfo &unit = *Units[i]; + OS.indent(6) << "\"triple\": \"" << unit.Triple << "\",\n"; + OS.indent(6) << "\"out-file\": " << unit.OutFile << ",\n"; + if (!unit.UnitDepends.empty()) { + OS.indent(6) << "\"unit-dependencies\": ["; + for (unsigned ui = 0, ue = unit.UnitDepends.size(); ui != ue; ++ui) { + OS << UnitIndicesByName[unit.UnitDepends[ui]]; + if (ui < ue-1) OS << ", "; + } + OS << "],\n"; + } + OS.indent(6) << "\"sources\": [\n"; + for (unsigned si = 0, se = unit.Sources.size(); si != se; ++si) { + OS.indent(8) << "{\n"; + UnitSourceInfo &source = unit.Sources[si]; + OS.indent(10) << "\"file\": " << source.FilePath; + if (!source.AssociatedRecords.empty()) { + OS << ",\n"; + OS.indent(10) << "\"records\": ["; + for (unsigned ri = 0, re = source.AssociatedRecords.size(); ri != re; ++ri) { + OS << source.AssociatedRecords[ri]; + if (ri < re-1) OS << ", "; + } + OS << ']'; + } + OS << '\n'; + OS.indent(8) << "}"; + if (si < se-1) OS << ','; + OS << '\n'; + } + OS.indent(6) << "]\n"; + OS.indent(4) << "}"; + if (i < e-1) OS << ','; + OS << '\n'; + } + OS.indent(2) << "]\n"; + OS << "}\n"; +} + + +bool index::aggregateDataAsJSON(StringRef StorePath, raw_ostream &OS) { + std::string error; + auto dataStore = IndexStore(StorePath, error); + if (!dataStore) { + errs() << "error opening store path '" << StorePath << "': " << error << '\n'; + return true; + } + + // Explicitely avoid doing any memory cleanup for aggregator since the process + // is going to exit when we are done. + Aggregator *aggregator = new Aggregator(std::move(dataStore)); + bool err = aggregator->process(); + if (err) + return true; + aggregator->dumpJSON(OS); + return false; +} + +#else + +bool index::aggregateDataAsJSON(StringRef StorePath, raw_ostream &OS) { + return true; +} +#endif diff --git a/clang/tools/c-index-test/JSONAggregation.h b/clang/tools/c-index-test/JSONAggregation.h new file mode 100644 index 0000000000000..5224ce8e8769b --- /dev/null +++ b/clang/tools/c-index-test/JSONAggregation.h @@ -0,0 +1,24 @@ +//===--- JSONAggregation.h - Index data aggregation in JSON format --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_CINDEXTEST_JSONAGGREGATION_H +#define LLVM_CLANG_TOOLS_CINDEXTEST_JSONAGGREGATION_H + +#include "clang/Basic/LLVM.h" + +namespace clang { +namespace index { + +/// Returns true if an error occurred, false otherwise. +bool aggregateDataAsJSON(StringRef StorePath, raw_ostream &OS); + +} // end namespace index +} // end namespace clang + +#endif diff --git a/clang/tools/c-index-test/core_main.cpp b/clang/tools/c-index-test/core_main.cpp index 4f2c3cb34a9ba..1335b05075cfb 100644 --- a/clang/tools/c-index-test/core_main.cpp +++ b/clang/tools/c-index-test/core_main.cpp @@ -7,6 +7,9 @@ // //===----------------------------------------------------------------------===// +#include "JSONAggregation.h" +#include "indexstore/IndexStoreCXX.h" +#include "clang/DirectoryWatcher/DirectoryWatcher.h" #include "clang/CodeGen/ObjectFilePCHContainerOperations.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" @@ -14,14 +17,31 @@ #include "clang/Frontend/FrontendAction.h" #include "clang/Index/IndexingAction.h" #include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexDataStoreSymbolUtils.h" +#include "clang/Index/IndexRecordReader.h" +#include "clang/Index/IndexUnitReader.h" #include "clang/Index/USRGeneration.h" #include "clang/Index/CodegenNameGenerator.h" #include "clang/Serialization/ASTReader.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" #include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/PrettyStackTrace.h" +#define HAVE_CORESERVICES 0 + +#if defined(__has_include) +#if __has_include() + +#include +#undef HAVE_CORESERVICES +#define HAVE_CORESERVICES 1 + +#endif +#endif + using namespace clang; using namespace clang::index; using namespace llvm; @@ -33,6 +53,11 @@ namespace { enum class ActionType { None, PrintSourceSymbols, + PrintRecord, + PrintUnit, + PrintStoreFormatVersion, + AggregateAsJSON, + WatchDir, }; namespace options { @@ -43,9 +68,26 @@ static cl::opt Action(cl::desc("Action:"), cl::init(ActionType::None), cl::values( clEnumValN(ActionType::PrintSourceSymbols, - "print-source-symbols", "Print symbols from source")), + "print-source-symbols", "Print symbols from source"), + clEnumValN(ActionType::PrintRecord, + "print-record", "Print record info"), + clEnumValN(ActionType::PrintUnit, + "print-unit", "Print unit info"), + clEnumValN(ActionType::PrintStoreFormatVersion, + "print-store-format-version", "Print store format version"), + clEnumValN(ActionType::AggregateAsJSON, + "aggregate-json", "Aggregate index data in JSON format"), + clEnumValN(ActionType::WatchDir, + "watch-dir", "Watch directory for file events")), cl::cat(IndexTestCoreCategory)); +static cl::opt +OutputFile("o", cl::desc("output file"), + cl::cat(IndexTestCoreCategory)); + +static cl::list +InputFiles(cl::Positional, cl::desc("...")); + static cl::extrahelp MoreHelp( "\nAdd \"-- \" at the end to setup the compiler " "invocation\n" @@ -65,6 +107,10 @@ static cl::opt ModuleFormat("fmodule-format", cl::init("raw"), cl::desc("Container format for clang modules and PCH, 'raw' or 'obj'")); +static cl::opt +FilePathAndRange("filepath", + cl::desc("File path that can optionally include a line range")); + } } // anonymous namespace @@ -235,6 +281,299 @@ static bool printSourceSymbolsFromModule(StringRef modulePath, return false; } +#if INDEXSTORE_HAS_BLOCKS + +//===----------------------------------------------------------------------===// +// Print Record +//===----------------------------------------------------------------------===// + +static void printSymbol(const IndexRecordDecl &Rec, raw_ostream &OS); +static void printSymbol(const IndexRecordOccurrence &Rec, raw_ostream &OS); + +static int printRecord(StringRef Filename, raw_ostream &OS) { + std::string Error; + auto Reader = IndexRecordReader::createWithFilePath(Filename, Error); + if (!Reader) { + errs() << Error << '\n'; + return true; + } + + Reader->foreachDecl(/*noCache=*/true, [&](const IndexRecordDecl *Rec)->bool { + printSymbol(*Rec, OS); + return true; + }); + OS << "------------\n"; + Reader->foreachOccurrence([&](const IndexRecordOccurrence &Rec)->bool { + printSymbol(Rec, OS); + return true; + }); + + return false; +}; + +//===----------------------------------------------------------------------===// +// Print Store Records +//===----------------------------------------------------------------------===// + +static void printSymbol(indexstore::IndexRecordSymbol Sym, raw_ostream &OS); +static void printSymbol(indexstore::IndexRecordOccurrence Occur, raw_ostream &OS); + +static bool printStoreRecord(indexstore::IndexStore &Store, StringRef RecName, + StringRef FilePath, raw_ostream &OS) { + std::string Error; + indexstore::IndexRecordReader Reader(Store, RecName, Error); + if (!Reader) { + errs() << "error loading record: " << Error << "\n"; + return true; + } + + StringRef Filename = sys::path::filename(FilePath); + OS << Filename << '\n'; + OS << "------------\n"; + Reader.foreachSymbol(/*noCache=*/true, [&](indexstore::IndexRecordSymbol Sym) -> bool { + printSymbol(Sym, OS); + return true; + }); + OS << "------------\n"; + Reader.foreachOccurrence([&](indexstore::IndexRecordOccurrence Occur)->bool { + printSymbol(Occur, OS); + return true; + }); + + return false; +} + +static int printStoreRecords(StringRef StorePath, raw_ostream &OS) { + std::string Error; + indexstore::IndexStore Store(StorePath, Error); + if (!Store) { + errs() << "error loading store: " << Error << "\n"; + return 1; + } + + bool Success = Store.foreachUnit(/*sorted=*/true, [&](StringRef UnitName) -> bool { + indexstore::IndexUnitReader Reader(Store, UnitName, Error); + if (!Reader) { + errs() << "error loading unit: " << Error << "\n"; + return false; + } + return Reader.foreachDependency([&](indexstore::IndexUnitDependency Dep) -> bool { + if (Dep.getKind() == indexstore::IndexUnitDependency::DependencyKind::Record) { + bool Err = printStoreRecord(Store, Dep.getName(), Dep.getFilePath(), OS); + OS << '\n'; + return !Err; + } + return true; + }); + }); + + return !Success; +} + +static std::string findRecordNameForFile(indexstore::IndexStore &store, + StringRef filePath) { + std::string recName; + store.foreachUnit(/*sorted=*/false, [&](StringRef unitName) -> bool { + std::string error; + indexstore::IndexUnitReader Reader(store, unitName, error); + if (!Reader) { + errs() << "error loading unit: " << error << "\n"; + return false; + } + Reader.foreachDependency([&](indexstore::IndexUnitDependency Dep) -> bool { + if (Dep.getKind() == indexstore::IndexUnitDependency::DependencyKind::Record) { + if (Dep.getFilePath() == filePath) { + recName = Dep.getName(); + return false; + } + return true; + } + return true; + }); + return true; + }); + return recName; +} + +static int printStoreFileRecord(StringRef storePath, StringRef filePath, + Optional lineStart, unsigned lineCount, + raw_ostream &OS) { + std::string error; + indexstore::IndexStore store(storePath, error); + if (!store) { + errs() << "error loading store: " << error << "\n"; + return 1; + } + + std::string recName = findRecordNameForFile(store, filePath); + if (recName.empty()) { + errs() << "could not find record for '" << filePath << "'\n"; + return 1; + } + + if (!lineStart.hasValue()) + return printStoreRecord(store, recName, filePath, OS); + + indexstore::IndexRecordReader Reader(store, recName, error); + if (!Reader) { + errs() << "error loading record: " << error << "\n"; + return 1; + } + + Reader.foreachOccurrenceInLineRange(*lineStart, lineCount, [&](indexstore::IndexRecordOccurrence Occur)->bool { + printSymbol(Occur, OS); + return true; + }); + + return 0; +} + + +//===----------------------------------------------------------------------===// +// Print Unit +//===----------------------------------------------------------------------===// + +static int printUnit(StringRef Filename, raw_ostream &OS) { + std::string Error; + auto Reader = IndexUnitReader::createWithFilePath(Filename, Error); + if (!Reader) { + errs() << Error << '\n'; + return true; + } + + OS << "provider: " << Reader->getProviderIdentifier() << '-' << Reader->getProviderVersion() << '\n'; + OS << "is-system: " << Reader->isSystemUnit() << '\n'; + OS << "is-module: " << Reader->isModuleUnit() << '\n'; + OS << "module-name: " << (Reader->getModuleName().empty() ? "" : Reader->getModuleName()) << '\n'; + OS << "has-main: " << Reader->hasMainFile() << '\n'; + OS << "main-path: " << Reader->getMainFilePath() << '\n'; + OS << "work-dir: " << Reader->getWorkingDirectory() << '\n'; + OS << "out-file: " << Reader->getOutputFile() << '\n'; + OS << "target: " << Reader->getTarget() << '\n'; + OS << "is-debug: " << Reader->isDebugCompilation() << '\n'; + OS << "DEPEND START\n"; + unsigned NumDepends = 0; + Reader->foreachDependency([&](const IndexUnitReader::DependencyInfo &Dep) -> bool { + switch (Dep.Kind) { + case IndexUnitReader::DependencyKind::Unit: + OS << "Unit | "; break; + case IndexUnitReader::DependencyKind::Record: + OS << "Record | "; break; + case IndexUnitReader::DependencyKind::File: + OS << "File | "; break; + } + OS << (Dep.IsSystem ? "system" : "user"); + OS << " | "; + if (!Dep.ModuleName.empty()) + OS << Dep.ModuleName << " | "; + OS << Dep.FilePath << " | " << Dep.UnitOrRecordName << " | "; + OS << Dep.ModTime << " | " << Dep.FileSize << '\n'; + ++NumDepends; + return true; + }); + OS << "DEPEND END (" << NumDepends << ")\n"; + OS << "INCLUDE START\n"; + unsigned NumIncludes = 0; + Reader->foreachInclude([&](const IndexUnitReader::IncludeInfo &Inc) -> bool { + OS << Inc.SourcePath << ":" << Inc.SourceLine << " | "; + OS << Inc.TargetPath << '\n'; + ++NumIncludes; + return true; + }); + OS << "INCLUDE END (" << NumIncludes << ")\n"; + + return false; +}; + +//===----------------------------------------------------------------------===// +// Print Store Units +//===----------------------------------------------------------------------===// + +static bool printStoreUnit(indexstore::IndexStore &Store, StringRef UnitName, + raw_ostream &OS) { + std::string Error; + indexstore::IndexUnitReader Reader(Store, UnitName, Error); + if (!Reader) { + errs() << "error loading unit: " << Error << "\n"; + return true; + } + + OS << "provider: " << Reader.getProviderIdentifier() << '-' << Reader.getProviderVersion() << '\n'; + OS << "is-system: " << Reader.isSystemUnit() << '\n'; + OS << "is-module: " << Reader.isModuleUnit() << '\n'; + OS << "module-name: " << (Reader.getModuleName().empty() ? "" : Reader.getModuleName()) << '\n'; + OS << "has-main: " << Reader.hasMainFile() << '\n'; + OS << "main-path: " << Reader.getMainFilePath() << '\n'; + OS << "work-dir: " << Reader.getWorkingDirectory() << '\n'; + OS << "out-file: " << Reader.getOutputFile() << '\n'; + OS << "target: " << Reader.getTarget() << '\n'; + OS << "is-debug: " << Reader.isDebugCompilation() << '\n'; + OS << "DEPEND START\n"; + unsigned NumDepends = 0; + Reader.foreachDependency([&](indexstore::IndexUnitDependency Dep) -> bool { + switch (Dep.getKind()) { + case indexstore::IndexUnitDependency::DependencyKind::Unit: + OS << "Unit | "; break; + case indexstore::IndexUnitDependency::DependencyKind::Record: + OS << "Record | "; break; + case indexstore::IndexUnitDependency::DependencyKind::File: + OS << "File | "; break; + } + OS << (Dep.isSystem() ? "system" : "user"); + OS << " | "; + if (!Dep.getModuleName().empty()) + OS << Dep.getModuleName() << " | "; + OS << Dep.getFilePath() << " | " << Dep.getName() << " | "; + OS << Dep.getModificationTime() << '\n'; + ++NumDepends; + return true; + }); + OS << "DEPEND END (" << NumDepends << ")\n"; + OS << "INCLUDE START\n"; + unsigned NumIncludes = 0; + Reader.foreachInclude([&](indexstore::IndexUnitInclude Inc) -> bool { + OS << Inc.getSourcePath() << ":" << Inc.getSourceLine() << " | "; + OS << Inc.getTargetPath() << '\n'; + ++NumIncludes; + return true; + }); + OS << "INCLUDE END (" << NumIncludes << ")\n"; + + return false; +} + +static int printStoreUnits(StringRef StorePath, raw_ostream &OS) { + std::string Error; + indexstore::IndexStore Store(StorePath, Error); + if (!Store) { + errs() << "error loading store: " << Error << "\n"; + return 1; + } + + bool Success = Store.foreachUnit(/*sorted=*/true, [&](StringRef UnitName) -> bool { + OS << UnitName << '\n'; + OS << "--------\n"; + bool err = printStoreUnit(Store, UnitName, OS); + OS << '\n'; + return !err; + }); + + return !Success; +} + + +#else + +static int printUnit(StringRef Filename, raw_ostream &OS) { + return 1; +} + +static int printStoreUnits(StringRef StorePath, raw_ostream &OS) { + return 1; +} + +#endif + //===----------------------------------------------------------------------===// // Helper Utils //===----------------------------------------------------------------------===// @@ -266,10 +605,208 @@ static void printSymbolNameAndUSR(const Decl *D, ASTContext &Ctx, } } +#if INDEXSTORE_HAS_BLOCKS + +static void printSymbol(const IndexRecordDecl &Rec, raw_ostream &OS) { + printSymbolInfo(Rec.SymInfo, OS); + OS << " | "; + + if (Rec.Name.empty()) + OS << ""; + else + OS << Rec.Name; + OS << " | "; + + if (Rec.USR.empty()) + OS << ""; + else + OS << Rec.USR; + OS << " | "; + + if (Rec.CodeGenName.empty()) + OS << ""; + else + OS << Rec.CodeGenName; + OS << " | "; + + printSymbolRoles(Rec.Roles, OS); + OS << " - "; + printSymbolRoles(Rec.RelatedRoles, OS); + OS << '\n'; +} + +static void printSymbol(const IndexRecordOccurrence &Rec, raw_ostream &OS) { + OS << Rec.Line << ':' << Rec.Column << " | "; + printSymbolInfo(Rec.Dcl->SymInfo, OS); + OS << " | "; + + if (Rec.Dcl->USR.empty()) + OS << ""; + else + OS << Rec.Dcl->USR; + OS << " | "; + + printSymbolRoles(Rec.Roles, OS); + OS << " | "; + OS << "rel: " << Rec.Relations.size() << '\n'; + for (auto &Rel : Rec.Relations) { + OS << '\t'; + printSymbolRoles(Rel.Roles, OS); + OS << " | "; + if (Rel.Dcl->USR.empty()) + OS << ""; + else + OS << Rel.Dcl->USR; + OS << '\n'; + } +} + +static void printSymbol(indexstore::IndexRecordSymbol Sym, raw_ostream &OS) { + SymbolInfo SymInfo{getSymbolKind(Sym.getKind()), + getSymbolSubKind(Sym.getSubKind()), + SymbolPropertySet(Sym.getProperties()), + getSymbolLanguage(Sym.getLanguage())}; + + printSymbolInfo(SymInfo, OS); + OS << " | "; + + if (Sym.getName().empty()) + OS << ""; + else + OS << Sym.getName(); + OS << " | "; + + if (Sym.getUSR().empty()) + OS << ""; + else + OS << Sym.getUSR(); + OS << " | "; + + if (Sym.getCodegenName().empty()) + OS << ""; + else + OS << Sym.getCodegenName(); + OS << " | "; + + printSymbolRoles(Sym.getRoles(), OS); + OS << " - "; + printSymbolRoles(Sym.getRelatedRoles(), OS); + OS << '\n'; +} + +static void printSymbol(indexstore::IndexRecordOccurrence Occur, raw_ostream &OS) { + OS << Occur.getLineCol().first << ':' << Occur.getLineCol().second << " | "; + auto Sym = Occur.getSymbol(); + SymbolInfo SymInfo{getSymbolKind(Sym.getKind()), + getSymbolSubKind(Sym.getSubKind()), + SymbolPropertySet(Sym.getProperties()), + getSymbolLanguage(Sym.getLanguage())}; + + printSymbolInfo(SymInfo, OS); + OS << " | "; + + if (Sym.getUSR().empty()) + OS << ""; + else + OS << Sym.getUSR(); + OS << " | "; + + unsigned NumRelations = 0; + Occur.foreachRelation([&](indexstore::IndexSymbolRelation) { + ++NumRelations; + return true; + }); + + printSymbolRoles(Occur.getRoles(), OS); + OS << " | "; + OS << "rel: " << NumRelations << '\n'; + Occur.foreachRelation([&](indexstore::IndexSymbolRelation Rel) { + OS << '\t'; + printSymbolRoles(Rel.getRoles(), OS); + OS << " | "; + auto Sym = Rel.getSymbol(); + if (Sym.getUSR().empty()) + OS << ""; + else + OS << Sym.getUSR(); + OS << '\n'; + return true; + }); +} + +#else + +static int printRecord(StringRef Filename, raw_ostream &OS) { + return 1; +} +static int printStoreRecords(StringRef StorePath, raw_ostream &OS) { + return 1; +} + +#endif + +static int watchDirectory(StringRef dirPath) { + raw_ostream &OS = outs(); + auto receiver = [&](ArrayRef Events, bool isInitial) { + for (auto evt : Events) { + switch (evt.Kind) { + case DirectoryWatcher::EventKind::Added: + OS << "added: "; break; + case DirectoryWatcher::EventKind::Modified: + OS << "modified: "; break; + case DirectoryWatcher::EventKind::Removed: + OS << "removed: "; break; + case DirectoryWatcher::EventKind::DirectoryDeleted: + OS << "dir deleted: "; break; + + } + OS << evt.Filename << '\n'; + } + }; + std::string Error; + auto watcher = DirectoryWatcher::create(dirPath, receiver, + /*waitInitialSync=*/true, Error); + if (!watcher) { + errs() << "failed creating directory watcher: " << Error << '\n'; + return 1; + } +#if HAVE_CORESERVICES + dispatch_main(); +#endif +} + //===----------------------------------------------------------------------===// // Command line processing. //===----------------------------------------------------------------------===// +bool deconstructPathAndRange(StringRef input, + std::string &filepath, + Optional &lineStart, + unsigned &lineCount) { + StringRef path, range; + std::tie(path, range) = input.split(':'); + StringRef start, end; + std::tie(start, end) = range.split(':'); + filepath = path; + lineCount = 0; + if (start.empty()) + return false; + unsigned num; + if (start.getAsInteger(10, num)) { + errs() << "couldn't convert to integer: " << start << '\n'; + return true; + } + lineStart = num; + if (end.empty()) + return false; + if (end.getAsInteger(10, num)) { + errs() << "couldn't convert to integer: " << end << '\n'; + return true; + } + lineCount = num-lineStart.getValue(); + return false; +} + int indextest_core_main(int argc, const char **argv) { sys::PrintStackTraceOnErrorSignal(argv[0]); PrettyStackTraceProgram X(argc, argv); @@ -305,5 +842,73 @@ int indextest_core_main(int argc, const char **argv) { return printSourceSymbols(CompArgs, options::DumpModuleImports, options::IncludeLocals); } + if (options::Action == ActionType::PrintRecord) { + if (!options::FilePathAndRange.empty()) { + std::string filepath; + Optional lineStart; + unsigned lineCount; + if (deconstructPathAndRange(options::FilePathAndRange, + filepath, lineStart, lineCount)) + return 1; + + if (options::InputFiles.empty()) { + errs() << "error: missing index store path\n"; + return 1; + } + return printStoreFileRecord(options::InputFiles[0], filepath, lineStart, lineCount, outs()); + } + + if (options::InputFiles.empty()) { + errs() << "error: missing input file or directory\n"; + return 1; + } + + if (sys::fs::is_directory(options::InputFiles[0])) + return printStoreRecords(options::InputFiles[0], outs()); + else + return printRecord(options::InputFiles[0], outs()); + } + + if (options::Action == ActionType::PrintUnit) { + if (options::InputFiles.empty()) { + errs() << "error: missing input file or directory\n"; + return 1; + } + + if (sys::fs::is_directory(options::InputFiles[0])) + return printStoreUnits(options::InputFiles[0], outs()); + else + return printUnit(options::InputFiles[0], outs()); + } + + if (options::Action == ActionType::PrintStoreFormatVersion) { + outs() << indexstore::IndexStore::formatVersion() << '\n'; + } + + if (options::Action == ActionType::AggregateAsJSON) { + if (options::InputFiles.empty()) { + errs() << "error: missing input data store directory\n"; + return 1; + } + StringRef storePath = options::InputFiles[0]; + if (options::OutputFile.empty()) + return aggregateDataAsJSON(storePath, outs()); + std::error_code EC; + raw_fd_ostream OS(options::OutputFile, EC, llvm::sys::fs::F_None); + if (EC) { + errs() << "failed to open output file: " << EC.message() << '\n'; + return 1; + } + return aggregateDataAsJSON(storePath, OS); + } + + if (options::Action == ActionType::WatchDir) { + if (options::InputFiles.empty()) { + errs() << "error: missing directory path\n"; + return 1; + } + return watchDirectory(options::InputFiles[0]); + } + return 0; } From 8289650bc1334a77f1a87427d389e2adf8e8e42f Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 26 Jun 2017 11:34:44 +0100 Subject: [PATCH 173/585] "Generate Missing Function Definitions" should insert method stubs into the class @implementation when there's no appropriate category implementation This will require a slight SK update as well. rdar://32875896 apple-llvm-split-commit: bd2ec9d3e33564fa2b1d33bcea293bfce6160ddf apple-llvm-split-dir: clang/ --- .../Refactor/ASTStateSerialization.cpp | 2 ++ .../Refactor/ImplementDeclaredMethods.cpp | 28 ++++++++++++++----- .../Refactor/RefactoringContinuations.h | 9 ++++-- .../implement-declared-methods.m | 11 ++++++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/clang/lib/Tooling/Refactor/ASTStateSerialization.cpp b/clang/lib/Tooling/Refactor/ASTStateSerialization.cpp index 3fa2e606e1f79..2d10fbc31ad33 100644 --- a/clang/lib/Tooling/Refactor/ASTStateSerialization.cpp +++ b/clang/lib/Tooling/Refactor/ASTStateSerialization.cpp @@ -40,6 +40,8 @@ class USRToDeclConverter } // end anonymous namespace const Decl *PersistentToASTSpecificStateConverter::lookupDecl(StringRef USR) { + if (USR.empty()) + return nullptr; auto It = ConvertedDeclRefs.find(USR); if (It != ConvertedDeclRefs.end()) return It->second; diff --git a/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp b/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp index ba4fb903c392c..72649a8f42f21 100644 --- a/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp +++ b/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp @@ -86,11 +86,18 @@ class ImplementDeclaredObjCMethodsOperation : public ImplementDeclaredMethodsOperation< ObjCContainerDecl, ObjCMethodDecl, ImplementDeclaredObjCMethodsOperation> { + const ObjCInterfaceDecl *Interface; + public: ImplementDeclaredObjCMethodsOperation( const ObjCContainerDecl *Container, ArrayRef SelectedMethods) - : ImplementDeclaredMethodsOperation(Container, SelectedMethods) {} + : ImplementDeclaredMethodsOperation(Container, SelectedMethods) { + if (const auto *CD = dyn_cast(Container)) + Interface = CD->getClassInterface(); + else + Interface = nullptr; + } llvm::Expected perform(ASTContext &Context, const Preprocessor &ThePreprocessor, @@ -100,6 +107,7 @@ class ImplementDeclaredObjCMethodsOperation static llvm::Expected runInImplementationAST(ASTContext &Context, const FileID &File, const ObjCContainerDecl *Container, + const ObjCInterfaceDecl *Interface, ArrayRef SelectedMethods); }; @@ -363,27 +371,33 @@ ImplementDeclaredObjCMethodsOperation::perform( using namespace indexer; return continueInExternalASTUnit( fileThatShouldContainImplementationOf(Container), runInImplementationAST, - Container, filter(llvm::makeArrayRef(SelectedMethods), - [](const DeclEntity &D) { return !D.isDefined(); })); + Container, Interface, + filter(llvm::makeArrayRef(SelectedMethods), + [](const DeclEntity &D) { return !D.isDefined(); })); } static const ObjCImplDecl * -getImplementationContainer(const ObjCContainerDecl *Container) { +getImplementationContainer(const ObjCContainerDecl *Container, + const ObjCInterfaceDecl *Interface) { if (!Container) return nullptr; if (const auto *ID = dyn_cast(Container)) return ID->getImplementation(); - if (const auto *CD = dyn_cast(Container)) - return CD->getImplementation(); + if (const auto *CD = dyn_cast(Container)) { + if (const auto *Impl = CD->getImplementation()) + return Impl; + return getImplementationContainer(Interface, /*Interface=*/nullptr); + } return nullptr; } llvm::Expected ImplementDeclaredObjCMethodsOperation::runInImplementationAST( ASTContext &Context, const FileID &File, const ObjCContainerDecl *Container, + const ObjCInterfaceDecl *Interface, ArrayRef SelectedMethods) { const ObjCImplDecl *ImplementationContainer = - getImplementationContainer(Container); + getImplementationContainer(Container, Interface); if (!ImplementationContainer) return llvm::make_error( "the target @interface is not implemented in the continuation AST " diff --git a/clang/lib/Tooling/Refactor/RefactoringContinuations.h b/clang/lib/Tooling/Refactor/RefactoringContinuations.h index 25c3a39f89428..87b65351c74cc 100644 --- a/clang/lib/Tooling/Refactor/RefactoringContinuations.h +++ b/clang/lib/Tooling/Refactor/RefactoringContinuations.h @@ -115,7 +115,8 @@ class PersistentToASTSpecificStateConverter { const PersistentDeclRef &Ref, typename std::enable_if::value>::type * = nullptr) { - ConvertedDeclRefs[Ref.USR] = nullptr; + if (!Ref.USR.empty()) + ConvertedDeclRefs[Ref.USR] = nullptr; return true; } @@ -132,8 +133,10 @@ class PersistentToASTSpecificStateConverter { const std::vector> &Refs, typename std::enable_if::value>::type * = nullptr) { - for (const auto &Ref : Refs) - ConvertedDeclRefs[Ref.USR] = nullptr; + for (const auto &Ref : Refs) { + if (!Ref.USR.empty()) + ConvertedDeclRefs[Ref.USR] = nullptr; + } return true; } diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m index 1c330392d2066..5e56fd0ac8f9d 100644 --- a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m @@ -34,6 +34,7 @@ - (void)someOtherMethod { } // CHECK1: "{{.*}}implement-declared-methods.m" "- (void)method { \n <#code#>;\n}\n\n+ (void)classMethod { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n- (void)method:(int)x with:(int)y { \n <#code#>;\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 // CHECK2: "{{.*}}implement-declared-methods.m" "- (void)method { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n" [[@LINE-2]]:1 // CHECK-EXT: "{{.*}}implement-declared-methods.m" "- (void)anExtensionMethod { \n <#code#>;\n}\n\n" [[@LINE-3]]:1 +// CHECK-CAT-NO-IMPL: "{{.*}}implement-declared-methods.m" "- (void)thisCategoryMethodShouldBeInTheClassImplementation { \n <#code#>;\n}\n\n" [[@LINE-4]]:1 -> [[@LINE-4]]:1 #endif // RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK1 %s // RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-mix-impl %s | FileCheck --check-prefix=CHECK2 %s @@ -66,3 +67,13 @@ - (void)anotherMethod { @end // CHECK3: "{{.*}}implement-declared-methods.m" "- (void)categoryMethod { \n <#code#>;\n}\n\n+ (MyClass *)classCategoryMethod { \n <#code#>;\n}\n\n" [[@LINE-1]]:1 // RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-category-methods -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK3 %s + +@interface MyClass (NoCategoryImplementation) + +// category-no-impl-begin: +1:1 +- (void)thisCategoryMethodShouldBeInTheClassImplementation; +// category-no-impl-end: +0:1 + +@end + +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=category-no-impl -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK-CAT-NO-IMPL %s From a987013fcefe6f440d5780c2a57ee2725dce0705 Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Thu, 29 Jun 2017 17:25:37 -0700 Subject: [PATCH 174/585] [c-index-test] Fix compilation for linux apple-llvm-split-commit: 82f1bbe40a106c5e710c049dd0f60cf3312edcee apple-llvm-split-dir: clang/ --- clang/tools/c-index-test/core_main.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clang/tools/c-index-test/core_main.cpp b/clang/tools/c-index-test/core_main.cpp index 1335b05075cfb..0bafcda1f359f 100644 --- a/clang/tools/c-index-test/core_main.cpp +++ b/clang/tools/c-index-test/core_main.cpp @@ -572,6 +572,12 @@ static int printStoreUnits(StringRef StorePath, raw_ostream &OS) { return 1; } +static int printStoreFileRecord(StringRef storePath, StringRef filePath, + Optional lineStart, unsigned lineCount, + raw_ostream &OS) { + return 1; +} + #endif //===----------------------------------------------------------------------===// @@ -772,6 +778,8 @@ static int watchDirectory(StringRef dirPath) { } #if HAVE_CORESERVICES dispatch_main(); +#else + return 1; #endif } @@ -881,9 +889,11 @@ int indextest_core_main(int argc, const char **argv) { return printUnit(options::InputFiles[0], outs()); } +#if INDEXSTORE_HAS_BLOCKS if (options::Action == ActionType::PrintStoreFormatVersion) { outs() << indexstore::IndexStore::formatVersion() << '\n'; } +#endif if (options::Action == ActionType::AggregateAsJSON) { if (options::InputFiles.empty()) { From c8dce8a2e5ca19f50b740344dade7100734366ef Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Fri, 30 Jun 2017 10:58:00 +0100 Subject: [PATCH 175/585] Look through declarations that start in a macro but end up in a source file when searching for a declaration at some location rdar://32934347 apple-llvm-split-commit: 6ea0e95dbd98a66942c6aaf9d872a70813c87b3f apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/USRFinder.cpp | 14 +++++++++----- .../test/Refactor/Rename/Inputs/TransparentEnum.h | 1 + clang/test/Refactor/Rename/TransparentTypedef.m | 13 +++++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 clang/test/Refactor/Rename/Inputs/TransparentEnum.h diff --git a/clang/lib/Tooling/Refactor/USRFinder.cpp b/clang/lib/Tooling/Refactor/USRFinder.cpp index d4e78bd28a6b3..95e5244639daa 100644 --- a/clang/lib/Tooling/Refactor/USRFinder.cpp +++ b/clang/lib/Tooling/Refactor/USRFinder.cpp @@ -556,6 +556,7 @@ const NamedDecl *getNamedDeclAt(const ASTContext &Context, SourceLocation Point) { if (Point.isMacroID()) Point = Context.getSourceManager().getSpellingLoc(Point); + // FIXME: If point is in a system header, return early here. OccurrenceCheckerType PointChecker = [Point, &Context]( const NamedDecl *Decl, SourceLocation Start, SourceLocation End) -> bool { @@ -566,13 +567,16 @@ const NamedDecl *getNamedDeclAt(const ASTContext &Context, NamedDeclFindingASTVisitor Visitor(PointChecker, Context); // We only want to search the decls that exist in the same file as the point. - StringRef SearchFile = Context.getSourceManager().getFilename(Point); + FileID InitiationFile = Context.getSourceManager().getFileID(Point); for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { - const SourceLocation FileLoc = CurrDecl->getLocStart(); - StringRef FileName = Context.getSourceManager().getFilename(FileLoc); - // FIME: Skip system headers. + const SourceRange DeclRange = CurrDecl->getSourceRange(); + SourceLocation FileLoc; + if (DeclRange.getBegin().isMacroID() && !DeclRange.getEnd().isMacroID()) + FileLoc = DeclRange.getEnd(); + else + FileLoc = Context.getSourceManager().getSpellingLoc(DeclRange.getBegin()); // FIXME: Add test. - if (FileName == SearchFile) + if (Context.getSourceManager().getFileID(FileLoc) == InitiationFile) Visitor.TraverseDecl(CurrDecl); if (Visitor.isDone()) break; diff --git a/clang/test/Refactor/Rename/Inputs/TransparentEnum.h b/clang/test/Refactor/Rename/Inputs/TransparentEnum.h new file mode 100644 index 0000000000000..b21adae6ae786 --- /dev/null +++ b/clang/test/Refactor/Rename/Inputs/TransparentEnum.h @@ -0,0 +1 @@ +#define TRANSPARENT_ENUM(_name, _type) enum _name:_type _name; enum _name : _type diff --git a/clang/test/Refactor/Rename/TransparentTypedef.m b/clang/test/Refactor/Rename/TransparentTypedef.m index 5717bb8636cd7..1a2dcb1e5afea 100644 --- a/clang/test/Refactor/Rename/TransparentTypedef.m +++ b/clang/test/Refactor/Rename/TransparentTypedef.m @@ -36,5 +36,14 @@ typedef OPAQUE(Separate) { // CHECK3: rename [[@LINE]]:16 -> [[@LINE]]:24 Separate separateT; // CHECK3: rename [[@LINE]]:1 -> [[@LINE]]:9 struct Separate separateE; // CHECK4: rename [[@LINE]]:8 -> [[@LINE]]:16 -// RUN: clang-refactor-test rename-initiate -at=%s:31:16 -at=%s:36:1 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK3 %s -// RUN: clang-refactor-test rename-initiate -at=%s:37:8 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK4 %s +// RUN: clang-refactor-test rename-initiate -at=%s:36:1 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test rename-initiate -at=%s:31:16 -at=%s:37:8 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK4 %s + +#include "Inputs/TransparentEnum.h" + +// CHECK5: 'c:@E@AnotherEnum2' +typedef TRANSPARENT_ENUM(AnotherEnum2, int) { // CHECK5: rename [[@LINE]]:26 -> [[@LINE]]:38 + EnumThing = 0, // CHECK6: [[@LINE]]:3 -> [[@LINE]]:12 +}; +// RUN: clang-refactor-test rename-initiate -at=%s:45:26 -new-name=foo -dump-symbols %s | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-refactor-test rename-initiate -at=%s:46:3 -new-name=foo %s | FileCheck --check-prefix=CHECK6 %s From 5ecdb772817b8493fe313aeca51d46249404b563 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 26 Jun 2017 15:31:37 +0100 Subject: [PATCH 176/585] [refactor] Indexed file rename should match 'prop' when renaming 'setProp' rdar://32375673 apple-llvm-split-commit: edeb489d3049373634384b580bb1b925dae10481 apple-llvm-split-dir: clang/ --- .../clang/Tooling/Refactor/RenamedSymbol.h | 32 ++++++++-- .../Tooling/Refactor/RenameIndexedFile.cpp | 64 ++++++++++++++----- .../Refactor/Rename/IndexedObjCProperty.m | 15 +++++ clang/tools/libclang/CRefactor.cpp | 18 +++--- 4 files changed, 96 insertions(+), 33 deletions(-) diff --git a/clang/include/clang/Tooling/Refactor/RenamedSymbol.h b/clang/include/clang/Tooling/Refactor/RenamedSymbol.h index 5c5a9061eac72..d54c49799e821 100644 --- a/clang/include/clang/Tooling/Refactor/RenamedSymbol.h +++ b/clang/include/clang/Tooling/Refactor/RenamedSymbol.h @@ -54,6 +54,9 @@ class Symbol { /// account for things like Objective-C selectors. // TODO: Rename class SymbolOccurrence { + /// The source locations that correspond to the occurence of the symbol. + SmallVector Locations; + public: enum OccurrenceKind { /// \brief This occurrence is an exact match and can be renamed @@ -87,29 +90,44 @@ class SymbolOccurrence { /// The index of the symbol stored in a \c SymbolOperation which matches this /// occurrence. unsigned SymbolIndex; - /// The source locations that correspond to the occurence of the symbol. - SmallVector Locations; SymbolOccurrence() : Kind(MatchingSymbol), IsMacroExpansion(false), SymbolIndex(0) {} SymbolOccurrence(OccurrenceKind Kind, bool IsMacroExpansion, unsigned SymbolIndex, ArrayRef Locations) - : Kind(Kind), IsMacroExpansion(IsMacroExpansion), - SymbolIndex(SymbolIndex), - Locations(Locations.begin(), Locations.end()) { + : Locations(Locations.begin(), Locations.end()), Kind(Kind), + IsMacroExpansion(IsMacroExpansion), SymbolIndex(SymbolIndex) { assert(!Locations.empty() && "Renamed occurence without locations!"); } SymbolOccurrence(SymbolOccurrence &&) = default; SymbolOccurrence &operator=(SymbolOccurrence &&) = default; + ArrayRef locations() const { + if (Kind == MatchingImplicitProperty && Locations.size() == 2) + return llvm::makeArrayRef(Locations).drop_back(); + return Locations; + } + /// Return the source range that corresponds to an individual source location /// in this occurrence. SourceRange getLocationRange(SourceLocation Loc, size_t OldNameSize) const { - return SourceRange( - Loc, IsMacroExpansion ? Loc : Loc.getLocWithOffset(OldNameSize)); + SourceLocation EndLoc; + // Implicit property references might store the end as the second location + // to take into account the match for 'prop' when the old name is 'setProp'. + if (Kind == MatchingImplicitProperty && Locations.size() == 2) { + assert(Loc == Locations[0] && "invalid loc"); + EndLoc = Locations[1]; + } else + EndLoc = IsMacroExpansion ? Loc : Loc.getLocWithOffset(OldNameSize); + return SourceRange(Loc, EndLoc); } + + friend bool operator<(const SymbolOccurrence &LHS, + const SymbolOccurrence &RHS); + friend bool operator==(const SymbolOccurrence &LHS, + const SymbolOccurrence &RHS); }; /// \brief Less-than operator between the two renamed symbol occurrences. diff --git a/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp b/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp index 5ec491c557b6b..55238bc0788ba 100644 --- a/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp +++ b/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp @@ -48,22 +48,36 @@ IndexedFileOccurrenceProducer::IndexedFileOccurrenceProducer( namespace { -enum class MatchKind { SourceMatch, MacroExpansion, None }; +enum class MatchKind { + SourceMatch, + SourcePropSetterMatch, + MacroExpansion, + None +}; } // end anonymous namespace +static bool isSetterNameEqualToPropName(StringRef SetterName, + StringRef PropertyName) { + assert(SetterName.startswith("set") && "invalid setter name"); + SetterName = SetterName.drop_front(3); + return SetterName[0] == toUppercase(PropertyName[0]) && + SetterName.drop_front() == PropertyName.drop_front(); +} + static MatchKind checkOccurrence(const IndexedOccurrence &Occurrence, const IndexedSymbol &Symbol, const SourceManager &SM, const LangOptions &LangOpts, - SourceLocation &BeginLoc) { + SourceRange &SymbolRange, + bool AllowObjCSetterProp = false) { if (!Occurrence.Line || !Occurrence.Column) return MatchKind::None; // Ignore any invalid indexed locations. // Ensure that the first string in the name is present at the given // location. - BeginLoc = SM.translateLineCol(SM.getMainFileID(), Occurrence.Line, - Occurrence.Column); + SourceLocation BeginLoc = SM.translateLineCol( + SM.getMainFileID(), Occurrence.Line, Occurrence.Column); if (BeginLoc.isInvalid()) return MatchKind::None; StringRef SymbolNameStart = Symbol.Name[0]; @@ -83,8 +97,17 @@ static MatchKind checkOccurrence(const IndexedOccurrence &Occurrence, RawLex.LexFromRawLexer(Tok); if (Tok.isNot(tok::raw_identifier) || Tok.getLocation() != BeginLoc) return MatchKind::None; - return Tok.getRawIdentifier() == SymbolNameStart ? MatchKind::SourceMatch - : MatchKind::MacroExpansion; + SymbolRange = SourceRange(BeginLoc, Tok.getEndLoc()); + if (Tok.getRawIdentifier() == SymbolNameStart) + return MatchKind::SourceMatch; + // Match 'prop' when looking for 'setProp'. + // FIXME: Verify that the previous token is a '.' to be sure. + if (AllowObjCSetterProp && + Occurrence.Kind == IndexedOccurrence::IndexedObjCMessageSend && + SymbolNameStart.startswith("set") && + isSetterNameEqualToPropName(SymbolNameStart, Tok.getRawIdentifier())) + return MatchKind::SourcePropSetterMatch; + return MatchKind::MacroExpansion; } static void @@ -366,16 +389,22 @@ void IndexedFileOccurrenceProducer::ExecuteAction() { Consumer); continue; } - SourceLocation BeginLoc; - MatchKind Match = - checkOccurrence(Occurrence, Symbol.value(), SM, LangOpts, BeginLoc); + SourceRange SymbolRange; + MatchKind Match = checkOccurrence(Occurrence, Symbol.value(), SM, + LangOpts, SymbolRange, + /*AllowObjCSetterProp=*/true); if (Match == MatchKind::None) continue; - - SymbolOccurrence Result(SymbolOccurrence::MatchingSymbol, - /*IsMacroExpansion=*/Match == - MatchKind::MacroExpansion, - Symbol.index(), BeginLoc); + llvm::SmallVector Locs; + Locs.push_back(SymbolRange.getBegin()); + bool IsImpProp = Match == MatchKind::SourcePropSetterMatch; + if (IsImpProp) + Locs.push_back(SymbolRange.getEnd()); + SymbolOccurrence Result( + IsImpProp ? SymbolOccurrence::MatchingImplicitProperty + : SymbolOccurrence::MatchingSymbol, + /*IsMacroExpansion=*/Match == MatchKind::MacroExpansion, + Symbol.index(), Locs); Consumer.handleOccurrence(Result, SM, LangOpts); } } @@ -492,11 +521,12 @@ findObjCMultiPieceSelectorOccurrences(CompilerInstance &CI, // Selectors and names in #includes shouldn't really mix. if (Occurrence.Kind == IndexedOccurrence::InclusionDirective) continue; - SourceLocation Loc; - MatchKind Match = - checkOccurrence(Occurrence, Symbol.value(), SM, LangOpts, Loc); + SourceRange SymbolRange; + MatchKind Match = checkOccurrence(Occurrence, Symbol.value(), SM, + LangOpts, SymbolRange); if (Match == MatchKind::None) continue; + SourceLocation Loc = SymbolRange.getBegin(); if (Match == MatchKind::MacroExpansion) { SymbolOccurrence Result(SymbolOccurrence::MatchingSymbol, /*IsMacroExpansion=*/true, Symbol.index(), Loc); diff --git a/clang/test/Refactor/Rename/IndexedObjCProperty.m b/clang/test/Refactor/Rename/IndexedObjCProperty.m index d8725754932ea..003186dab99dc 100644 --- a/clang/test/Refactor/Rename/IndexedObjCProperty.m +++ b/clang/test/Refactor/Rename/IndexedObjCProperty.m @@ -28,3 +28,18 @@ - (void)foo { // CHECK: selector [[@LINE-3]]:11 // CHECK: selector "setFoo" [[@LINE-3]]:11 // CHECK-NOT: selector + +@interface ImplicitProperty + +- (int)implicit; // IMPL_GET: rename [[@LINE]]:8 -> [[@LINE]]:16 +- (void)setImplicit:(int)x; // IMPL_SET: rename [[@LINE]]:9 -> [[@LINE]]:20 + +@end + +void useImplicitProperty(ImplicitProperty *x) { + x.implicit; // IMPL_GET: rename [[@LINE]]:5 -> [[@LINE]]:13 + x.implicit = 0; // IMPL_SET: implicit-property [[@LINE]]:5 -> [[@LINE]]:13 +} + +// RUN: clang-refactor-test rename-indexed-file -name=implicit -new-name=foo -indexed-file=%s -indexed-at=objc-im:34:8 -indexed-at=objc-message:40:5 %s | FileCheck --check-prefix=IMPL_GET %s +// RUN: clang-refactor-test rename-indexed-file -name=setImplicit -new-name=setFoo -indexed-file=%s -indexed-at=objc-im:35:9 -indexed-at=objc-message:41:5 %s | FileCheck --check-prefix=IMPL_SET %s diff --git a/clang/tools/libclang/CRefactor.cpp b/clang/tools/libclang/CRefactor.cpp index 098e685f880e3..506f8d28f4280 100644 --- a/clang/tools/libclang/CRefactor.cpp +++ b/clang/tools/libclang/CRefactor.cpp @@ -101,15 +101,15 @@ class RenamingResult { void addOccurrence(const rename::SymbolOccurrence &RenamedOccurrence, const SourceManager &SM, const LangOptions &LangOpts) { CXRefactoringReplacement_Old *OccurrenceReplacements = - Replacements.Allocate(RenamedOccurrence.Locations.size()); + Replacements.Allocate(RenamedOccurrence.locations().size()); unsigned I = 0; const auto &SymbolNameInfo = NameInfo[RenamedOccurrence.SymbolIndex]; if (!RenamedOccurrence.IsMacroExpansion && RenamedOccurrence.Kind != rename::SymbolOccurrence::MatchingComment && RenamedOccurrence.Kind != rename::SymbolOccurrence::MatchingDocComment) - assert(RenamedOccurrence.Locations.size() == SymbolNameInfo.size()); - for (const auto &Location : RenamedOccurrence.Locations) { + assert(RenamedOccurrence.locations().size() == SymbolNameInfo.size()); + for (const auto &Location : RenamedOccurrence.locations()) { CXSourceRange Range = cxloc::translateSourceRange( SM, LangOpts, CharSourceRange::getCharRange(RenamedOccurrence.getLocationRange( @@ -188,7 +188,7 @@ class RenamingResult { llvm::StringMap FilenamesToSymbolOccurrences; for (auto &Occurrence : Results) { const std::pair DecomposedLocation = - SM.getDecomposedLoc(Occurrence.Locations[0]); + SM.getDecomposedLoc(Occurrence.locations()[0]); const FileEntry *Entry = SM.getFileEntryForID(DecomposedLocation.first); assert(Entry && "Invalid file entry"); auto &FileOccurrences = @@ -259,16 +259,16 @@ class SymbolOccurrencesResult { void addOccurrence(const rename::SymbolOccurrence &RenamedOccurrence, const SourceManager &SM, const LangOptions &LangOpts) { - CXFileRange *OccurrenceRanges = - Ranges.Allocate(RenamedOccurrence.Locations.size()); + ArrayRef Locations = RenamedOccurrence.locations(); + CXFileRange *OccurrenceRanges = Ranges.Allocate(Locations.size()); unsigned I = 0; const auto &SymbolNameInfo = NameInfo[RenamedOccurrence.SymbolIndex]; if (!RenamedOccurrence.IsMacroExpansion && RenamedOccurrence.Kind != rename::SymbolOccurrence::MatchingComment && RenamedOccurrence.Kind != rename::SymbolOccurrence::MatchingDocComment) - assert(RenamedOccurrence.Locations.size() == SymbolNameInfo.size()); - for (const auto &Location : RenamedOccurrence.Locations) { + assert(Locations.size() == SymbolNameInfo.size()); + for (const auto &Location : Locations) { CXSourceRange Range = cxloc::translateSourceRange( SM, LangOpts, CharSourceRange::getCharRange(RenamedOccurrence.getLocationRange( @@ -327,7 +327,7 @@ class SymbolOccurrencesResult { llvm::StringMap FilenamesToSymbolOccurrences; for (auto &Occurrence : Results) { const std::pair DecomposedLocation = - SM.getDecomposedLoc(Occurrence.Locations[0]); + SM.getDecomposedLoc(Occurrence.locations()[0]); const FileEntry *Entry = SM.getFileEntryForID(DecomposedLocation.first); assert(Entry && "Invalid file entry"); auto &FileOccurrences = From d965fa06180845991960ec28884955016f776fa9 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Sun, 2 Jul 2017 14:58:30 -0700 Subject: [PATCH 177/585] Update IndexerQueries.cpp according to changes made in r306878. Patch by Michael Zolotukhin. apple-llvm-split-commit: 9fb6d709f324c0e5580ce5f087e0014db227581b apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/IndexerQueries.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/lib/Tooling/Refactor/IndexerQueries.cpp b/clang/lib/Tooling/Refactor/IndexerQueries.cpp index 063a27be3b83a..bf4fb6539cfba 100644 --- a/clang/lib/Tooling/Refactor/IndexerQueries.cpp +++ b/clang/lib/Tooling/Refactor/IndexerQueries.cpp @@ -59,7 +59,6 @@ struct QueryYAMLNode { } // end anonymous namespace -LLVM_YAML_IS_SEQUENCE_VECTOR(int) LLVM_YAML_IS_SEQUENCE_VECTOR(QueryPredicateNode) LLVM_YAML_IS_SEQUENCE_VECTOR(QueryYAMLNode) From ddddfa0063fdd36ffe07d77026f794276911ad20 Mon Sep 17 00:00:00 2001 From: Ben Langmuir Date: Wed, 12 Jul 2017 13:27:02 -0700 Subject: [PATCH 178/585] [index] Add a test for a crash with unnamed NamedDecls Previously this would crash during record hashing. rdar://problem/32474406 apple-llvm-split-commit: f127690cd0cafdd53c70f839dddaab98eca7406f apple-llvm-split-dir: clang/ --- .../Store/record-hash-crash-invalid-name.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 clang/test/Index/Store/record-hash-crash-invalid-name.cpp diff --git a/clang/test/Index/Store/record-hash-crash-invalid-name.cpp b/clang/test/Index/Store/record-hash-crash-invalid-name.cpp new file mode 100644 index 0000000000000..4479789e9a236 --- /dev/null +++ b/clang/test/Index/Store/record-hash-crash-invalid-name.cpp @@ -0,0 +1,17 @@ +// Makes sure it doesn't crash. + +// XFAIL: linux + +// RUN: rm -rf %t +// RUN: not %clang_cc1 %s -index-store-path %t/idx -std=c++14 +// RUN: c-index-test core -print-record %t/idx | FileCheck %s + +namespace rdar32474406 { +void foo(); +typedef void (*Func_t)(); +// CHECK: [[@LINE+4]]:1 | type-alias/C | c:record-hash-crash-invalid-name.cpp@N@rdar32474406@T@Func_t | Ref,RelCont | rel: 1 +// CHECK-NEXT: RelCont | c:@N@rdar32474406 +// CHECK: [[@LINE+2]]:14 | function/C | c:@N@rdar32474406@F@foo# | Ref,RelCont | rel: 1 +// CHECK-NEXT: RelCont | c:@N@rdar32474406 +Func_t[] = { foo }; // invalid decomposition +} From 2d8e1c4f761f21623715802e26050e6ccb979aa2 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 10 Jul 2017 17:21:06 +0100 Subject: [PATCH 179/585] [refactor][rename] Indexed ObjC method renames should take empty selector name pieces into account rdar://33188656 apple-llvm-split-commit: 454f38d28bbbe1312e7e8388b45a0ebcc0613e0f apple-llvm-split-dir: clang/ --- .../clang/Tooling/Refactor/SymbolName.h | 8 +++ .../Tooling/Refactor/RenameIndexedFile.cpp | 72 ++++++++++++++----- .../Rename/IndexedObjCMethodEmptySelector.mm | 49 +++++++++++++ 3 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 clang/test/Refactor/Rename/IndexedObjCMethodEmptySelector.mm diff --git a/clang/include/clang/Tooling/Refactor/SymbolName.h b/clang/include/clang/Tooling/Refactor/SymbolName.h index b2d18720987ae..f348a02a813e0 100644 --- a/clang/include/clang/Tooling/Refactor/SymbolName.h +++ b/clang/include/clang/Tooling/Refactor/SymbolName.h @@ -52,6 +52,14 @@ class SymbolName { ArrayRef strings() const { return Strings; } + bool containsEmptyPiece() const { + for (const auto &String : Strings) { + if (String.empty()) + return true; + } + return false; + } + void print(raw_ostream &OS) const; private: diff --git a/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp b/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp index 55238bc0788ba..82e9c917107e2 100644 --- a/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp +++ b/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp @@ -81,7 +81,9 @@ static MatchKind checkOccurrence(const IndexedOccurrence &Occurrence, if (BeginLoc.isInvalid()) return MatchKind::None; StringRef SymbolNameStart = Symbol.Name[0]; - SourceLocation EndLoc = BeginLoc.getLocWithOffset(SymbolNameStart.size()); + SourceLocation EndLoc = BeginLoc.getLocWithOffset(std::max( + SymbolNameStart.size(), + size_t(1))); // Take empty Objective-C selector pieces into account. if (!SM.isBeforeInTranslationUnit(BeginLoc, EndLoc)) { // Ignore any invalid source ranges. This can occur if the indexed // location is invalid. @@ -95,8 +97,16 @@ static MatchKind checkOccurrence(const IndexedOccurrence &Occurrence, File->getBufferStart() + DecomposedLoc.second, File->getBufferEnd()); Token Tok; RawLex.LexFromRawLexer(Tok); - if (Tok.isNot(tok::raw_identifier) || Tok.getLocation() != BeginLoc) + if (Tok.isNot(tok::raw_identifier) || Tok.getLocation() != BeginLoc) { + if (SymbolNameStart.empty() && Tok.is(tok::colon) && + Tok.getLocation() == BeginLoc) { + // Must be the location of an empty Objective-C selector piece. + SymbolRange = SourceRange(BeginLoc, BeginLoc); + return MatchKind::SourceMatch; + } + // FIXME: Handle empty selector piece in a macro? return MatchKind::None; + } SymbolRange = SourceRange(BeginLoc, Tok.getEndLoc()); if (Tok.getRawIdentifier() == SymbolNameStart) return MatchKind::SourceMatch; @@ -176,18 +186,22 @@ SelectorParser::ParseState SelectorParser::stateForToken(const Token &RawTok) { break; SelectorLocations.clear(); return ExpectingSelectorPiece; - case ExpectingSelectorPiece: + case ExpectingSelectorPiece: { assert(SelectorLocations.size() < Name.size() && "Expecting invalid selector piece"); - if (RawTok.isNot(tok::raw_identifier) || - RawTok.getRawIdentifier() != Name[SelectorLocations.size()]) + StringRef NamePiece = Name[SelectorLocations.size()]; + if ((RawTok.isNot(tok::raw_identifier) || + RawTok.getRawIdentifier() != NamePiece) && + !(NamePiece.empty() && RawTok.is(tok::colon))) { break; + } SelectorLocations.push_back(RawTok.getLocation()); if (SelectorLocations.size() == Name.size()) { // We found the selector that we were looking for, now check for ')'. - return ExpectingRParenOrColon; + return NamePiece.empty() ? ExpectingRParen : ExpectingRParenOrColon; } - return ExpectingColon; + return NamePiece.empty() ? ExpectingSelectorPiece : ExpectingColon; + } case ExpectingColon: if (RawTok.is(tok::colon)) return ExpectingSelectorPiece; @@ -210,6 +224,15 @@ SelectorParser::ParseState SelectorParser::stateForToken(const Token &RawTok) { } bool SelectorParser::handleToken(const Token &RawTok) { + if (RawTok.is(tok::coloncolon)) { + // Split the '::' into two ':'. + Token T(RawTok); + T.setKind(tok::colon); + T.setLength(1); + handleToken(T); + T.setLocation(T.getLocation().getLocWithOffset(1)); + return handleToken(T); + } State = stateForToken(RawTok); if (State != Success) return false; @@ -221,14 +244,18 @@ static void collectTextualMatchesInComment( ArrayRef Symbols, SourceLocation CommentLoc, StringRef Comment, llvm::SmallVectorImpl &Result) { for (const auto &Symbol : llvm::enumerate(Symbols)) { + const SymbolName &Name = Symbol.value().Name; + if (Name.containsEmptyPiece()) // Ignore Objective-C selectors with empty + // pieces. + continue; size_t Offset = 0; while (true) { - Offset = Comment.find(Symbol.value().Name[0], /*From=*/Offset); + Offset = Comment.find(Name[0], /*From=*/Offset); if (Offset == StringRef::npos) break; Result.push_back( {CommentLoc.getLocWithOffset(Offset), (unsigned)Symbol.index()}); - Offset += Symbol.value().Name[0].size(); + Offset += Name[0].size(); } } } @@ -432,12 +459,20 @@ enum class ObjCSymbolSelectorKind { MessageSend, MethodDecl }; } // end anonymous namespace +static bool isMatchingSelectorName(const Token &Tok, const Token &Next, + StringRef NamePiece) { + if (NamePiece.empty()) + return Tok.is(tok::colon); + return Tok.is(tok::raw_identifier) && Next.is(tok::colon) && + Tok.getRawIdentifier() == NamePiece; +} + static bool findObjCSymbolSelectorPieces(ArrayRef Tokens, const SymbolName &Name, SmallVectorImpl &Pieces, ObjCSymbolSelectorKind Kind) { assert(!Tokens.empty() && "no tokens"); - assert(Tokens[0].getRawIdentifier() == Name[0]); + assert(Name[0].empty() || Tokens[0].getRawIdentifier() == Name[0]); assert(Name.size() > 1); assert(Pieces.empty()); @@ -453,13 +488,17 @@ findObjCSymbolSelectorPieces(ArrayRef Tokens, const SymbolName &Name, // Start looking for the next selector piece. unsigned Last = Tokens.size() - 1; // Skip the ':' or any other token after the first selector piece token. - for (unsigned Index = 2; Index < Last; ++Index) { + for (unsigned Index = Name[0].empty() ? 1 : 2; Index < Last; ++Index) { const auto &Tok = Tokens[Index]; bool NoScoping = SquareCount == 0 && BraceCount == 0 && ParenCount == 0; - if (NoScoping && Tok.is(tok::raw_identifier) && - Tokens[Index + 1].is(tok::colon) && - Tok.getRawIdentifier() == Name[Pieces.size()]) { + if (NoScoping && + isMatchingSelectorName(Tok, Tokens[Index + 1], Name[Pieces.size()])) { + if (!Name[Pieces.size()].empty()) { + // Skip the ':' after the name. This ensures that it won't match a + // follow-up selector piece with an empty name. + ++Index; + } Pieces.push_back(Tok.getLocation()); // All the selector pieces have been found. if (Pieces.size() == Name.size()) @@ -565,10 +604,11 @@ findObjCMultiPieceSelectorOccurrences(CompilerInstance &CI, auto It = MappedIndexedOccurrences.find(Tok.getLocation().getRawEncoding()); if (It == MappedIndexedOccurrences.end()) continue; - if (Tok.getKind() != tok::raw_identifier) + unsigned SymbolIndex = It->second.second; + if (Tok.getKind() != tok::raw_identifier && + !(Symbols[SymbolIndex].Name[0].empty() && Tok.is(tok::colon))) continue; const IndexedOccurrence &Occurrence = It->second.first; - unsigned SymbolIndex = It->second.second; // Scan the source for the remaining selector pieces. SmallVector SelectorPieces; diff --git a/clang/test/Refactor/Rename/IndexedObjCMethodEmptySelector.mm b/clang/test/Refactor/Rename/IndexedObjCMethodEmptySelector.mm new file mode 100644 index 0000000000000..f533331a3f859 --- /dev/null +++ b/clang/test/Refactor/Rename/IndexedObjCMethodEmptySelector.mm @@ -0,0 +1,49 @@ + +@interface EmptySelectorsRule_Psych + +- (int):(int)_; // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:8 +- (void)test: (int)x :(int)y; // CHECK2: rename [[@LINE]]:9 -> [[@LINE]]:13, [[@LINE]]:22 -> [[@LINE]]:22 +- (void):(int)_ :(int) m:(int)z; // CHECK3: rename [[@LINE]]:9 -> [[@LINE]]:9, [[@LINE]]:17 -> [[@LINE]]:17, [[@LINE]]:25 -> [[@LINE]]:25 + +@end + +namespace g { + int x; +} + +@implementation EmptySelectorsRule_Psych + +- (int):(int)_ { // CHECK1: rename [[@LINE]]:8 -> [[@LINE]]:8 + [self :2]; // CHECK1: rename [[@LINE]]:11 -> [[@LINE]]:11 + SEL s0 = @selector(:); // CHECK1: selector [[@LINE]]:24 -> [[@LINE]]:24 + // CHECK1-NOT: comment + // CHECK1-NOT: rename + // CHECK1-NOT: selector +// RUN: clang-refactor-test rename-indexed-file -name=: -new-name=foo -indexed-file=%s -indexed-symbol-kind=objc-im -indexed-at=4:8 -indexed-at=16:8 -indexed-at=objc-message:17:11 %s | FileCheck --check-prefix=CHECK1 %s + return 0; +} +- (void)test: (int)x :(int)y { } // CHECK2: rename [[@LINE]]:9 -> [[@LINE]]:13, [[@LINE]]:22 -> [[@LINE]]:22 +- (void) :(int)_ :(int)m :(int)z { // CHECK3: rename [[@LINE]]:10 -> [[@LINE]]:10, [[@LINE]]:18 -> [[@LINE]]:18, [[@LINE]]:26 -> [[@LINE]]:26 + [self test:0:1]; // CHECK2: rename [[@LINE]]:11 -> [[@LINE]]:15, [[@LINE]]:17 -> [[@LINE]]:17 + SEL s1 = @selector(test::); // CHECK2: selector [[@LINE]]:24 -> [[@LINE]]:28, [[@LINE]]:29 -> [[@LINE]]:29 + @selector(test: :); // CHECK2: selector [[@LINE]]:15 -> [[@LINE]]:19, [[@LINE]]:21 -> [[@LINE]]:21 + // CHECK2-NOT: comment + // CHECK2-NOT: rename + // CHECK2-NOT: selector +// RUN: clang-refactor-test rename-indexed-file -name=test:: -new-name=foo:bar: -indexed-file=%s -indexed-symbol-kind=objc-im -indexed-at=5:9 -indexed-at=25:9 -indexed-at=objc-message:27:11 %s | FileCheck --check-prefix=CHECK2 %s + + [self: ::g::x + ([self: 0]):~0 :3]; // CHECK3: rename [[@LINE]]:10 -> [[@LINE]]:10, [[@LINE]]:32 -> [[@LINE]]:32, [[@LINE]]:36 -> [[@LINE]]:36 + SEL s2 = @selector(:::); // CHECK3: selector [[@LINE]]:24 -> [[@LINE]]:24, [[@LINE]]:25 -> [[@LINE]]:25, [[@LINE]]:26 -> [[@LINE]]:26 + @selector(: ::); // CHECK3: selector [[@LINE]]:15 -> [[@LINE]]:15, [[@LINE]]:17 -> [[@LINE]]:17, [[@LINE]]:18 -> [[@LINE]]:18 + @selector(::::); // not matching. + // CHECK3-NOT: comment + // CHECK3-NOT: rename + // CHECK3-NOT: selector +// RUN: clang-refactor-test rename-indexed-file -name=::: -new-name=do:stuff:: -indexed-file=%s -indexed-symbol-kind=objc-im -indexed-at=6:9 -indexed-at=26:10 -indexed-at=objc-message:35:10 %s | FileCheck --check-prefix=CHECK3 %s + + // NO Textual matches: text: + // : + // ::: +} + +@end From 145a7725af64ca55c168aa82513b2c4abfdae226 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 10 Jul 2017 17:40:07 +0100 Subject: [PATCH 180/585] [refactor][rename] Initiate rename even for blank selectors! rdar://33188656 apple-llvm-split-commit: 7088654281909cd409d2c29ec1cab26589038148 apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/USRFinder.cpp | 12 ++++++++---- clang/test/Refactor/Rename/ObjCMethod.m | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/clang/lib/Tooling/Refactor/USRFinder.cpp b/clang/lib/Tooling/Refactor/USRFinder.cpp index 95e5244639daa..6959490f014e5 100644 --- a/clang/lib/Tooling/Refactor/USRFinder.cpp +++ b/clang/lib/Tooling/Refactor/USRFinder.cpp @@ -127,8 +127,10 @@ class NamedDeclFindingASTVisitor bool VisitObjCMethodDecl(const ObjCMethodDecl *Decl) { // Check all of the selector source ranges. for (unsigned I = 0, E = Decl->getNumSelectorLocs(); I != E; ++I) { - if (!checkOccurrence(Decl, Decl->getSelectorLoc(I), - Decl->getSelector().getNameForSlot(I).size())) + SourceLocation Loc = Decl->getSelectorLoc(I); + if (!checkOccurrence(Decl, Loc, + Loc.getLocWithOffset( + Decl->getSelector().getNameForSlot(I).size()))) return false; } return true; @@ -247,8 +249,10 @@ class NamedDeclFindingASTVisitor // Check all of the selector source ranges. for (unsigned I = 0, E = Expr->getNumSelectorLocs(); I != E; ++I) { - if (!checkOccurrence(Decl, Expr->getSelectorLoc(I), - Decl->getSelector().getNameForSlot(I).size())) + SourceLocation Loc = Expr->getSelectorLoc(I); + if (!checkOccurrence(Decl, Loc, + Loc.getLocWithOffset( + Decl->getSelector().getNameForSlot(I).size()))) return false; } return true; diff --git a/clang/test/Refactor/Rename/ObjCMethod.m b/clang/test/Refactor/Rename/ObjCMethod.m index 7f3655d8979e3..9a255a6c74590 100644 --- a/clang/test/Refactor/Rename/ObjCMethod.m +++ b/clang/test/Refactor/Rename/ObjCMethod.m @@ -133,3 +133,18 @@ - (void)foo { @end // RUN: not clang-refactor-test rename-initiate -at=%s:130:3 -new-name=foo %s -Wno-objc-root-class 2>&1 | FileCheck --check-prefix=CHECK-NORENAME %s // CHECK-NORENAME: could not rename symbol at the given location + +@interface EmptySelectorsRule_Psych + +- (void):(int)_ :(int) m:(int)z; // EMPTY-SELECTOR: rename [[@LINE]]:9 -> [[@LINE]]:9, [[@LINE]]:17 -> [[@LINE]]:17, [[@LINE]]:25 -> [[@LINE]]:25 + +@end + +@implementation EmptySelectorsRule_Psych + +- (void) :(int)_ :(int)m :(int)z { // EMPTY-SELECTOR: rename [[@LINE]]:10 -> [[@LINE]]:10, [[@LINE]]:18 -> [[@LINE]]:18, [[@LINE]]:26 -> [[@LINE]]:26 + [self: 15:0 :3]; // EMPTY-SELECTOR: rename [[@LINE]]:10 -> [[@LINE]]:10, [[@LINE]]:14 -> [[@LINE]]:14, [[@LINE]]:17 -> [[@LINE]]:17 +} +// RUN: clang-refactor-test rename-initiate -at=%s:139:9 -new-name=test:a: %s -Wno-objc-root-class | FileCheck --check-prefix=EMPTY-SELECTOR %s + +@end From f666256c8f40b6e2e4b9c34bf87035fd68e20b8f Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Wed, 12 Jul 2017 14:18:57 +0100 Subject: [PATCH 181/585] rename indexed file: drop redundant beginloc < endloc check Thanks to Ben who pointed this out! rdar://33188656 apple-llvm-split-commit: 13bfdc79c24fb64bd0d6ed86bee47527957c2450 apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/RenameIndexedFile.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp b/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp index 82e9c917107e2..ef88e1fac6b44 100644 --- a/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp +++ b/clang/lib/Tooling/Refactor/RenameIndexedFile.cpp @@ -81,14 +81,6 @@ static MatchKind checkOccurrence(const IndexedOccurrence &Occurrence, if (BeginLoc.isInvalid()) return MatchKind::None; StringRef SymbolNameStart = Symbol.Name[0]; - SourceLocation EndLoc = BeginLoc.getLocWithOffset(std::max( - SymbolNameStart.size(), - size_t(1))); // Take empty Objective-C selector pieces into account. - if (!SM.isBeforeInTranslationUnit(BeginLoc, EndLoc)) { - // Ignore any invalid source ranges. This can occur if the indexed - // location is invalid. - return MatchKind::None; - } // Extract the token at the location. auto DecomposedLoc = SM.getDecomposedLoc(BeginLoc); const llvm::MemoryBuffer *File = SM.getBuffer(DecomposedLoc.first); From a3839bfc32520606e3a2cd82c13e94f7c8ca369a Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Fri, 14 Jul 2017 12:07:28 +0100 Subject: [PATCH 182/585] [refactor] Fix a test by adding a missing selector piece apple-llvm-split-commit: 1d5cc186fcc8564ee27a2c95fe4e494fd58806c3 apple-llvm-split-dir: clang/ --- clang/test/Refactor/Rename/ObjCMethod.m | 2 +- clang/tools/clang-refactor-test/ClangRefactorTest.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/test/Refactor/Rename/ObjCMethod.m b/clang/test/Refactor/Rename/ObjCMethod.m index 9a255a6c74590..d2b1ad354883c 100644 --- a/clang/test/Refactor/Rename/ObjCMethod.m +++ b/clang/test/Refactor/Rename/ObjCMethod.m @@ -145,6 +145,6 @@ @implementation EmptySelectorsRule_Psych - (void) :(int)_ :(int)m :(int)z { // EMPTY-SELECTOR: rename [[@LINE]]:10 -> [[@LINE]]:10, [[@LINE]]:18 -> [[@LINE]]:18, [[@LINE]]:26 -> [[@LINE]]:26 [self: 15:0 :3]; // EMPTY-SELECTOR: rename [[@LINE]]:10 -> [[@LINE]]:10, [[@LINE]]:14 -> [[@LINE]]:14, [[@LINE]]:17 -> [[@LINE]]:17 } -// RUN: clang-refactor-test rename-initiate -at=%s:139:9 -new-name=test:a: %s -Wno-objc-root-class | FileCheck --check-prefix=EMPTY-SELECTOR %s +// RUN: clang-refactor-test rename-initiate -at=%s:139:9 -new-name=test:a:: %s -Wno-objc-root-class | FileCheck --check-prefix=EMPTY-SELECTOR %s @end diff --git a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp index 5f220e40adc2d..524a5f01842bf 100644 --- a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp +++ b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp @@ -372,6 +372,8 @@ occurrenceToString(const CXSymbolOccurrence &Occurrence, bool IsLocal, OS << '"' << Filename << "\" "; bool FirstRange = true; + assert(NewName.size() == Occurrence.NumNamePieces && + "new name doesn't match the number of pieces"); for (unsigned J = 0; J != Occurrence.NumNamePieces; ++J) { if (!FirstRange) // TODO OS << ", "; From d73f9d38f952ecf5ed684ac30cf990f85bf3abfe Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Fri, 14 Jul 2017 12:30:06 +0100 Subject: [PATCH 183/585] [refactor] The assertion from previous commit should use >= Some occurrences might have fewer pieces than new name. apple-llvm-split-commit: d2e5cf7dd61bd3404ffad4c509da4d287c9435f5 apple-llvm-split-dir: clang/ --- clang/tools/clang-refactor-test/ClangRefactorTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp index 524a5f01842bf..a5fd3e487b0eb 100644 --- a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp +++ b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp @@ -372,7 +372,7 @@ occurrenceToString(const CXSymbolOccurrence &Occurrence, bool IsLocal, OS << '"' << Filename << "\" "; bool FirstRange = true; - assert(NewName.size() == Occurrence.NumNamePieces && + assert(NewName.size() >= Occurrence.NumNamePieces && "new name doesn't match the number of pieces"); for (unsigned J = 0; J != Occurrence.NumNamePieces; ++J) { if (!FirstRange) // TODO From d1188b9588329155b181bfc3131d1bf7376b9cc9 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Wed, 12 Jul 2017 17:22:09 +0100 Subject: [PATCH 184/585] [refactor] "Generate Missing Function Definitions" should insert category methods into class implementation declarations even when the category is not available in the TU in which the class is implemented rdar://32875896 apple-llvm-split-commit: c790745221aa5c187a9819d60645b4ee6e8d13a5 apple-llvm-split-dir: clang/ --- .../clang/Tooling/Refactor/IndexerQuery.h | 38 +++++++++-- .../Refactor/ImplementDeclaredMethods.cpp | 68 +++++++++++-------- clang/lib/Tooling/Refactor/IndexerQueries.cpp | 8 ++- .../Refactor/RefactoringContinuations.h | 50 +++++++++++++- .../Inputs/objcClass.m | 2 + .../implement-declared-methods.m | 2 + clang/tools/libclang/CRefactor.cpp | 12 ++-- 7 files changed, 137 insertions(+), 43 deletions(-) diff --git a/clang/include/clang/Tooling/Refactor/IndexerQuery.h b/clang/include/clang/Tooling/Refactor/IndexerQuery.h index a3b3caa723c44..b14e46a8d75a1 100644 --- a/clang/include/clang/Tooling/Refactor/IndexerQuery.h +++ b/clang/include/clang/Tooling/Refactor/IndexerQuery.h @@ -181,6 +181,33 @@ class DeclPredicateNotPredicate : public DeclPredicateNode { } // end namespace detail +enum class QueryBoolResult { + Unknown, + Yes, + No, +}; + +// FIXME: Check that 'T' is either a PersistentDeclRef<> or a Decl *. +template struct Indexed { + T Decl; + // FIXME: Generalize better in the new refactoring engine. + QueryBoolResult IsNotDefined; + + Indexed(T Decl, QueryBoolResult IsNotDefined = QueryBoolResult::Unknown) + : Decl(Decl), IsNotDefined(IsNotDefined) {} + + Indexed(Indexed &&Other) = default; + Indexed &operator=(Indexed &&Other) = default; + Indexed(const Indexed &Other) = default; + Indexed &operator=(const Indexed &Other) = default; + + /// True iff the declaration is not defined in the entire project. + bool isNotDefined() const { + // FIXME: This is hack. Need a better system in the new engine. + return IsNotDefined == QueryBoolResult::Yes; + } +}; + /// Transforms one set of declarations into another using some predicate. class DeclarationsQuery : public IndexerQuery { static const char *BaseUIDString; @@ -189,7 +216,7 @@ class DeclarationsQuery : public IndexerQuery { std::unique_ptr Predicate; protected: - std::vector> Output; + std::vector>> Output; public: DeclarationsQuery(std::vector Input, @@ -203,7 +230,7 @@ class DeclarationsQuery : public IndexerQuery { void invalidateTUSpecificState() override { Input.clear(); } - void setOutput(std::vector> Output) { + void setOutput(std::vector>> Output) { this->Output = Output; } @@ -238,10 +265,11 @@ class ManyToManyDeclarationsQuery final : DeclarationsQuery(std::vector(Input.begin(), Input.end()), std::move(Predicate)) {} - std::vector> getOutput() const { - std::vector> Results; + std::vector>> getOutput() const { + std::vector>> Results; for (const auto &Ref : DeclarationsQuery::Output) - Results.push_back(PersistentDeclRef(Ref.USR)); + Results.push_back(Indexed>( + PersistentDeclRef(Ref.Decl.USR), Ref.IsNotDefined)); return Results; } }; diff --git a/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp b/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp index 72649a8f42f21..c5865e468a20b 100644 --- a/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp +++ b/clang/lib/Tooling/Refactor/ImplementDeclaredMethods.cpp @@ -76,10 +76,9 @@ class ImplementDeclaredCXXMethodsOperation static void addInlineBody(const CXXMethodDecl *MD, const ASTContext &Context, std::vector &Replacements); - static llvm::Expected - runInImplementationAST(ASTContext &Context, const FileID &File, - const CXXRecordDecl *Class, - ArrayRef SelectedMethods); + static llvm::Expected runInImplementationAST( + ASTContext &Context, const FileID &File, const CXXRecordDecl *Class, + ArrayRef> SelectedMethods); }; class ImplementDeclaredObjCMethodsOperation @@ -104,11 +103,11 @@ class ImplementDeclaredObjCMethodsOperation const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) override; - static llvm::Expected - runInImplementationAST(ASTContext &Context, const FileID &File, - const ObjCContainerDecl *Container, - const ObjCInterfaceDecl *Interface, - ArrayRef SelectedMethods); + static llvm::Expected runInImplementationAST( + ASTContext &Context, const FileID &File, + const ObjCContainerDecl *Container, const ObjCInterfaceDecl *Interface, + ArrayRef MethodDeclarations, + ArrayRef> SelectedMethods); }; /// Returns true if the given Objective-C method has an implementation. @@ -224,7 +223,7 @@ static bool containsUsingOf(const NamespaceDecl *ND, llvm::Expected ImplementDeclaredCXXMethodsOperation::runInImplementationAST( ASTContext &Context, const FileID &File, const CXXRecordDecl *Class, - ArrayRef SelectedMethods) { + ArrayRef> SelectedMethods) { if (!Class) return llvm::make_error( "the target class is not defined in the continuation AST unit"); @@ -316,7 +315,8 @@ ImplementDeclaredCXXMethodsOperation::runInImplementationAST( PP.SuppressLifetimeQualifiers = true; PP.SuppressUnwrittenScope = true; OS << "\n"; - for (const CXXMethodDecl *MD : SelectedMethods) { + for (const auto &I : SelectedMethods) { + const CXXMethodDecl *MD = I.Decl; // Check if the method is already defined. if (!MD) continue; @@ -369,24 +369,41 @@ ImplementDeclaredObjCMethodsOperation::perform( ASTContext &Context, const Preprocessor &ThePreprocessor, const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { using namespace indexer; + + // Print the methods before running the continuation because the continuation + // TU might not have these method declarations (e.g. category implemented in + // the class implementation). + PrintingPolicy PP = Context.getPrintingPolicy(); + PP.PolishForDeclaration = true; + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + std::vector MethodDeclarations; + for (const ObjCMethodDecl *MD : SelectedMethods) { + std::string MethodDeclStr; + llvm::raw_string_ostream MethodOS(MethodDeclStr); + MD->print(MethodOS, PP); + MethodDeclarations.push_back(std::move(MethodOS.str())); + } + return continueInExternalASTUnit( fileThatShouldContainImplementationOf(Container), runInImplementationAST, - Container, Interface, + Container, Interface, MethodDeclarations, filter(llvm::makeArrayRef(SelectedMethods), [](const DeclEntity &D) { return !D.isDefined(); })); } static const ObjCImplDecl * getImplementationContainer(const ObjCContainerDecl *Container, - const ObjCInterfaceDecl *Interface) { + const ObjCInterfaceDecl *Interface = nullptr) { if (!Container) - return nullptr; + return Interface ? getImplementationContainer(Interface) : nullptr; if (const auto *ID = dyn_cast(Container)) return ID->getImplementation(); if (const auto *CD = dyn_cast(Container)) { if (const auto *Impl = CD->getImplementation()) return Impl; - return getImplementationContainer(Interface, /*Interface=*/nullptr); + return getImplementationContainer(Interface); } return nullptr; } @@ -395,7 +412,8 @@ llvm::Expected ImplementDeclaredObjCMethodsOperation::runInImplementationAST( ASTContext &Context, const FileID &File, const ObjCContainerDecl *Container, const ObjCInterfaceDecl *Interface, - ArrayRef SelectedMethods) { + ArrayRef MethodDeclarations, + ArrayRef> SelectedMethods) { const ObjCImplDecl *ImplementationContainer = getImplementationContainer(Container, Interface); if (!ImplementationContainer) @@ -408,21 +426,15 @@ ImplementDeclaredObjCMethodsOperation::runInImplementationAST( std::string MethodString; llvm::raw_string_ostream OS(MethodString); - PrintingPolicy PP = Context.getPrintingPolicy(); - PP.PolishForDeclaration = true; - PP.SuppressStrongLifetime = true; - PP.SuppressLifetimeQualifiers = true; - PP.SuppressUnwrittenScope = true; - for (const ObjCMethodDecl *MD : SelectedMethods) { + assert(MethodDeclarations.size() >= SelectedMethods.size() && + "fewer declarations than selected methods?"); + for (const auto &I : llvm::enumerate(SelectedMethods)) { + indexer::Indexed Decl = I.value(); // Skip methods that are already defined. - if (!MD) + if (!Decl.isNotDefined()) continue; - std::string MethodDeclStr; - llvm::raw_string_ostream MethodOS(MethodDeclStr); - MD->print(MethodOS, PP); - - OS << StringRef(MethodOS.str()).drop_back(); // Drop the ';' + OS << StringRef(MethodDeclarations[I.index()]).drop_back(); // Drop the ';' OS << " { \n <#code#>;\n}\n\n"; } SourceLocation InsertionLoc = ImplementationContainer->getLocEnd(); diff --git a/clang/lib/Tooling/Refactor/IndexerQueries.cpp b/clang/lib/Tooling/Refactor/IndexerQueries.cpp index bf4fb6539cfba..b96599caecfdf 100644 --- a/clang/lib/Tooling/Refactor/IndexerQueries.cpp +++ b/clang/lib/Tooling/Refactor/IndexerQueries.cpp @@ -116,13 +116,15 @@ IndexerQuery::loadResultsFromYAML(StringRef Source, for (const auto &PredicateResult : Result.PredicateResults) { if (PredicateResult.Name != ActualPredicate.Name) continue; - std::vector> Output; + std::vector>> Output; for (const auto &ResultTuple : zip(DQ->getInputs(), PredicateResult.IntegerValues)) { const Decl *D = std::get<0>(ResultTuple); int Result = std::get<1>(ResultTuple); - Output.push_back(PersistentDeclRef::create( - (IsNot ? !Result : !!Result) ? D : nullptr)); + bool Value = (IsNot ? !Result : !!Result); + Output.push_back(Indexed>( + PersistentDeclRef::create(Value ? D : nullptr), + Value ? QueryBoolResult::Yes : QueryBoolResult::No)); } DQ->setOutput(std::move(Output)); break; diff --git a/clang/lib/Tooling/Refactor/RefactoringContinuations.h b/clang/lib/Tooling/Refactor/RefactoringContinuations.h index 87b65351c74cc..ec2ee1bcf50ca 100644 --- a/clang/lib/Tooling/Refactor/RefactoringContinuations.h +++ b/clang/lib/Tooling/Refactor/RefactoringContinuations.h @@ -61,8 +61,13 @@ struct StateTraits> template struct StateTraits>> : std::enable_if::value, ValidBase>::type { - using StoredResultType = std::vector; - using PersistentType = std::vector>; + using StoredResultType = std::vector>; + using PersistentType = std::vector>>; +}; + +template <> struct StateTraits> { + using StoredResultType = std::vector; + using PersistentType = std::vector; }; /// Conversion functions convert the TU-specific state to a TU independent @@ -88,7 +93,8 @@ std::vector> convertToPersistentRepresentation( } template -std::vector> convertToPersistentRepresentation( +std::vector>> +convertToPersistentRepresentation( std::unique_ptr> &Query, typename std::enable_if::value>::type * = nullptr) { @@ -96,6 +102,11 @@ std::vector> convertToPersistentRepresentation( return Query->getOutput(); } +inline std::vector +convertToPersistentRepresentation(const std::vector &Values) { + return Values; +} + /// Converts the TU-independent state to the TU-specific state. class PersistentToASTSpecificStateConverter { ASTContext &Context; @@ -154,6 +165,33 @@ class PersistentToASTSpecificStateConverter { return Results; } + template + bool addConvertible( + const std::vector>> &Refs, + typename std::enable_if::value>::type * = + nullptr) { + for (const auto &Ref : Refs) { + if (!Ref.Decl.USR.empty()) + ConvertedDeclRefs[Ref.Decl.USR] = nullptr; + } + return true; + } + + template + std::vector> + convert(const std::vector>> &Refs, + typename std::enable_if::value>::type * = + nullptr) { + std::vector> Results; + Results.reserve(Refs.size()); + // Allow nulls in the produced array, the continuation will have to deal + // with them by itself. + for (const auto &Ref : Refs) + Results.push_back(indexer::Indexed( + dyn_cast_or_null(lookupDecl(Ref.Decl.USR)), Ref.IsNotDefined)); + return Results; + } + bool addConvertible(const PersistentFileID &) { // Do nothing since FileIDs are converted one-by-one. return true; @@ -161,6 +199,12 @@ class PersistentToASTSpecificStateConverter { FileID convert(const PersistentFileID &Ref); + bool addConvertible(const std::vector &) { return true; } + + std::vector convert(const std::vector &Values) { + return Values; + } + /// Converts the added persistent state into TU-specific state using one /// efficient operation. void runCoalescedConversions(); diff --git a/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcClass.m b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcClass.m index 1b31110b051ee..318b7b72832c4 100644 --- a/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcClass.m +++ b/clang/test/Refactor/ImplementDeclaredMethods/Inputs/objcClass.m @@ -11,3 +11,5 @@ - (void)method:(int)x with:(int)y { } @end // CHECK1: "{{.*}}objcClass.m" "- (void)method { \n <#code#>;\n}\n\n+ (void)classMethod { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n- (void)method:(int)x with:(int)y { \n <#code#>;\n}\n\n" [[@LINE-1]]:1 -> [[@LINE-1]]:1 // CHECK2: "{{.*}}objcClass.m" "- (void)method { \n <#code#>;\n}\n\n- (void)implementedMethod { \n <#code#>;\n}\n\n" [[@LINE-2]]:1 + +// CHECK-CAT-NO-IMPL: "{{.*}}objcClass.m" "- (void)thisCategoryMethodShouldBeInTheClassImplementation { \n <#code#>;\n}\n\n" 11:1 -> 11:1 diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m index 5e56fd0ac8f9d..38b52971d6f8a 100644 --- a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m @@ -77,3 +77,5 @@ - (void)thisCategoryMethodShouldBeInTheClassImplementation; @end // RUN: clang-refactor-test perform -action implement-declared-methods -selected=category-no-impl -continuation-file=%s -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK-CAT-NO-IMPL %s +// It should work even in another TU! +// RUN: clang-refactor-test perform -action implement-declared-methods -selected=category-no-impl -continuation-file=%S/Inputs/objcClass.m -query-results=query-all-impl %s | FileCheck --check-prefix=CHECK-CAT-NO-IMPL %S/Inputs/objcClass.m diff --git a/clang/tools/libclang/CRefactor.cpp b/clang/tools/libclang/CRefactor.cpp index 506f8d28f4280..e5b411c153fc6 100644 --- a/clang/tools/libclang/CRefactor.cpp +++ b/clang/tools/libclang/CRefactor.cpp @@ -1024,7 +1024,7 @@ class RefactoringContinuationWrapper { struct QueryWrapper { indexer::IndexerQuery *Query; CXTranslationUnit TU; - std::vector> DeclResults; + std::vector>> DeclResults; unsigned ConsumedResults = 0; QueryWrapper(indexer::IndexerQuery *Query, CXTranslationUnit TU) @@ -1884,13 +1884,17 @@ clang_IndexerQuery_consumeIntResult(CXIndexerQuery Query, unsigned CursorIndex, return CXIndexerQueryAction_None; if (Wrapper->DeclResults.empty()) Wrapper->DeclResults.resize(DQ->getInputs().size(), - PersistentDeclRef::create(nullptr)); + indexer::Indexed>( + PersistentDeclRef::create(nullptr))); // Filter the declarations! bool IsNot = false; if (isa(DQ->getPredicateNode())) IsNot = true; - Wrapper->DeclResults[CursorIndex] = PersistentDeclRef::create( - (IsNot ? !Value : !!Value) ? DQ->getInputs()[CursorIndex] : nullptr); + bool Result = IsNot ? !Value : !!Value; + Wrapper->DeclResults[CursorIndex] = indexer::Indexed>( + PersistentDeclRef::create(Result ? DQ->getInputs()[CursorIndex] + : nullptr), + Result ? indexer::QueryBoolResult::Yes : indexer::QueryBoolResult::No); Wrapper->ConsumedResults++; if (Wrapper->ConsumedResults == Wrapper->DeclResults.size()) { // We've received all the results, pass them back to the query. From 61abc6bc528e67a87c4044a5646e05cc9da955ac Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Fri, 14 Jul 2017 15:19:56 +0100 Subject: [PATCH 185/585] The "does not conform to protocol" -Wprotocol warning should take method availability into account This also ensures that the "Add missing requirements" refactoring action doesn't add stubs for methods that are unavailable or not yet introduced. rdar://32549953 apple-llvm-split-commit: 4d7cc27fda0cc3c9ff5eb74fbcb1f6cdfe6c422e apple-llvm-split-dir: clang/ --- clang/lib/Edit/FillInMissingProtocolStubs.cpp | 5 +++ clang/lib/Sema/SemaDeclObjC.cpp | 41 +++++++++++-------- .../fixit-fill-in-protocol-requirements.m | 34 ++++++++++++++- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/clang/lib/Edit/FillInMissingProtocolStubs.cpp b/clang/lib/Edit/FillInMissingProtocolStubs.cpp index 3173a345e56fa..64843e508b380 100644 --- a/clang/lib/Edit/FillInMissingProtocolStubs.cpp +++ b/clang/lib/Edit/FillInMissingProtocolStubs.cpp @@ -139,6 +139,11 @@ class MethodSet { for (const ObjCMethodDecl *M : P->methods()) { if (M->isImplicit()) continue; + AvailabilityResult Availability = M->getAvailability(); + // Methods that are unavailable or not yet introduced are not considered + // to be required. + if (Availability == AR_NotYetIntroduced || Availability == AR_Unavailable) + continue; auto &Map = M->isInstanceMethod() ? InstanceMethods : ClassMethods; Map.insert(std::make_pair(M->getSelector(), MethodInfo(M, P, Priority))); } diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 299509bdb78b8..b13238112cbb3 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -2149,23 +2149,28 @@ void Sema::CheckImplementationIvars(ObjCImplementationDecl *ImpDecl, Diag(IVI->getLocation(), diag::err_inconsistent_ivar_count); } -static void WarnUndefinedMethod(Sema &S, SourceLocation ImpLoc, - ObjCMethodDecl *method, - bool &IncompleteImpl, - unsigned DiagID, - NamedDecl *NeededFor = nullptr) { +static bool shouldWarnUndefinedMethod(const ObjCMethodDecl *M) { // No point warning no definition of method which is 'unavailable'. - switch (method->getAvailability()) { + switch (M->getAvailability()) { case AR_Available: case AR_Deprecated: - break; + return true; - // Don't warn about unavailable or not-yet-introduced methods. + // Don't warn about unavailable or not-yet-introduced methods. case AR_NotYetIntroduced: case AR_Unavailable: - return; + return false; } - + llvm_unreachable("Invalid availability"); +} + +static void WarnUndefinedMethod(Sema &S, SourceLocation ImpLoc, + ObjCMethodDecl *method, bool &IncompleteImpl, + unsigned DiagID, + NamedDecl *NeededFor = nullptr) { + if (!shouldWarnUndefinedMethod(method)) + return; + // FIXME: For now ignore 'IncompleteImpl'. // Previously we grouped all unimplemented methods under a single // warning, but some users strongly voiced that they would prefer @@ -2721,11 +2726,13 @@ static void CheckProtocolMethodDefs( continue; unsigned DIAG = diag::warn_unimplemented_protocol_method; if (!S.Diags.isIgnored(DIAG, ImpLoc)) { - if (MissingRequirements) - HasMissingRequirements = true; - else + if (MissingRequirements) { + if (!HasMissingRequirements) + HasMissingRequirements = shouldWarnUndefinedMethod(method); + } else { WarnUndefinedMethod(S, ImpLoc, method, IncompleteImpl, DIAG, PDecl); + } } } } @@ -2747,10 +2754,12 @@ static void CheckProtocolMethodDefs( unsigned DIAG = diag::warn_unimplemented_protocol_method; if (!S.Diags.isIgnored(DIAG, ImpLoc)) { - if (MissingRequirements) - HasMissingRequirements = true; - else + if (MissingRequirements) { + if (!HasMissingRequirements) + HasMissingRequirements = shouldWarnUndefinedMethod(method); + } else { WarnUndefinedMethod(S, ImpLoc, method, IncompleteImpl, DIAG, PDecl); + } } } } diff --git a/clang/test/FixIt/fixit-fill-in-protocol-requirements.m b/clang/test/FixIt/fixit-fill-in-protocol-requirements.m index c86b8c28672c8..642a70cf34d7e 100644 --- a/clang/test/FixIt/fixit-fill-in-protocol-requirements.m +++ b/clang/test/FixIt/fixit-fill-in-protocol-requirements.m @@ -1,5 +1,6 @@ -// RUN: %clang_cc1 -verify -Wno-objc-root-class -serialize-diagnostic-file /dev/null %s -// RUN: %clang_cc1 -fdiagnostics-parseable-fixits -serialize-diagnostic-file /dev/null %s 2>&1 | FileCheck %s +// RUN: %clang_cc1 -triple=x86_64-apple-macos10.10 -verify -Wno-objc-root-class -serialize-diagnostic-file /dev/null %s +// RUN: %clang_cc1 -triple=x86_64-apple-macos10.10 -fdiagnostics-parseable-fixits -serialize-diagnostic-file /dev/null %s 2>&1 | FileCheck %s +// RUN: %clang_cc1 -triple=x86_64-apple-macos10.12 -fdiagnostics-parseable-fixits -serialize-diagnostic-file /dev/null %s 2>&1 | FileCheck --check-prefix=AVAILABLE %s @protocol P1 @@ -52,3 +53,32 @@ @interface FourProtocols // expected-warning {{class 'ThreeProtocol @end @implementation FourProtocols @end + +// Unavailable methods +@protocol TakeAvailabilityIntoAccount + +- (void)unavailableMethod __attribute__((availability(macos,unavailable))); ++ (void)notYetAvailableMethod __attribute__((availability(macos,introduced=10.11))); +- (void)availableMethod; +- (void)deprecatedMethod __attribute__((availability(macos,introduced=10.0, deprecated=10.6))); + +@end + +@interface ImplementsAllAvailable +@end + +@implementation ImplementsAllAvailable // No warning! + +- (void)availableMethod { } +- (void)deprecatedMethod { } + +@end + +@interface FixitJustAvailable +@end + +@implementation FixitJustAvailable // expected-warning {{class 'FixitJustAvailable' does not conform to protocol 'TakeAvailabilityIntoAccount'}} expected-note {{add stubs for missing protocol requirements}} + +@end +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:1-[[@LINE-1]]:1}:"- (void)availableMethod { \n <#code#>\n}\n\n- (void)deprecatedMethod { \n <#code#>\n}\n\n" +// AVAILABLE: fix-it:{{.*}}:{[[@LINE-2]]:1-[[@LINE-2]]:1}:"- (void)availableMethod { \n <#code#>\n}\n\n- (void)deprecatedMethod { \n <#code#>\n}\n\n+ (void)notYetAvailableMethod { \n <#code#>\n}\n\n" From a784fa3dcc77bb2aa00d277c219c7d656da113d0 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 17 Jul 2017 16:58:59 +0100 Subject: [PATCH 186/585] [refactor] Extract: use substitued types for ObjC property expressions rdar://33350790 apple-llvm-split-commit: 29428eeadd217195045dde123b6f540fd8a0363f apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/TypeUtils.cpp | 15 +++++-- .../return-objc-generic-argument-type.m | 40 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 clang/test/Refactor/Extract/return-objc-generic-argument-type.m diff --git a/clang/lib/Tooling/Refactor/TypeUtils.cpp b/clang/lib/Tooling/Refactor/TypeUtils.cpp index 3a36aec1d5ba4..c07136bd709ad 100644 --- a/clang/lib/Tooling/Refactor/TypeUtils.cpp +++ b/clang/lib/Tooling/Refactor/TypeUtils.cpp @@ -162,10 +162,17 @@ QualType findExpressionLexicalType(const Decl *FunctionLikeParentDecl, // Get the correct property type. if (const auto *PRE = dyn_cast(E)) { if (PRE->isMessagingGetter()) { - if (PRE->isExplicitProperty()) - return PRE->getExplicitProperty()->getType(); - if (const ObjCMethodDecl *M = PRE->getImplicitPropertyGetter()) - return M->getReturnType(); + if (PRE->isExplicitProperty()) { + QualType ReceiverType = PRE->getReceiverType(Ctx); + return PRE->getExplicitProperty()->getUsageType(ReceiverType); + } + if (const ObjCMethodDecl *M = PRE->getImplicitPropertyGetter()) { + if (!PRE->isObjectReceiver()) + return M->getSendResultType(PRE->getReceiverType(Ctx)); + const Expr *Base = PRE->getBase(); + return M->getSendResultType(findExpressionLexicalType( + FunctionLikeParentDecl, Base, Base->getType(), Policy, Ctx)); + } } } diff --git a/clang/test/Refactor/Extract/return-objc-generic-argument-type.m b/clang/test/Refactor/Extract/return-objc-generic-argument-type.m new file mode 100644 index 0000000000000..42d97705c4b22 --- /dev/null +++ b/clang/test/Refactor/Extract/return-objc-generic-argument-type.m @@ -0,0 +1,40 @@ +// RUN: clang-refactor-test perform -action extract -selected=prop -selected=imp-prop -selected=class-prop -selected=class-prop2 -selected=class-method %s | FileCheck %s + +@interface NSObject +@end + +@interface Array : NSObject + +@property Element prop; + +- (Element)get; + +@property (class) Array *classProp; + ++ (Element *)classGet; + +@end + +void foo(Array *objects) { +// prop-begin: +1:3 + objects.prop; +// prop-end: -1:15 +// CHECK: "static NSObject * extracted(Array *objects) {\nreturn objects.prop;\n}\n\n" +// imp-prop-begin: +1:3 + objects.get; +// imp-prop-end: -1:14 +// CHECK: "static NSObject * extracted(Array *objects) {\nreturn objects.get;\n}\n\n" +// class-prop-begin: +1:3 + Array.classProp; +// class-prop-end: -1:30 +// CHECK: "static Array * extracted() {\nreturn Array.classProp;\n}\n\n" + typedef Array ObjectArray; +// class-prop2-begin: +1:3 + [ObjectArray classProp]; +// class-prop2-end: -1:26 +// CHECK: "static Array * extracted() {\nreturn [ObjectArray classProp];\n}\n\n" +// class-method-begin: +1:3 + [ObjectArray classGet]; +// class-method-end: -1:25 +// CHECK: "static NSObject ** extracted() {\nreturn [ObjectArray classGet];\n}\n\n" +} From 690870a316c1b7c9c029a9e81a3698c5281750bf Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 18 Jul 2017 18:14:52 +0100 Subject: [PATCH 187/585] [refactor] Extract expression should be prohibited for calls that return void rdar://33378777 apple-llvm-split-commit: a4c1b7aeb931fe4b1a62170ed9ab049713738152 apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/StmtUtils.cpp | 2 +- .../test/Refactor/Extract/extract-expression-into-var.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/clang/lib/Tooling/Refactor/StmtUtils.cpp b/clang/lib/Tooling/Refactor/StmtUtils.cpp index ed8ae30543a48..d5644b29c1b33 100644 --- a/clang/lib/Tooling/Refactor/StmtUtils.cpp +++ b/clang/lib/Tooling/Refactor/StmtUtils.cpp @@ -68,5 +68,5 @@ bool clang::tooling::isLexicalExpression(const Stmt *S, const Stmt *Parent) { // of an expression. if (isAssignmentOperator(S) && (!Parent || !isa(Parent))) return false; - return true; + return !cast(S)->getType()->isVoidType(); } diff --git a/clang/test/Refactor/Extract/extract-expression-into-var.cpp b/clang/test/Refactor/Extract/extract-expression-into-var.cpp index f1ae8f5ae41f1..9fd4f7a54f777 100644 --- a/clang/test/Refactor/Extract/extract-expression-into-var.cpp +++ b/clang/test/Refactor/Extract/extract-expression-into-var.cpp @@ -37,3 +37,11 @@ void dontExtractStatement(int x, int y) { // RUN: not clang-refactor-test perform -action extract-expression -selected=stmt1 -selected=stmt2 %s -std=c++11 2>&1 | FileCheck --check-prefix=CHECK-FAIL %s // CHECK-FAIL: Failed to initiate the refactoring action! + +void dontExtractVoidCall() { + // void-call-begin: +1:3 + dontExtractVoidCall(); + // void-call-end: -1:24 +} + +// RUN: not clang-refactor-test perform -action extract-expression -selected=void-call %s -std=c++11 2>&1 | FileCheck --check-prefix=CHECK-FAIL %s From 4ec65cfdc36764c83ca8bf14290ebe2f0634ce21 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Wed, 19 Jul 2017 17:26:11 +0100 Subject: [PATCH 188/585] refactor: Add a continuation verification function to report errors for the "Generate Missing Function Definitions" action that can be propagated back to SK rdar://33403708 apple-llvm-split-commit: 414251c9711ba8fa6781f2f899d855a9579b91d5 apple-llvm-split-dir: clang/ --- clang/include/clang-c/Refactor.h | 12 +++++++ .../clang/Basic/DiagnosticRefactoringKinds.td | 12 +++++++ .../clang/Tooling/Refactor/IndexerQuery.h | 8 +++++ clang/lib/Tooling/Refactor/IndexerQueries.cpp | 35 +++++++++++++++++++ .../implement-declared-methods.cpp | 4 +++ .../implement-declared-methods.m | 4 +++ .../clang-refactor-test/ClangRefactorTest.cpp | 13 +++++++ clang/tools/libclang/CRefactor.cpp | 34 +++++++++++++++--- clang/tools/libclang/libclang.exports | 1 + 9 files changed, 119 insertions(+), 4 deletions(-) diff --git a/clang/include/clang-c/Refactor.h b/clang/include/clang-c/Refactor.h index 63b01a4175450..834584285ddc8 100644 --- a/clang/include/clang-c/Refactor.h +++ b/clang/include/clang-c/Refactor.h @@ -1169,6 +1169,18 @@ CINDEX_LINKAGE CXIndexerQuery clang_RefactoringContinuation_getIndexerQuery( CXRefactoringContinuation Continuation, unsigned Index); +/** + * \brief Verify that the all of the indexer queries are satisfied by the + * continuation. + * + * \returns Null if all of the queries are satisfied an no errors have been + * reported, or a set of diagnostics that describes why the continuation should + * not be run. + */ +CINDEX_LINKAGE +CXDiagnosticSet clang_RefactoringContinuation_verifyBeforeFinalizing( + CXRefactoringContinuation Continuation); + /** * \brief Terminate the connection between the initiation TU and the refactoring * continuation. diff --git a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td index 6dfd29169923a..41113c25516ce 100644 --- a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td +++ b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td @@ -24,4 +24,16 @@ def err_rename_external_source_symbol : Error<"%0 is declared in a %1 file; " "rename can be initiated in a %1 file only">; } +let CategoryName = "Refactoring Continuation Issue" in { + +def err_ref_continuation_missing_implementation : Error< + "no %select{implementation file|@implementation declaration}0 for the " + "selected %select{declaration|@interface}0 %1; please add one and run the " + "refactoring action again">; + +def err_implement_declared_methods_all_implemented : Error< + "the selected methods are already implemented">; + +} + } // end of Refactoring diagnostics diff --git a/clang/include/clang/Tooling/Refactor/IndexerQuery.h b/clang/include/clang/Tooling/Refactor/IndexerQuery.h index b14e46a8d75a1..48728930b182e 100644 --- a/clang/include/clang/Tooling/Refactor/IndexerQuery.h +++ b/clang/include/clang/Tooling/Refactor/IndexerQuery.h @@ -36,6 +36,10 @@ class IndexerQuery { virtual void invalidateTUSpecificState() = 0; + /// Checks if this query was satisfied. Returns true if it wasn't and reports + /// appropriate errors. + virtual bool verify(ASTContext &) { return false; } + // Mainly used for testing. static llvm::Error loadResultsFromYAML(StringRef Source, ArrayRef Queries); @@ -82,6 +86,8 @@ class ASTUnitForImplementationOfDeclarationQuery final void setResult(PersistentFileID File) { Result = std::move(File); } + bool verify(ASTContext &Context) override; + const PersistentFileID &getResult() const { return Result; } static bool classof(const IndexerQuery *D) { @@ -230,6 +236,8 @@ class DeclarationsQuery : public IndexerQuery { void invalidateTUSpecificState() override { Input.clear(); } + bool verify(ASTContext &Context) override; + void setOutput(std::vector>> Output) { this->Output = Output; } diff --git a/clang/lib/Tooling/Refactor/IndexerQueries.cpp b/clang/lib/Tooling/Refactor/IndexerQueries.cpp index b96599caecfdf..f2392e419c8e5 100644 --- a/clang/lib/Tooling/Refactor/IndexerQueries.cpp +++ b/clang/lib/Tooling/Refactor/IndexerQueries.cpp @@ -7,6 +7,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Tooling/Core/RefactoringDiagnostic.h" #include "clang/Tooling/Refactor/IndexerQuery.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" @@ -44,6 +45,40 @@ clang::tooling::indexer::fileThatShouldContainImplementationOf(const Decl *D) { return llvm::make_unique(D); } +bool ASTUnitForImplementationOfDeclarationQuery::verify(ASTContext &Context) { + if (!D) { + assert(false && "Query should be verified before persisting"); + return false; + } + // Check if we've got the filename. + if (!Result.Filename.empty()) + return false; + Context.getDiagnostics().Report( + D->getLocation(), diag::err_ref_continuation_missing_implementation) + << isa(D) << cast(D); + return true; +} + +bool DeclarationsQuery::verify(ASTContext &Context) { + if (Input.empty()) { + assert(false && "Query should be verified before persisting"); + return false; + } + if (!Output.empty()) { + // At least one output declaration must be valid. + for (const auto &Ref : Output) { + if (!Ref.Decl.USR.empty()) + return false; + } + } + // FIXME: This is too specific, the new refactoring engine at llvm.org should + // generalize this. + Context.getDiagnostics().Report( + Input[0]->getLocation(), + diag::err_implement_declared_methods_all_implemented); + return true; +} + namespace { struct QueryPredicateNode { diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.cpp b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.cpp index 82ea02187aeb6..3d8164f9eb2fe 100644 --- a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.cpp +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.cpp @@ -85,6 +85,10 @@ struct } // query-mix-impl-header: [ { name: ast.producer.query, filenameResult: "%S/classInHeader.h" } , { name: decl.query , predicateResults: [{name: decl.isDefined, intValues: [0, 1, 0, 1] }] }] // RUN: clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-mix-impl-header %s | FileCheck %S/Inputs/classInHeader.h +// query-no-impl: [ { name: ast.producer.query, filenameResult: "%s" } , { name: decl.query , predicateResults: [{name: decl.isDefined, intValues: [1, 1, 1, 1] }] }] +// RUN: not clang-refactor-test perform -action implement-declared-methods -selected=class-in-header -continuation-file=%S/Inputs/classInHeader.cpp -query-results=query-no-impl %s 2>&1 | FileCheck --check-prefix=ALL-IMPLEMENTED-ERROR %s +// ALL-IMPLEMENTED-ERROR: error: continuation failed: the selected methods are already implemented + // Ensure that the methods which are placed right after the record are placed // after the outermost record: namespace ns { diff --git a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m index 38b52971d6f8a..834e20f62bb69 100644 --- a/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m +++ b/clang/test/Refactor/ImplementDeclaredMethods/implement-declared-methods.m @@ -47,6 +47,10 @@ - (void)someOtherMethod { } // RUN: not clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-all-impl %s -DNO_IMPL 2>&1 | FileCheck --check-prefix=CHECK-EMPTY-ERR %s // CHECK-EMPTY-ERR: failed to perform the refactoring continuation (the target @interface is not implemented in the continuation AST unit)! +// query-no-file-all-impl: [ { name: ast.producer.query, filenameResult: "" } , { name: decl.query , predicateResults: [{name: decl.isDefined, intValues: [0, 0, 0, 0] }] }] +// RUN: not clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%s -query-results=query-no-file-all-impl %s -DNO_IMPL 2>&1 | FileCheck --check-prefix=CHECK-NO-IMPLEMENTATION-ERROR %s +// CHECK-NO-IMPLEMENTATION-ERROR: error: continuation failed: no @implementation declaration for the selected @interface 'MyClass'; please add one and run the refactoring action again + // RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%S/Inputs/objcClass.m -query-results=query-all-impl %s -DNO_IMPL | FileCheck --check-prefix=CHECK1 %S/Inputs/objcClass.m // RUN: clang-refactor-test perform -action implement-declared-methods -selected=all-methods -continuation-file=%S/Inputs/objcClass.m -query-results=query-mix-impl %s -DNO_IMPL -DMIX_IMPL | FileCheck --check-prefix=CHECK2 %S/Inputs/objcClass.m diff --git a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp index a5fd3e487b0eb..5de26f96fcf7c 100644 --- a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp +++ b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp @@ -1109,6 +1109,19 @@ bool performOperation(CXRefactoringAction Action, ArrayRef Args, /*FileSubstitution=*/opts::initiateAndPerform::ContinuationFile); clang_RefactoringContinuation_loadSerializedIndexerQueryResults( Continuation, /*Source=*/QueryResults.c_str()); + CXDiagnosticSet Diags = + clang_RefactoringContinuation_verifyBeforeFinalizing(Continuation); + if (Diags) { + llvm::errs() << "error: continuation failed: "; + for (unsigned I = 0, E = clang_getNumDiagnosticsInSet(Diags); I != E; ++I) { + CXDiagnostic Diag = clang_getDiagnosticInSet(Diags, I); + CXString Spelling = clang_getDiagnosticSpelling(Diag); + errs() << clang_getCString(Spelling) << "\n"; + clang_disposeString(Spelling); + } + clang_RefactoringContinuation_dispose(Continuation); + return true; + } clang_RefactoringContinuation_finalizeEvaluationInInitationTU(Continuation); // Load the continuation TU. CXTranslationUnit ContinuationTU; diff --git a/clang/tools/libclang/CRefactor.cpp b/clang/tools/libclang/CRefactor.cpp index e5b411c153fc6..45f21ba134206 100644 --- a/clang/tools/libclang/CRefactor.cpp +++ b/clang/tools/libclang/CRefactor.cpp @@ -1051,6 +1051,7 @@ class RefactoringDiagnosticConsumer : public DiagnosticConsumer { DiagnosticConsumer *PreviousClient; std::unique_ptr PreviousClientPtr; llvm::SmallVector RenameDiagnostics; + llvm::SmallVector ContinuationDiagnostics; public: RefactoringDiagnosticConsumer(ASTContext &Context) : Context(Context) { @@ -1069,17 +1070,24 @@ class RefactoringDiagnosticConsumer : public DiagnosticConsumer { void HandleDiagnostic(DiagnosticsEngine::Level Level, const Diagnostic &Info) override { - if (DiagnosticIDs::getCategoryNumberForDiag(Info.getID()) == - diag::DiagCat_Rename_Issue) + unsigned Cat = DiagnosticIDs::getCategoryNumberForDiag(Info.getID()); + if (Cat == diag::DiagCat_Rename_Issue) RenameDiagnostics.push_back(StoredDiagnostic(Level, Info)); + else if (Cat == diag::DiagCat_Refactoring_Continuation_Issue) + ContinuationDiagnostics.push_back(StoredDiagnostic(Level, Info)); else assert(false && "Unhandled refactoring category"); } CXDiagnosticSetImpl *createDiags() const { - if (RenameDiagnostics.empty()) + if (RenameDiagnostics.empty() && ContinuationDiagnostics.empty()) return nullptr; - return cxdiag::createStoredDiags(RenameDiagnostics, Context.getLangOpts()); + llvm::SmallVector AllDiagnostics; + for (const auto &D : RenameDiagnostics) + AllDiagnostics.push_back(D); + for (const auto &D : ContinuationDiagnostics) + AllDiagnostics.push_back(D); + return cxdiag::createStoredDiags(AllDiagnostics, Context.getLangOpts()); } CXRefactoringActionSetWithDiagnostics createActionSet() const { @@ -1771,6 +1779,24 @@ CXIndexerQuery clang_RefactoringContinuation_getIndexerQuery( return &Wrapper->Queries[Index]; } +CXDiagnosticSet clang_RefactoringContinuation_verifyBeforeFinalizing( + CXRefactoringContinuation Continuation) { + if (!Continuation) + return nullptr; + auto *Wrapper = static_cast(Continuation); + CXTranslationUnit TU = Wrapper->Queries[0].TU; + ASTUnit *CXXUnit = cxtu::getASTUnit(TU); + if (!CXXUnit) + return nullptr; + ASTContext &Context = CXXUnit->getASTContext(); + RefactoringDiagnosticConsumer DiagConsumer(Context); + for (const auto &Query : Wrapper->Queries) { + if (Query.Query->verify(Context)) + break; + } + return DiagConsumer.createDiags(); +} + void clang_RefactoringContinuation_finalizeEvaluationInInitationTU( CXRefactoringContinuation Continuation) { if (!Continuation) diff --git a/clang/tools/libclang/libclang.exports b/clang/tools/libclang/libclang.exports index f8b9f09bc2908..e9a9b580dd50b 100644 --- a/clang/tools/libclang/libclang.exports +++ b/clang/tools/libclang/libclang.exports @@ -398,6 +398,7 @@ clang_RefactoringResult_dispose clang_RefactoringContinuation_loadSerializedIndexerQueryResults clang_RefactoringContinuation_getNumIndexerQueries clang_RefactoringContinuation_getIndexerQuery +clang_RefactoringContinuation_verifyBeforeFinalizing clang_RefactoringContinuation_finalizeEvaluationInInitationTU clang_RefactoringContinuation_continueOperationInTU clang_RefactoringContinuation_dispose From ec75656258cb4f768c6395a48571317000cc17ac Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Wed, 19 Jul 2017 18:07:40 +0100 Subject: [PATCH 189/585] [refactor] Record extracted symbol information for the "Extract Repeated Expression" action rdar://33404892 apple-llvm-split-commit: 6ba2ae5e4543ab6a5813d6d2bd890154041e9978 apple-llvm-split-dir: clang/ --- .../ExtractRepeatedExpressionIntoVariable.cpp | 25 ++++++++++++++----- .../extract-repeated-expr-perform.cpp | 10 ++++---- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp index dc90d9ea93c1e..43718c37eabf0 100644 --- a/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp +++ b/clang/lib/Tooling/Refactor/ExtractRepeatedExpressionIntoVariable.cpp @@ -244,7 +244,8 @@ llvm::Expected ExtractRepeatedExpressionIntoVariableOperation::perform( ASTContext &Context, const Preprocessor &ThePreprocessor, const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { - std::vector Replacements; + RefactoringResult Result(std::vector{}); + std::vector &Replacements = Result.Replacements; const SourceManager &SM = Context.getSourceManager(); SourceLocation InsertionLoc = @@ -255,6 +256,10 @@ ExtractRepeatedExpressionIntoVariableOperation::perform( "no appropriate insertion location found"); StringRef Name = nameForExtractedVariable(E); + Result.AssociatedSymbols.push_back( + llvm::make_unique(SymbolName(Name))); + RefactoringResultAssociatedSymbol *CreatedSymbol = + Result.AssociatedSymbols.back().get(); // Create the variable that will hold the value of the duplicate expression. std::string VariableDeclarationString; @@ -265,18 +270,26 @@ ExtractRepeatedExpressionIntoVariableOperation::perform( PP.SuppressLifetimeQualifiers = true; PP.SuppressUnwrittenScope = true; T.print(OS, PP, /*PlaceHolder*/ Name); + // FIXME: We should hook into the TypePrinter when moving over to llvm.org + // instead and get the offset from it. + unsigned NameOffset = StringRef(OS.str()).find(Name); OS << " = "; E->printPretty(OS, /*Helper=*/nullptr, Context.getPrintingPolicy()); OS << ";\n"; - Replacements.emplace_back(SourceRange(InsertionLoc, InsertionLoc), OS.str()); + Replacements.push_back(RefactoringReplacement( + SourceRange(InsertionLoc, InsertionLoc), OS.str(), CreatedSymbol, + RefactoringReplacement::AssociatedSymbolLocation( + llvm::makeArrayRef(NameOffset), /*IsDeclaration=*/true))); // Replace the duplicates with a reference to the variable. - for (const Expr *E : DuplicateExpressions) - Replacements.emplace_back( + for (const Expr *E : DuplicateExpressions) { + Replacements.push_back(RefactoringReplacement( SourceRange(SM.getSpellingLoc(E->getLocStart()), getPreciseTokenLocEnd(SM.getSpellingLoc(E->getLocEnd()), SM, Context.getLangOpts())), - Name); + Name, CreatedSymbol, + /*NameOffset=*/llvm::makeArrayRef(unsigned(0)))); + } - return std::move(Replacements); + return std::move(Result); } diff --git a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.cpp b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.cpp index 4d8c40099801c..9329502b4c232 100644 --- a/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.cpp +++ b/clang/test/Refactor/ExtractRepeatedExpression/extract-repeated-expr-perform.cpp @@ -45,12 +45,12 @@ void takesClass2(AWrapper &ref) { ref.object(x).constMethod(); ref.object(x).method(); } -// CHECK3: "AClass &object = ref.object(x);\n" [[@LINE-4]]:3 -> [[@LINE-4]]:3 -// CHECK3-NEXT: "object" [[@LINE-4]]:5 -> [[@LINE-4]]:18 -// CHECK3-NEXT: "object" [[@LINE-4]]:3 -> [[@LINE-4]]:16 +// CHECK3: "AClass &object = ref.object(x);\n" [[@LINE-4]]:3 -> [[@LINE-4]]:3 [Symbol extracted-decl 0 1:9 -> 1:15] +// CHECK3-NEXT: "object" [[@LINE-4]]:5 -> [[@LINE-4]]:18 [Symbol extracted-decl-ref 0 1:1 -> 1:7] +// CHECK3-NEXT: "object" [[@LINE-4]]:3 -> [[@LINE-4]]:16 [Symbol extracted-decl-ref 0 1:1 -> 1:7] -// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:45:5 %s | FileCheck --check-prefix=CHECK3 %s -// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:46:3 %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:45:5 -emit-associated %s | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-refactor-test perform -action extract-repeated-expr-into-var -at=%s:46:3 -emit-associated %s | FileCheck --check-prefix=CHECK3 %s void takesClass4(AWrapper &ref) { int x = 0; From 5cd0ae5e49ece217150207c0da591447cbc49cbe Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Fri, 21 Jul 2017 14:33:18 +0100 Subject: [PATCH 190/585] Dispose of the diagnostics in clang-refactor-test 414251c9711ba8fa6781f2f899d855a9579b91d5 forgot that. apple-llvm-split-commit: 6b59623ebe1dd854c9828b43ad3bb03d03e76bf9 apple-llvm-split-dir: clang/ --- clang/tools/clang-refactor-test/ClangRefactorTest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp index 5de26f96fcf7c..424d130d14f86 100644 --- a/clang/tools/clang-refactor-test/ClangRefactorTest.cpp +++ b/clang/tools/clang-refactor-test/ClangRefactorTest.cpp @@ -1118,8 +1118,10 @@ bool performOperation(CXRefactoringAction Action, ArrayRef Args, CXString Spelling = clang_getDiagnosticSpelling(Diag); errs() << clang_getCString(Spelling) << "\n"; clang_disposeString(Spelling); + clang_disposeDiagnostic(Diag); } clang_RefactoringContinuation_dispose(Continuation); + clang_disposeDiagnosticSet(Diags); return true; } clang_RefactoringContinuation_finalizeEvaluationInInitationTU(Continuation); From eb3dbbb7b598e430adb4c8b73dc6f4bd417a35c2 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 24 Jul 2017 10:03:46 -0700 Subject: [PATCH 191/585] Mark the swift_wrapper attribute as inheritable (#111) (as it should have been all along) This prevents a crash in the case where someone happened to redeclare the typedef later, and then different Swift compilation contexts treated the typedef differently. rdar://problem/31935341 apple-llvm-split-commit: 0ad3d0a63b8524e644874825ff78bb8f054d43d3 apple-llvm-split-dir: clang/ --- clang/include/clang/Basic/Attr.td | 2 +- clang/test/SemaObjC/attr-swift_newtype.c | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 55abe0757aa5f..59fc73b0682dc 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1711,7 +1711,7 @@ def SwiftName : InheritableAttr { let Documentation = [Undocumented]; } -def SwiftNewtype : Attr { +def SwiftNewtype : InheritableAttr { let Spellings = [GNU<"swift_newtype">, GNU<"swift_wrapper">]; let Subjects = SubjectList<[TypedefName], ErrorDiag, "ExpectedType">; let Args = [EnumArgument<"NewtypeKind", "NewtypeKind", diff --git a/clang/test/SemaObjC/attr-swift_newtype.c b/clang/test/SemaObjC/attr-swift_newtype.c index 61e4d89642a7c..62a20d1cf27e0 100644 --- a/clang/test/SemaObjC/attr-swift_newtype.c +++ b/clang/test/SemaObjC/attr-swift_newtype.c @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -verify -fsyntax-only %s +// RUN: not %clang_cc1 -ast-dump %s | FileCheck %s typedef int T1 __attribute__((swift_newtype(struct))); typedef int T2 __attribute__((swift_newtype(enum))); @@ -6,6 +7,17 @@ typedef int T2 __attribute__((swift_newtype(enum))); typedef int T3 __attribute__((swift_wrapper(struct))); typedef int T4 __attribute__((swift_wrapper(enum))); +typedef int T5; +typedef int T5 __attribute__((swift_wrapper(struct))); +typedef int T5; +// CHECK-LABEL: TypedefDecl {{.+}} T5 'int' +// CHECK-NEXT: BuiltinType {{.+}} 'int' +// CHECK-NEXT: TypedefDecl {{.+}} T5 'int' +// CHECK-NEXT: BuiltinType {{.+}} 'int' +// CHECK-NEXT: SwiftNewtypeAttr {{.+}} NK_Struct +// CHECK-NEXT: TypedefDecl {{.+}} T5 'int' +// CHECK-NEXT: BuiltinType {{.+}} 'int' +// CHECK-NEXT: SwiftNewtypeAttr {{.+}} NK_Struct typedef int Bad1 __attribute__((swift_newtype(bad))); // expected-warning{{'swift_newtype' attribute argument not supported: 'bad'}} typedef int Bad2 __attribute__((swift_newtype())); // expected-error{{argument required after attribute}} From 4e8e12043bf971bcd7883580bdcc4bf6570c1456 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 24 Jul 2017 14:55:03 +0100 Subject: [PATCH 192/585] [refactor][extract] Look through PseudoObjectExprs when looking for the selected statement in a CompoundStatement rdar://33348439 apple-llvm-split-commit: d5107e32179107ca8c669478520d17f047e50bb5 apple-llvm-split-dir: clang/ --- clang/lib/Tooling/Refactor/Extract.cpp | 21 +++++++++++++++++-- .../Refactor/Extract/extract-objc-property.m | 13 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/clang/lib/Tooling/Refactor/Extract.cpp b/clang/lib/Tooling/Refactor/Extract.cpp index 624b18dc772a3..57a8775f4b095 100644 --- a/clang/lib/Tooling/Refactor/Extract.cpp +++ b/clang/lib/Tooling/Refactor/Extract.cpp @@ -161,6 +161,22 @@ static bool isMultipleCandidateBinOp(BinaryOperatorKind Op) { return Op == BO_Add || Op == BO_Sub; } +/// Searches for the selected statement in the given CompoundStatement, looking +/// through things like PseudoObjectExpressions. +static CompoundStmt::const_body_iterator +findSelectedStmt(CompoundStmt::body_const_range Statements, + const Stmt *Target) { + return llvm::find_if(Statements, [=](const Stmt *S) { + if (S == Target) + return true; + if (const auto *POE = dyn_cast(S)) { + if (POE->getSyntacticForm() == Target) + return true; + } + return false; + }); +} + /// Returns the first and the last statements that should be extracted from a /// compound statement. Optional getExtractedStatements(const CompoundStmt *CS, @@ -170,9 +186,10 @@ Optional getExtractedStatements(const CompoundStmt *CS, return None; assert(Begin && End); CompoundStatementRange Result; - Result.First = std::find(CS->body_begin(), CS->body_end(), Begin); + Result.First = findSelectedStmt(CS->body(), Begin); assert(Result.First != CS->body_end()); - Result.Last = std::find(Result.First, CS->body_end(), End); + Result.Last = findSelectedStmt( + CompoundStmt::body_const_range(Result.First, CS->body_end()), End); assert(Result.Last != CS->body_end()); return Result; } diff --git a/clang/test/Refactor/Extract/extract-objc-property.m b/clang/test/Refactor/Extract/extract-objc-property.m index 94cb4ef70991a..7fc79193c18d6 100644 --- a/clang/test/Refactor/Extract/extract-objc-property.m +++ b/clang/test/Refactor/Extract/extract-objc-property.m @@ -45,3 +45,16 @@ - (void)prohibitSetterExtraction { // RUN: not clang-refactor-test initiate -action extract -selected=setter -selected=setter-pref -selected=implicit-setter -selected=implicit-setter-pref %s -fobjc-arc 2>&1 | FileCheck --check-prefix=CHECK-SETTER %s @end + +@interface HasIntProp +@property (readwrite) int item; +@end + +// AVOID-CRASH: "static void extracted(HasIntProp *f) {\nf.item = !f.item;\n}\n\n" +// avoid-extraction-crash-begin: +1:42 +void avoidExtractionCrash(HasIntProp *f) { + f.item = !f.item; +// avoid-extraction-crash-end: -1:5 +} + +// RUN: clang-refactor-test perform -action extract -selected=avoid-extraction-crash %s -fobjc-arc | FileCheck --check-prefix=AVOID-CRASH %s From 5480dba1c17059c51fc9c33d1ebc41a3dcd251c2 Mon Sep 17 00:00:00 2001 From: Ben Langmuir Date: Thu, 10 Aug 2017 13:34:52 -0700 Subject: [PATCH 193/585] [index] Fix a crash indexing a non-canonical template declaration rdar://problem/33811115 apple-llvm-split-commit: 549cfcef848556cc1f938766fb8e6dc31425a94d apple-llvm-split-dir: clang/ --- clang/lib/Index/IndexRecordHasher.cpp | 2 +- clang/test/Index/Store/record-hash-crash.cpp | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/clang/lib/Index/IndexRecordHasher.cpp b/clang/lib/Index/IndexRecordHasher.cpp index dd3c11ddfc797..855f902f24e67 100644 --- a/clang/lib/Index/IndexRecordHasher.cpp +++ b/clang/lib/Index/IndexRecordHasher.cpp @@ -304,7 +304,7 @@ static hash_code computeHash(TemplateName Name, IndexRecordHasher &Hasher) { return COMBINE_HASH('t', TTP->getDepth(), TTP->getIndex()); } - return COMBINE_HASH(Hasher.hash(Template)); + return COMBINE_HASH(Hasher.hash(Template->getCanonicalDecl())); } // FIXME: Hash dependent template names. diff --git a/clang/test/Index/Store/record-hash-crash.cpp b/clang/test/Index/Store/record-hash-crash.cpp index 8239901669c04..3d71ac9234743 100644 --- a/clang/test/Index/Store/record-hash-crash.cpp +++ b/clang/test/Index/Store/record-hash-crash.cpp @@ -10,3 +10,22 @@ namespace crash1 { // CHECK: [[@LINE+1]]:6 | function/C auto getit() { return []() {}; } } + +namespace crash2 { +// CHECK: [[@LINE+2]]:7 | class(Gen)/C++ | c:@N@crash2@ST>1#T@Foo | Ref,RelCont | rel: 1 +template +class Foo; // canonical decl + +// CHECK: [[@LINE+2]]:7 | class(Gen)/C++ | c:@N@crash2@ST>1#T@Foo | Def,RelChild | rel: 1 +template +class Foo {}; + +// CHECK: [[@LINE+2]]:8 | struct(Gen)/C++ | c:@N@crash2@ST>1#t>1#pT@Wrapper | Def,RelChild | rel: 1 +template