// // This file is distributed under the MIT License. See LICENSE.md for details. // #include "llvm/IR/Intrinsics.h" #include "revng/Model/Binary.h" #include "revng/Model/NameBuilder.h" #include "revng/PTML/CAttributes.h" #include "revng/Support/Identifier.h" #include "revng/Support/ResourceFinder.h" #include "revng/Support/YAMLTraits.h" static size_t trailingDigitCount(llvm::StringRef Name) { size_t Result = 0; for (auto Character : std::views::reverse(Name)) { if (not std::isdigit(Character)) return Result; else ++Result; } revng_assert(Result == Name.size()); return Result; } static llvm::StringRef dropWrapperSuffix(llvm::StringRef Name) { size_t Position = Name.find("_wrapper"); if (Position != llvm::StringRef::npos) return Name.substr(0, Position); return Name; } static bool isLLVMIntrinsic(llvm::StringRef Name) { // Since intrinsics are all guaranteed to start with `llvm_`, // we can skip the check for most of the identifiers. if (not Name.starts_with("llvm_")) return false; // Start iteration from 1 because 0 is reserved for `not_intrinsic`. for (llvm::Intrinsic::ID I = 1; I < llvm::Intrinsic::num_intrinsics; ++I) { auto Sanitized = sanitizeIdentifier(llvm::Intrinsic::getBaseName(I)); if (Name.starts_with(Sanitized)) return true; } return false; } static const SortedVector &loadHelperNameList() { static std::optional> Cache = std::nullopt; if (not Cache.has_value()) { auto MaybePath = revng::ResourceFinder.findFile("share/revng/" "helper-list.csv"); revng_assert(MaybePath.has_value(), ("Helper list is missing: " + *MaybePath).c_str()); auto MaybeBuffer = llvm::MemoryBuffer::getFile(*MaybePath, true); revng_assert(MaybeBuffer, ("Can't open " + *MaybePath).c_str()); llvm::SmallVector Names; llvm::StringRef(MaybeBuffer->get()->getBuffer()).split(Names, "\n"); revng_assert(!Names.empty()); revng_assert(Names[0] == "Name"); revng_assert(Names.back().empty()); Names.pop_back(); Cache = SortedVector(); auto Inserter = Cache->batch_insert(); for (llvm::StringRef Name : Names | std::views::drop(1)) { revng_assert(not Name.empty()); revng_assert(Name == Name.trim()); // We don't want to ban `main`, even if it's is in the helper module. if (Name == "main") continue; // We don't care about llvm debug information. if (Name.starts_with("llvm.dbg")) continue; // No reason to double-ban intrinsics. if (isLLVMIntrinsic(Name)) continue; // TODO: other names we don't want to ban? Inserter.insert(sanitizeIdentifier(Name)); } } return *Cache; } const std::set ReservedKeywords = { // Reserved keywords for primitive types. // Note that the primitive types we emit are checked separately. "int_fast8_t", "int_fast16_t", "int_fast32_t", "int_fast64_t", "int_fast128_t", "int_least8_t", "int_least16_t", "int_least32_t", "int_least64_t", "int_least128_t", "intmax_t", "intptr_t", "uint_fast8_t", "uint_fast16_t", "uint_fast32_t", "uint_fast64_t", "uint_fast128_t", "uint_least8_t", "uint_least16_t", "uint_least32_t", "uint_least64_t", "uint_least128_t", "uintmax_t", "uintptr_t", // Integer macros from stdint.h, reserved to prevent clashes. "INT8_WIDTH", "INT16_WIDTH", "INT32_WIDTH", "INT64_WIDTH", "INT_FAST8_WIDTH", "INT_FAST16_WIDTH", "INT_FAST32_WIDTH", "INT_FAST64_WIDTH", "INT_LEAST8_WIDTH", "INT_LEAST16_WIDTH", "INT_LEAST32_WIDTH", "INT_LEAST64_WIDTH", "INTPTR_WIDTH", "INTMAX_WIDTH", "INT8_MIN", "INT16_MIN", "INT32_MIN", "INT64_MIN", "INT_FAST8_MIN", "INT_FAST16_MIN", "INT_FAST32_MIN", "INT_FAST64_MIN", "INT_LEAST8_MIN", "INT_LEAST16_MIN", "INT_LEAST32_MIN", "INT_LEAST64_MIN", "INTPTR_MIN", "INTMAX_MIN", "INT8_MAX", "INT16_MAX", "INT32_MAX", "INT64_MAX", "INT_FAST8_MAX", "INT_FAST16_MAX", "INT_FAST32_MAX", "INT_FAST64_MAX", "INT_LEAST8_MAX", "INT_LEAST16_MAX", "INT_LEAST32_MAX", "INT_LEAST64_MAX", "INTPTR_MAX", "INTMAX_MAX", "UINT8_WIDTH", "UINT16_WIDTH", "UINT32_WIDTH", "UINT64_WIDTH", "UINT_FAST8_WIDTH", "UINT_FAST16_WIDTH", "UINT_FAST32_WIDTH", "UINT_FAST64_WIDTH", "UINT_LEAST8_WIDTH", "UINT_LEAST16_WIDTH", "UINT_LEAST32_WIDTH", "UINT_LEAST64_WIDTH", "UINTPTR_WIDTH", "UINTMAX_WIDTH", "UINT8_MAX", "UINT16_MAX", "UINT32_MAX", "UINT64_MAX", "UINT_FAST8_MAX", "UINT_FAST16_MAX", "UINT_FAST32_MAX", "UINT_FAST64_MAX", "UINT_LEAST8_MAX", "UINT_LEAST16_MAX", "UINT_LEAST32_MAX", "UINT_LEAST64_MAX", "UINTPTR_MAX", "UINTMAX_MAX", "INT8_C", "INT16_C", "INT32_C", "INT64_C", "INTMAX_C", "UINT8_C", "UINT16_C", "UINT32_C", "UINT64_C", "UINTMAX_C", // C reserved keywords "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", // Since C99 "int", "long", "register", "restrict", // Since C99 "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "volatile", "while", "_Alignas", // Since C11 "_Alignof", // Since C11 "_Atomic", // Since C11 "_Bool", // Since C99 "_Complex", // Since C99 "_Decimal128", // Since C23 "_Decimal32", // Since C23 "_Decimal64", // Since C23 "_Generic", // Since C11 "_Imaginary", // Since C99 "_Noreturn", // Since C11 "_Static_assert", // Since C11 "_Thread_local", // Since C11 // Convenience macros "alignas", "alignof", "bool", "complex", "imaginary", "noreturn", "static_assert", "thread_local", // Common C stdlib names "assert", "max_align_t", "NULL", "nullptr_t", "offsetof", "ptrdiff_t", "size_t", "wchar_t", // Convenience macros for atomic types "atomic_bool", "atomic_char", "atomic_schar", "atomic_uchar", "atomic_short", "atomic_ushort", "atomic_int", "atomic_uint", "atomic_long", "atomic_ulong", "atomic_llong", "atomic_ullong", "atomic_char16_t", "atomic_char32_t", "atomic_wchar_t", "atomic_int_least8_t", "atomic_uint_least8_t", "atomic_int_least16_t", "atomic_uint_least16_t", "atomic_int_least32_t", "atomic_uint_least32_t", "atomic_int_least64_t", "atomic_uint_least64_t", "atomic_int_fast8_t", "atomic_uint_fast8_t", "atomic_int_fast16_t", "atomic_uint_fast16_t", "atomic_int_fast32_t", "atomic_uint_fast32_t", "atomic_int_fast64_t", "atomic_uint_fast64_t", "atomic_intptr_t", "atomic_uintptr_t", "atomic_size_t", "atomic_ptrdiff_t", "atomic_intmax_t", "atomic_uintmax_t", // C Extensions "_Pragma", "asm", // Support library "undef", "undef_value", "break_to", "continue_to", "bit_cast", "static_assert_typedef", }; /// Returns `true` iff the identifier is exactly the given prefix + a decimal /// number. For example (Prefix = `argument_` ): /// * `argument_4` - `true` /// * `argument_4a` - `false` /// * `argument_4_` - `false` /// * `argument_` - `false` static bool isPrefixAndIndex(llvm::StringRef Name, llvm::StringRef Prefix) { if (not Name.starts_with(Prefix)) return false; size_t DigitCount = trailingDigitCount(Name); return DigitCount != 0 && Name.size() - Prefix.size() == DigitCount; } /// Returns `true` iff the identifier is exactly the given prefix + /// a serialized (flattened) `MetaAddress`. For example (Prefix = `function_`): /// * `function_0x1004_Code_arm` - `true` /// * `function_0x1004_Code_arma` - `false` /// * `function_0x1004_Code_arm_` - `false` /// * `function_0x1004_Co_de_arm` - `false` /// * `function_` - `false` static bool isPrefixAndAddress(llvm::StringRef Name, llvm::StringRef Prefix) { if (not Name.starts_with(Prefix)) return false; // This is a hack to let us reuse meta-address parsing. // // We want this because this would allow keeping names like // `function__anything` while still banning // `function_`. // // TODO: find a cleaner solution for detecting dehydrated (`:` -> `_`) // addresses. std::string RehydratedAddress = Name.substr(Prefix.size()).str(); for (size_t Index = 0; Index < RehydratedAddress.size(); ++Index) { if (RehydratedAddress[Index] != '_') continue; if (Index > 5) { // Note that this specifies "Code_" because currently that's the only // place where `MetaAddressType` can contain a `_`. if (RehydratedAddress.substr(Index - 4, 5) == "Code_") { auto Remainder = llvm::StringRef(RehydratedAddress).substr(Index - 4); size_t S = MetaAddressType::consumeFromString(Remainder).size(); if (S != RehydratedAddress.size()) { revng_assert(RehydratedAddress.size() >= S); Index += RehydratedAddress.size() - S - 1; continue; } } } RehydratedAddress[Index] = ':'; } return MetaAddress::fromString(RehydratedAddress).isValid(); } /// Returns `true` iff the identifier is exactly the given prefix + /// a serialized model type key. /// /// Note that unlike other similar functions, the prefix is allowed (and even /// expected) to be empty here. /// /// For example (Prefix = ``): /// * `struct_42` - `true` /// * `struct_42a` - `false` /// * `struct_42_` - `false` /// * `struct_4_2` - `false` /// * `struct_` - `false` static bool isPrefixAndTypeDefinitionKey(llvm::StringRef Name, llvm::StringRef Prefix) { if (not Name.starts_with(Prefix)) return false; llvm::StringRef WithoutPrefix = Name.substr(Prefix.size()); namespace TDK = model::TypeDefinitionKind; llvm::StringRef WithoutKind = TDK::consumeNamePrefix(WithoutPrefix); if (WithoutPrefix.size() == WithoutKind.size()) return false; return isPrefixAndIndex(WithoutKind, ""); } /// Returns `true` iff the identifier is exactly the given prefix + /// a serialized register name *without* the architecture. /// /// For example (Prefix = `register_`): /// * `register_r4` - `true` /// * `register_r4a` - `false` /// * `register_r4_` - `false` /// * `register_r_4` - `false` /// * `register_` - `false` static bool isPrefixAndRegister(llvm::StringRef Name, llvm::StringRef Prefix) { if (not Name.starts_with(Prefix)) return false; llvm::StringRef WithoutPrefix = Name.substr(Prefix.size()); for (model::Register::Values R : model::Register::allRegisters()) if (WithoutPrefix == model::Register::getRegisterName(R)) return true; return false; } llvm::Error model::CNameBuilder::isNameReserved(llvm::StringRef Name) const { revng_assert(!Name.empty()); // Only alphanumeric characters and '_' are allowed // TODO: handle unicode auto IsCharacterForbidden = [](char Character) -> bool { return Character != '_' && !std::isalnum(Character); }; auto Iterator = llvm::find_if(Name, IsCharacterForbidden); if (Iterator != Name.end()) { using namespace std::string_literals; return revng::createError("it contains a forbidden character: `"s + *Iterator + '`'); } if (std::isdigit(Name[0])) return revng::createError(std::string{ "it starts with a digit: `" } + Name[0] + '`'); // Filter out primitive names we use - we don't want collisions with those if (model::PrimitiveType::isCName(Name)) return revng::createError("it is reserved for a primitive type we use"); // Forbid names reserved by the C language and some common extensions. if (ReservedKeywords.contains(Name)) return revng::createError("it is reserved by the C language or some common " "extension"); llvm::StringRef Unwrapped = dropWrapperSuffix(Name); if (loadHelperNameList().contains(Unwrapped.str())) return revng::createError("it collides with a helper (`" + Unwrapped.str() + "`)"); if (isLLVMIntrinsic(Name)) return revng::createError("it collides with one of the LLVM intrinsics"); // // The following names are reserved based on the configuration // // Names we emit as macros. if (ptml::Attributes.isMacro(Name)) return revng::createError("it is reserved for a macro we use in the " "decompiled code"); if (Configuration.ReserveNamesStartingWithUnderscore()) if (Name[0] == '_') return revng::createError("it is reserved because it begins with an " "underscore"); // Prefix + `[0-9]+` if (isPrefixAndIndex(Name, Configuration.UnnamedSegmentPrefix())) return revng::createError("it is reserved for an automatic segment name"); if (isPrefixAndIndex(Name, Configuration.UnnamedStructFieldPrefix())) return revng::createError("it is reserved for an automatic struct field " "name"); if (isPrefixAndIndex(Name, Configuration.UnnamedUnionFieldPrefix())) return revng::createError("it is reserved for an automatic struct union " "name"); if (isPrefixAndIndex(Name, Configuration.UnnamedFunctionArgumentPrefix())) return revng::createError("it is reserved for an automatic function " "argument name"); if (isPrefixAndIndex(Name, Configuration.UnnamedLocalVariablePrefix())) return revng::createError("it is reserved for an automatic local variable " "name"); if (isPrefixAndIndex(Name, Configuration.UnnamedBreakFromLoopVariablePrefix())) return revng::createError("it is reserved for a break-from-loop variable " "name"); if (isPrefixAndIndex(Name, Configuration.UnnamedGotoLabelPrefix())) return revng::createError("it is reserved for an automatic goto label " "name"); // NOTE: This should live in the "Prefix + `[0-9]+`" section, but because we // parse these names when importing from C, let's be extra cautious and // forbid them all. if (Name.starts_with(Configuration.StructPaddingPrefix())) return revng::createError("it is reserved for a struct padding name"); // Prefix + MetaAddress.toIdentifier() if (isPrefixAndAddress(Name, Configuration.UnnamedFunctionPrefix())) return revng::createError("it is reserved for an automatic function name"); // Prefix + toString(TypeDefinitionKey) if (isPrefixAndTypeDefinitionKey(Name, Configuration.UnnamedTypeDefinitionPrefix())) return revng::createError("it is reserved for an automatic type name"); // NOTE: since automatic enum entry names depend on (potentially unreserved) // enum names, we have no choice but to reserve everything starting with // this prefix that also ends with a number. if (Name.starts_with(Configuration.UnnamedEnumEntryPrefix()) and std::isdigit(Name.back())) return revng::createError("it is reserved for an automatic enum entry " "name"); // NOTE: since we parse these it's safer to reserve all of them. if (Name.starts_with(Configuration.MaximumEnumValuePrefix())) return revng::createError("it is reserved for a maximum enum value"); // Prefix + model::Register::getRegisterName(Register) if (isPrefixAndRegister(Name, Configuration.UnnamedFunctionRegisterPrefix())) return revng::createError("it is reserved for an automatic register " "argument name"); // NOTE: since artificial return value struct name depends on a (potentially // unreserved) function type name, we have no choice but to reserve // everything starting with this prefix. if (Name.starts_with(Configuration.ArtificialReturnValuePrefix())) return revng::createError("it is reserved for an artificial return value " "struct name"); // NOTE: since CSV value names are kind of external, reserve everything just // to be safe. if (Name.starts_with(Configuration.OpaqueCSVValuePrefix())) return revng::createError("it is reserved for an opaque CSV value"); // NOTE: since these use a hash suffix, let's be on the safe side and reserve // the entire prefix. if (Name.starts_with(Configuration.UnnamedDynamicFunctionPrefix())) return revng::createError("it is reserved for a dynamic function name"); // // Hardcoded prefixes // // TODO: We can be more careful with these and only reserve the ones we use, // We should take a fresh look once `CTargetImplementation` is mature // enough to give us the list. if (Name.starts_with("__builtin_")) return revng::createError("it is reserved for a builtin intrinsic"); // Temporarily ban the following due to limitations of the new model-agnostic // header emitter. // // TODO: when the old pipeline is dropped, user-level configuration options // like these ones will be moved to the configutation-focused // abstraction level only supported by the new one. At which point // `NameBuilder` will have to be updated accordingly. if (Name.starts_with("padding_at_")) return revng::createError("it is reserved for padding fields"); if (Name.starts_with("enum_max_value_")) return revng::createError("it is reserved for the maximum enum value"); // // Exact names // if (Name == Configuration.UnnamedStackFrameVariableName()) return revng::createError("it is reserved for a stack variable name"); if (Name == Configuration.RawStackArgumentName()) return revng::createError("it is reserved for a stack argument name"); if (Name == Configuration.LoopStateVariableName()) return revng::createError("it is reserved for a loop state variable name"); // Anything else to add here? return llvm::Error::success(); } llvm::Error model::AssemblyNameBuilder::isNameReserved(llvm::StringRef Name) const { revng_assert(!Name.empty()); // Only alphanumeric characters and '_' are allowed // TODO: handle unicode auto IsCharacterForbidden = [](char Character) -> bool { return Character != '_' && !std::isalnum(Character); }; auto Iterator = llvm::find_if(Name, IsCharacterForbidden); if (Iterator != Name.end()) { using namespace std::string_literals; return revng::createError("it contains a forbidden character: `"s + *Iterator + '`'); } if (std::isdigit(Name[0])) return revng::createError(std::string{ "it starts with a digit: `" } + Name[0] + '`'); if (Configuration.ReserveNamesStartingWithUnderscore()) if (Name[0] == '_') return revng::createError("it is reserved because it begins with an " "underscore"); // Prefix + MetaAddress.toIdentifier() if (isPrefixAndAddress(Name, Configuration.UnnamedFunctionPrefix())) return revng::createError("it is reserved for an automatic function name"); // NOTE: since these use a hash suffix, let's be on the safe side and reserve // the entire prefix. if (Name.starts_with(Configuration.UnnamedDynamicFunctionPrefix())) return revng::createError("it is reserved for a dynamic function name"); // Register names for (model::Register::Values Register : registers(Architecture)) if (Name == model::Register::getRegisterName(Register)) return revng::createError("it collides with a register name"); // Anything else to add here? return llvm::Error::success(); }