#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include "llvm/ADT/StringSet.h" #include "revng/ADT/RecursiveCoroutine.h" #include "revng/ADT/SortedVector.h" #include "revng/Model/ArrayType.h" #include "revng/Model/Binary.h" #include "revng/Model/CABIFunctionDefinition.h" #include "revng/Model/EnumDefinition.h" #include "revng/Model/Function.h" #include "revng/Model/Helpers.h" #include "revng/Model/LocalIdentifier.h" #include "revng/Model/NamingConfiguration.h" #include "revng/Model/PrimitiveType.h" #include "revng/Model/RawFunctionDefinition.h" #include "revng/Model/StructDefinition.h" #include "revng/Model/UnionDefinition.h" #include "revng/Support/CommonOptions.h" #include "revng/Support/MetaAddress.h" #include "revng/Support/StringOperations.h" namespace model { template class NameBuilder { private: using RFT = model::RawFunctionDefinition; public: const model::NamingConfiguration &Configuration; public: NameBuilder(const model::Binary &Binary) : Configuration(Binary.Configuration().Naming()) {} public: [[nodiscard]] llvm::Error isNameReserved(llvm::StringRef Name) const { return static_cast(*this).isNameReserved(Name); } void assertNameIsReserved(llvm::StringRef Name) const { if (llvm::Error Error = isNameReserved(Name)) { // All good, we want an error here. llvm::consumeError(std::move(Error)); } else { std::string ActualError = "An automatic name `" + Name.str() + "` is reserved and should therefore be " "banned in order to avoid collisions."; revng_abort(ActualError.c_str()); } } private: [[nodiscard]] std::string automaticName(const model::Binary &Binary, const model::Segment &Segment) const { auto Iterator = Binary.Segments().find(Segment.key()); auto Result = std::string(Configuration.UnnamedSegmentPrefix()) + std::to_string(std::distance(Binary.Segments().begin(), Iterator)); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::DynamicFunction &Function) const { // TODO: use something nicer to look at than hash, maybe punycode. std::string Result = Configuration.UnnamedDynamicFunctionPrefix() + revng::nameHash(Function.Name()); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::Function &Function) const { std::string Result = Configuration.UnnamedFunctionPrefix() + Function.Entry().toIdentifier(); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::TypeDefinition &Definition) const { auto K = model::TypeDefinitionKind::automaticNamePrefix(Definition.Kind()); std::string Result = Configuration.UnnamedTypeDefinitionPrefix() + K.str() + std::to_string(Definition.ID()); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::EnumDefinition &Definition, const model::EnumEntry &Entry) const { std::string Result = Configuration.UnnamedEnumEntryPrefix() + name(Definition) + "_" + std::to_string(Entry.Value()); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::StructDefinition &Definition, const model::StructField &Field) const { std::string Result = Configuration.UnnamedStructFieldPrefix() + std::to_string(Field.Offset()); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::UnionDefinition &Definition, const model::UnionField &Field) const { std::string Result = Configuration.UnnamedUnionFieldPrefix() + std::to_string(Field.Index()); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::CABIFunctionDefinition &Function, const model::Argument &Argument) const { std::string Result = Configuration.UnnamedFunctionArgumentPrefix() + std::to_string(Argument.Index()); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::RawFunctionDefinition &Function, const model::NamedTypedRegister &Argument) const { std::string Result = Configuration.UnnamedFunctionRegisterPrefix() + std::string(getRegisterName(Argument.Location())); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string automaticName(const model::StackFrame &StackFrame) const { std::string Result = Configuration.UnnamedStackFrameVariableName(); assertNameIsReserved(Result); return Result; } public: [[nodiscard]] bool isAutomaticName(EntityWithName auto const &E, llvm::StringRef Name) { return automaticName(E) == Name; } [[nodiscard]] bool isAutomaticName(const auto &Parent, EntityWithName auto const &E, llvm::StringRef Name) { return automaticName(Parent, E) == Name; } public: /// These method (and its overloads) should be the only way you obtain any /// name you embed into an artifact (be it decompiled c, disassembly, or /// anything else). /// /// That ensures that the names always comply with our policy for names. /// Which is as follows: /// /// Users can give _any_ names to the objects they are allowed to rename /// in the model and as long as such names are unique, the model will be /// valid (*). /// BUT, such names can sometimes be suppressed and replaced by their /// corresponding automatic names (every user-renamable object always has /// one). That happens if: /// 1. The name contains symbols not allowed to be emitted by underlying /// language, for example anything outside of `[A-Za-z0-9_]` for C. /// 2. It collides with something underlying language reserves, for example /// we will not emit a function named `if` in C, or `rax` in assembly. /// 3. It collides with something we reserve, which includes: /// 1. automatic names for every renamable object, for example, struct /// names: `struct_42`. /// 2. names for static stuff that cannot be renamed, for example, /// primitive names: `uint64_t`. /// 3. names for dynamic stuff that cannot be renamed, for example, /// helper names: `page_dump`. /// 4. some extra prefixes, like everything starting with `__builtin_`. /// /// ------------------------ /// (*) Temporarily, there are further restrictions, namely you are not /// allowed to use `/`. But these will eventually be lifted. [[nodiscard]] std::string name(EntityWithName auto const &E) const { if (shouldUseAutomaticName(E)) return automaticName(E); else return E.Name(); } [[nodiscard]] std::string name(const auto &Parent, EntityWithName auto const &E) const { if (shouldUseAutomaticName(E)) return automaticName(Parent, E); else return E.Name(); } [[nodiscard]] bool shouldUseAutomaticName(EntityWithName auto const &E) const { if (E.Name().empty()) { return true; } else if (llvm::Error Error = isNameReserved(E.Name())) { // We don't care what the specific error is - if there is one, // just fall back on the automatic name. llvm::consumeError(std::move(Error)); return true; } else { return false; } } // This is just here for compatibility: it's nice to be able to pass anything // into the name builder and get the name back. std::string name(const model::PrimitiveType &Primitive) const { std::string Result = Primitive.getCName(); assertNameIsReserved(Result); return Result; } /// This helper provides shared functionality for all the different types /// of counting name builders (those that emit `prefix_1`, `prefix_2`, /// `prefix_3`, and so on as default names on sequential requests). /// /// It ensures that: /// - a single *name* cannot appear more than once /// - a single *location* cannot appear more than once /// - the indexes (either a part of a name, or the corresponding locations) /// are unique for each \ref nameImpl call. class CountingNameBuilder { private: const NameBuilder *Parent = nullptr; uint64_t NextIndex = 0; /// This is used to keep track of the *names* that were already emitted. llvm::DenseSet EmittedNames = {}; /// This is used to keep track of the *locations* that were already emitted. std::set> EmittedLocations = {}; protected: const NameBuilder &parent() const { return notNull(Parent); } public: CountingNameBuilder() = default; CountingNameBuilder(const NameBuilder &Parent) : Parent(&Parent) {} public: struct NamingResult { std::string Name; /// The index of this variable provided to enable `pipeline::Location` /// to use it as a key. uint64_t Index; /// This flag is set for variables that cannot be located (either no /// address set can be gathered or the gathered address set is already /// attached to another variable). Such variables should have any actions /// allowed on them. bool HasAddressAssociated; /// Since the same variable shouldn't be queried more then once, /// the warning appears warning here instead of using a dedicated query /// method (like the base `NameBuilder`). std::string Warning = {}; }; [[nodiscard]] NamingResult automaticName(llvm::StringRef Prefix, bool HasAddressAssociated) { uint64_t CurrentIndex = NextIndex++; std::string Result = Prefix.str() + std::to_string(CurrentIndex); parent().assertNameIsReserved(Result); return { Result, CurrentIndex, HasAddressAssociated }; } protected: [[nodiscard]] NamingResult nameImpl(SortedVector const &UserLocationSet, RangeOf auto const &KnownNames, llvm::StringRef AutomaticPrefix) { if (UserLocationSet.empty()) { // No location is provided, emit an *untagged* automatic name. return automaticName(AutomaticPrefix, false); } bool IsLocationUnique = EmittedLocations.insert(UserLocationSet).second; if (not IsLocationUnique) { // This location was used for a different variable already, // emit an *untagged* automatic name. return automaticName(AutomaticPrefix, false); } auto Comparator = [&](const model::LocalIdentifier &Identifier) { return Identifier.Location() == UserLocationSet; }; auto Iterator = std::ranges::find_if(KnownNames, Comparator); if (Iterator == KnownNames.end()) { // There's nothing in the model for the current location, // emit a *tagged* automatic name. return automaticName(AutomaticPrefix, true); } bool IsNameUnique = EmittedNames.insert(Iterator->Name()).second; if (not IsNameUnique) { // This name was already emitted, fall back on the automatic name. auto Result = automaticName(AutomaticPrefix, true); Result.Warning = "Name `" + Iterator->Name() + "` cannot be used more than once, so it was " "replaced by an automatic one (`" + Result.Name + "`)."; return Result; } if (llvm::Error Reason = parent().isNameReserved(Iterator->Name())) { // Current name cannot be used as its reserved for something else. auto Result = automaticName(AutomaticPrefix, true); Result.Warning = "Name `" + Iterator->Name() + "` is not valid, so it was replaced by an automatic " "one (`" + Result.Name + "`) because " + revng::unwrapError(std::move(Reason)) + "."; return Result; } else { // We don't want existing automatic names to "shift" when a rename // happens, as such we have to advance the index even if no automatic // name is produced for it. uint64_t CurrentIndex = NextIndex++; return { Iterator->Name(), CurrentIndex, true }; } } [[nodiscard]] std::set homelessNamesImpl(RangeOf auto R) const { return R | std::views::filter([this](const auto &V) { return not EmittedNames.contains(V.Name()); }) | std::views::transform([](const auto &V) { return V.Name(); }) | revng::to>(); } }; class VariableNameBuilder : public CountingNameBuilder { const model::Function *Function = nullptr; public: VariableNameBuilder() = default; VariableNameBuilder(const NameBuilder &Parent, const model::Function &Function) : CountingNameBuilder(Parent), Function(&Function) {} const model::Function &function() const { return notNull(Function); } CountingNameBuilder::NamingResult automaticName() { auto Prefix = this->parent().Configuration.UnnamedLocalVariablePrefix(); return CountingNameBuilder::automaticName(Prefix, /*HasAddressAssociated=*/false); } CountingNameBuilder::NamingResult name(SortedVector const &UserLocationSet) { auto Prefix = this->parent().Configuration.UnnamedLocalVariablePrefix(); return CountingNameBuilder::nameImpl(UserLocationSet, function().LocalVariables(), Prefix); } CountingNameBuilder::NamingResult name(TrackingSortedVector const &UserLocationSet) { auto Prefix = this->parent().Configuration.UnnamedLocalVariablePrefix(); return CountingNameBuilder::nameImpl(UserLocationSet.unwrap(), function().LocalVariables(), Prefix); } std::set homelessNames() const { return CountingNameBuilder::homelessNamesImpl(Function->LocalVariables()); } }; VariableNameBuilder localVariables(const model::Function &Function) const { return VariableNameBuilder(*this, Function); } class GotoLabelNameBuilder : public CountingNameBuilder { const model::Function *Function = nullptr; public: GotoLabelNameBuilder() = default; GotoLabelNameBuilder(const NameBuilder &Parent, const model::Function &Function) : CountingNameBuilder(Parent), Function(&Function) {} const model::Function &function() const { return notNull(Function); } CountingNameBuilder::NamingResult automaticName() { auto Prefix = this->parent().Configuration.UnnamedGotoLabelPrefix(); return CountingNameBuilder::automaticName(Prefix, /*HasAddressAssociated=*/false); } CountingNameBuilder::NamingResult name(SortedVector const &UserLocationSet) { auto Prefix = this->parent().Configuration.UnnamedGotoLabelPrefix(); return CountingNameBuilder::nameImpl(UserLocationSet, function().GotoLabels(), Prefix); } CountingNameBuilder::NamingResult name(TrackingSortedVector const &UserLocationSet) { auto Prefix = this->parent().Configuration.UnnamedGotoLabelPrefix(); return CountingNameBuilder::nameImpl(UserLocationSet.unwrap(), function().GotoLabels(), Prefix); } std::set homelessNames() const { return CountingNameBuilder::homelessNamesImpl(Function->GotoLabels()); } }; GotoLabelNameBuilder gotoLabels(const model::Function &Function) const { return GotoLabelNameBuilder(*this, Function); } private: [[nodiscard]] std::optional warningImpl(const std::string &Name, const std::string &OriginalName, llvm::Error &&Reason) const { if (Name == OriginalName) { // Suppress warnings on appropriately placed automatic names in the model. // // TODO: get rid of this once `import-from-c` is fixed and we no longer // put such names in. llvm::consumeError(std::move(Reason)); return std::nullopt; } return "Name `" + OriginalName + "` is not valid, so it was replaced by an automatic one (`" + Name + "`) because " + revng::unwrapError(std::move(Reason)) + "."; } public: [[nodiscard]] std::optional warning(EntityWithName auto const &E) const { if (E.Name().empty()) return std::nullopt; if (auto Reason = isNameReserved(E.Name())) return warningImpl(automaticName(E), E.Name(), std::move(Reason)); return std::nullopt; } [[nodiscard]] std::optional warning(const auto &Parent, EntityWithName auto const &E) const { if (E.Name().empty()) return std::nullopt; if (auto Reason = isNameReserved(E.Name())) return warningImpl(automaticName(Parent, E), E.Name(), std::move(Reason)); return std::nullopt; } public: [[nodiscard]] std::string paddingFieldName(uint64_t Offset) const { std::string Result = Configuration.StructPaddingPrefix() + std::to_string(Offset); assertNameIsReserved(Result); return Result; } public: [[nodiscard]] std::string artificialReturnValueWrapperName(const RFT &Function) const { auto Result = Configuration.ArtificialReturnValuePrefix() + name(Function); assertNameIsReserved(Result); return Result; } [[nodiscard]] std::string opaqueTypeName(uint64_t ByteSize) const { auto Result = Configuration.OpaqueTypePrefix() + std::to_string(ByteSize); assertNameIsReserved(Result); return Result; } }; struct CNameBuilder : public NameBuilder { public: using NameBuilder::NameBuilder; [[nodiscard]] llvm::Error isNameReserved(llvm::StringRef Name) const; }; struct AssemblyNameBuilder : public NameBuilder { public: const model::Architecture::Values &Architecture; public: AssemblyNameBuilder(const model::Binary &Binary) : NameBuilder(Binary), Architecture(Binary.Architecture()) {} [[nodiscard]] llvm::Error isNameReserved(llvm::StringRef Name) const; }; } // namespace model