//===- Core/File.h - A Container of Atoms ---------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLD_CORE_FILE_H #define LLD_CORE_FILE_H #include "lld/Core/AbsoluteAtom.h" #include "lld/Core/DefinedAtom.h" #include "lld/Core/SharedLibraryAtom.h" #include "lld/Core/UndefinedAtom.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/ErrorHandling.h" #include #include #include #include namespace lld { class LinkingContext; /// Every Atom is owned by some File. A common scenario is for a single /// object file (.o) to be parsed by some reader and produce a single /// File object that represents the content of that object file. /// /// To iterate through the Atoms in a File there are four methods that /// return collections. For instance to iterate through all the DefinedAtoms /// in a File object use: /// for (const DefinedAtoms *atom : file->defined()) { /// } /// /// The Atom objects in a File are owned by the File object. The Atom objects /// are destroyed when the File object is destroyed. class File { public: virtual ~File(); /// Kinds of files that are supported. enum Kind { kindErrorObject, ///< a error object file (.o) kindNormalizedObject, ///< a normalized file (.o) kindMachObject, ///< a MachO object file (.o) kindCEntryObject, ///< a file for CEntries kindHeaderObject, ///< a file for file headers kindEntryObject, ///< a file for the entry kindUndefinedSymsObject, ///< a file for undefined symbols kindStubHelperObject, ///< a file for stub helpers kindResolverMergedObject, ///< the resolver merged file. kindSectCreateObject, ///< a sect create object file (.o) kindSharedLibrary, ///< shared library (.so) kindArchiveLibrary ///< archive (.a) }; /// Returns file kind. Need for dyn_cast<> on File objects. Kind kind() const { return _kind; } /// This returns the path to the file which was used to create this object /// (e.g. "/tmp/foo.o"). If the file is a member of an archive file, the /// returned string includes the archive file name. StringRef path() const { if (_archivePath.empty()) return _path; if (_archiveMemberPath.empty()) _archiveMemberPath = (_archivePath + "(" + _path + ")").str(); return _archiveMemberPath; } /// Returns the path of the archive file name if this file is instantiated /// from an archive file. Otherwise returns the empty string. StringRef archivePath() const { return _archivePath; } void setArchivePath(StringRef path) { _archivePath = std::string(path); } /// Returns the path name of this file. It doesn't include archive file name. StringRef memberPath() const { return _path; } /// Returns the command line order of the file. uint64_t ordinal() const { assert(_ordinal != UINT64_MAX); return _ordinal; } /// Returns true/false depending on whether an ordinal has been set. bool hasOrdinal() const { return (_ordinal != UINT64_MAX); } /// Sets the command line order of the file. void setOrdinal(uint64_t ordinal) const { _ordinal = ordinal; } /// Returns the ordinal for the next atom to be defined in this file. uint64_t getNextAtomOrdinalAndIncrement() const { return _nextAtomOrdinal++; } /// For allocating any objects owned by this File. llvm::BumpPtrAllocator &allocator() const { return _allocator; } /// The type of atom mutable container. template using AtomVector = std::vector>; /// The range type for the atoms. template class AtomRange { public: AtomRange(AtomVector &v) : _v(v) {} AtomRange(const AtomVector &v) : _v(const_cast &>(v)) {} using ConstDerefFn = const T* (*)(const OwningAtomPtr&); using DerefFn = T* (*)(OwningAtomPtr&); typedef llvm::mapped_iterator::const_iterator, ConstDerefFn> ConstItTy; typedef llvm::mapped_iterator::iterator, DerefFn> ItTy; static const T* DerefConst(const OwningAtomPtr &p) { return p.get(); } static T* Deref(OwningAtomPtr &p) { return p.get(); } ConstItTy begin() const { return ConstItTy(_v.begin(), ConstDerefFn(DerefConst)); } ConstItTy end() const { return ConstItTy(_v.end(), ConstDerefFn(DerefConst)); } ItTy begin() { return ItTy(_v.begin(), DerefFn(Deref)); } ItTy end() { return ItTy(_v.end(), DerefFn(Deref)); } llvm::iterator_range::iterator> owning_ptrs() { return llvm::make_range(_v.begin(), _v.end()); } llvm::iterator_range::iterator> owning_ptrs() const { return llvm::make_range(_v.begin(), _v.end()); } bool empty() const { return _v.empty(); } size_t size() const { return _v.size(); } const OwningAtomPtr &operator[](size_t idx) const { return _v[idx]; } OwningAtomPtr &operator[](size_t idx) { return _v[idx]; } private: AtomVector &_v; }; /// Must be implemented to return the AtomVector object for /// all DefinedAtoms in this File. virtual const AtomRange defined() const = 0; /// Must be implemented to return the AtomVector object for /// all UndefinedAtomw in this File. virtual const AtomRange undefined() const = 0; /// Must be implemented to return the AtomVector object for /// all SharedLibraryAtoms in this File. virtual const AtomRange sharedLibrary() const = 0; /// Must be implemented to return the AtomVector object for /// all AbsoluteAtoms in this File. virtual const AtomRange absolute() const = 0; /// Drop all of the atoms owned by this file. This will result in all of /// the atoms running their destructors. /// This is required because atoms may be allocated on a BumpPtrAllocator /// of a different file. We need to destruct all atoms before any files. virtual void clearAtoms() = 0; /// If a file is parsed using a different method than doParse(), /// one must use this method to set the last error status, so that /// doParse will not be called twice. Only YAML reader uses this /// (because YAML reader does not read blobs but structured data). void setLastError(std::error_code err) { _lastError = err; } std::error_code parse(); // Usually each file owns a std::unique_ptr. // However, there's one special case. If a file is an archive file, // the archive file and its children all shares the same memory buffer. // This method is used by the ArchiveFile to give its children // co-ownership of the buffer. void setSharedMemoryBuffer(std::shared_ptr mb) { _sharedMemoryBuffer = mb; } protected: /// only subclasses of File can be instantiated File(StringRef p, Kind kind) : _path(p), _kind(kind), _ordinal(UINT64_MAX), _nextAtomOrdinal(0) {} /// Subclasses should override this method to parse the /// memory buffer passed to this file's constructor. virtual std::error_code doParse() { return std::error_code(); } static AtomVector _noDefinedAtoms; static AtomVector _noUndefinedAtoms; static AtomVector _noSharedLibraryAtoms; static AtomVector _noAbsoluteAtoms; mutable llvm::BumpPtrAllocator _allocator; private: StringRef _path; std::string _archivePath; mutable std::string _archiveMemberPath; Kind _kind; mutable uint64_t _ordinal; mutable uint64_t _nextAtomOrdinal; std::shared_ptr _sharedMemoryBuffer; llvm::Optional _lastError; std::mutex _parseMutex; }; /// An ErrorFile represents a file that doesn't exist. /// If you try to parse a file which doesn't exist, an instance of this /// class will be returned. That's parse method always returns an error. /// This is useful to delay erroring on non-existent files, so that we /// can do unit testing a driver using non-existing file paths. class ErrorFile : public File { public: ErrorFile(StringRef path, std::error_code ec) : File(path, kindErrorObject), _ec(ec) {} std::error_code doParse() override { return _ec; } const AtomRange defined() const override { llvm_unreachable("internal error"); } const AtomRange undefined() const override { llvm_unreachable("internal error"); } const AtomRange sharedLibrary() const override { llvm_unreachable("internal error"); } const AtomRange absolute() const override { llvm_unreachable("internal error"); } void clearAtoms() override { } private: std::error_code _ec; }; } // end namespace lld #endif