// // Copyright rev.ng Labs Srl. See LICENSE.md for details. // #include #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/IR/Argument.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constant.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Type.h" #include "llvm/IR/Value.h" #include "llvm/Support/Casting.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Progress.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include "revng/ABI/FunctionType/Layout.h" #include "revng/EarlyFunctionAnalysis/FunctionMetadataCache.h" #include "revng/Model/Binary.h" #include "revng/Model/Helpers.h" #include "revng/Model/IRHelpers.h" #include "revng/Model/Identifier.h" #include "revng/Model/PrimitiveTypeKind.h" #include "revng/Model/QualifiedType.h" #include "revng/Model/Qualifier.h" #include "revng/Model/RawFunctionType.h" #include "revng/Model/Segment.h" #include "revng/Model/StructType.h" #include "revng/Model/Type.h" #include "revng/Model/VerifyHelper.h" #include "revng/PTML/Constants.h" #include "revng/PTML/IndentedOstream.h" #include "revng/Pipeline/Location.h" #include "revng/Support/Assert.h" #include "revng/Support/FunctionTags.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/YAMLTraits.h" #include "revng/Yield/PTML.h" #include "revng-c/Backend/DecompileFunction.h" #include "revng-c/Backend/DecompiledCCodeIndentation.h" #include "revng-c/InitModelTypes/InitModelTypes.h" #include "revng-c/Pipes/Ranks.h" #include "revng-c/RestructureCFG/ASTNode.h" #include "revng-c/RestructureCFG/ASTTree.h" #include "revng-c/RestructureCFG/BeautifyGHAST.h" #include "revng-c/RestructureCFG/RestructureCFG.h" #include "revng-c/Support/DecompilationHelpers.h" #include "revng-c/Support/FunctionTags.h" #include "revng-c/Support/IRHelpers.h" #include "revng-c/Support/ModelHelpers.h" #include "revng-c/Support/PTMLC.h" #include "revng-c/TypeNames/LLVMTypeNames.h" #include "revng-c/TypeNames/ModelToPTMLTypeHelpers.h" #include "revng-c/TypeNames/ModelTypeNames.h" using llvm::cast; using llvm::dyn_cast; using llvm::isa; using llvm::BasicBlock; using llvm::CallInst; using llvm::Instruction; using llvm::raw_ostream; using llvm::StringRef; using model::Binary; using model::CABIFunctionType; using model::QualifiedType; using model::Qualifier; using model::RawFunctionType; using model::TypedefType; using pipeline::serializedLocation; using ptml::Tag; namespace ranks = revng::ranks; namespace attributes = ptml::attributes; namespace tokens = ptml::c::tokens; namespace tags = ptml::tags; using tokenDefinition::types::StringToken; using tokenDefinition::types::TypeString; using TokenMapT = std::map; using ModelTypesMap = std::map; using InstrSetVec = llvm::SmallSetVector; using InlineableTypesMap = std::unordered_map>; using QualifiedTypeNameMap = std::map; using TypeToNumOfRefsMap = std::unordered_map; using GraphInfo = TypeInlineHelper::GraphInfo; static constexpr const char *StackFrameVarName = "_stack"; static Logger<> Log{ "c-backend" }; static Logger<> VisitLog{ "c-backend-visit-order" }; static bool isAssignment(const llvm::Value *I) { return isCallToTagged(I, FunctionTags::Assign); } static bool isArtificialAggregateLocalVarDecl(const llvm::Value *I) { return isCallToIsolatedFunction(I) and I->getType()->isAggregateType(); } static bool isLocalVarDecl(const llvm::Value *I) { return isCallToTagged(I, FunctionTags::LocalVariable); } static bool isCallStackArgumentDecl(const llvm::Value *I) { auto *Call = dyn_cast_or_null(I); if (not Call) return false; auto *Callee = Call->getCalledFunction(); if (not Callee) return false; return Callee->getName().startswith("revng_call_stack_arguments"); } static bool isStackFrameDecl(const llvm::Value *I) { auto *Call = dyn_cast_or_null(I); if (not Call) return false; auto *Callee = Call->getCalledFunction(); if (not Callee) return false; return Callee->getName().startswith("revng_stack_frame"); } static const llvm::CallInst *isCallToNonIsolated(const llvm::Instruction *I) { if (isCallToTagged(I, FunctionTags::QEMU) or isCallToTagged(I, FunctionTags::Helper) or isCallToTagged(I, FunctionTags::Exceptional) or llvm::isa(I)) return llvm::cast(I); return nullptr; } static bool isCallToCustomOpcode(const llvm::Instruction *I) { return isCallToTagged(I, FunctionTags::Copy) or isCallToTagged(I, FunctionTags::Assign) or isCallToTagged(I, FunctionTags::ModelCast) or isCallToTagged(I, FunctionTags::ModelGEP) or isCallToTagged(I, FunctionTags::ModelGEPRef) or isCallToTagged(I, FunctionTags::AddressOf) or isCallToTagged(I, FunctionTags::Parentheses) or isCallToTagged(I, FunctionTags::OpaqueCSVValue) or isCallToTagged(I, FunctionTags::OpaqueExtractValue) or isCallToTagged(I, FunctionTags::StructInitializer) or isCallToTagged(I, FunctionTags::SegmentRef) or isCallToTagged(I, FunctionTags::UnaryMinus) or isCallToTagged(I, FunctionTags::BinaryNot) or isCallToTagged(I, FunctionTags::BooleanNot) or isCallToTagged(I, FunctionTags::StringLiteral); } static bool isIntegerConstFormatting(const llvm::Value *Call) { return isCallToTagged(Call, FunctionTags::HexInteger) or isCallToTagged(Call, FunctionTags::CharInteger) or isCallToTagged(Call, FunctionTags::BoolInteger); } static bool isCConstant(const llvm::Value *V) { return isa(V) or isIntegerConstFormatting(V); } static std::string addAlwaysParentheses(llvm::StringRef Expr) { return std::string("(") + Expr.str() + ")"; } static std::string get128BitIntegerHexConstant(llvm::APInt Value, const ptml::PTMLCBuilder &B, const model::Binary &Model) { revng_assert(Value.getBitWidth() > 64); revng_assert(Value.getBitWidth() <= 128); using PTMLOperator = ptml::PTMLCBuilder::Operator; using model::PrimitiveTypeKind::Unsigned; model::QualifiedType U128 = model::QualifiedType(Model.getPrimitiveType(Unsigned, 16), {}); std::string Cast = addAlwaysParentheses(getTypeName(U128, B)); if (Value.isZero()) return addAlwaysParentheses(Cast + " " + B.getNumber(0)); // In C, even if you can have 128-bit variables, you cannot have 128-bit // literals, so we need this hack to assign a big constant value to a // 128-bit variable. llvm::APInt HighBits = Value.getHiBits(Value.getBitWidth() - 64); llvm::APInt LowBits = Value.getLoBits(64); bool NeedsOr = not HighBits.isZero() and not LowBits.isZero(); std::string CompositeConstant = Cast + " "; if (not HighBits.isZero()) { StringToken HighBitsString; HighBits.toString(HighBitsString, /*radix=*/16, /*signed=*/false, /*formatAsCLiteral=*/true); auto HighConst = B.getConstantTag(HighBitsString) + " " + B.getOperator(PTMLOperator::LShift) + " " + B.getNumber(64); CompositeConstant += HighConst; } if (NeedsOr) CompositeConstant += " " + B.getOperator(PTMLOperator::Or) + " "; if (not LowBits.isZero()) { StringToken LowBitsString; LowBits.toString(LowBitsString, /*radix=*/16, /*signed=*/false, /*formatAsCLiteral=*/true); CompositeConstant += B.getConstantTag(LowBitsString).serialize(); } return addAlwaysParentheses(CompositeConstant); } static std::string hexLiteral(const llvm::ConstantInt *Int, const ptml::PTMLCBuilder &B, const model::Binary &Model) { StringToken Formatted; if (Int->getBitWidth() <= 64) { Int->getValue().toString(Formatted, /*radix*/ 16, /*signed*/ false, /*formatAsCLiteral*/ true); return Formatted.str().str(); } return get128BitIntegerHexConstant(Int->getValue(), B, Model); } static std::string charLiteral(const llvm::ConstantInt *Int) { revng_assert(Int->getValue().getBitWidth() == 8); const auto LimitedValue = Int->getLimitedValue(0xffu); const auto CharValue = static_cast(LimitedValue); std::string EscapedC; llvm::raw_string_ostream EscapeCStream(EscapedC); EscapeCStream.write_escaped(std::string(&CharValue, 1)); std::string EscapedHTML; llvm::raw_string_ostream EscapeHTMLStream(EscapedHTML); llvm::printHTMLEscaped(EscapedC, EscapeHTMLStream); return llvm::formatv("'{0}'", EscapeHTMLStream.str()); } static std::string boolLiteral(const llvm::ConstantInt *Int) { revng_assert(Int->getBitWidth() == 1); if (Int->isZero()) { return "false"; } else { return "true"; } } struct CCodeGenerator { private: /// The model of the binary being analysed const Binary &Model; /// The LLVM function that is being decompiled const llvm::Function &LLVMFunction; /// The model function corresponding to LLVMFunction const model::Function &ModelFunction; /// The model prototype of ModelFunction const model::Type &ParentPrototype; /// The (combed) control flow AST const ASTTree &GHAST; /// Set of values that have a corresponding local variable which should be /// declared at the start of the function const InstrSetVec &TopScopeVariables; /// A map containing a model type for each LLVM value in the function const ModelTypesMap TypeMap; /// Where to output the decompiled C code ptml::PTMLIndentedOstream Out; ptml::PTMLCBuilder B; /// Name of the local variable used to break out of loops from within nested /// switches std::vector SwitchStateVars; FunctionMetadataCache &Cache; private: class VarNameGenerator { private: uint64_t CurVarID = 0; public: std::string nextVarName() { return "_var_" + to_string(CurVarID++); } StringToken nextSwitchStateVar() { StringToken StateVar("_break_from_loop_"); StateVar += to_string(CurVarID++); return StateVar; } }; /// Stateful generator for variable names VarNameGenerator NameGenerator; /// Keep track of the names associated with function arguments, and local /// variables. In the past it also kept track of intermediate expressions, but /// with the new design all the tokens corresponding to instructions that /// don't represent local variables are recomputed every time. TokenMapT TokenMap; private: /// Name of the local variable used to break out from loops std::string LoopStateVar; std::string LoopStateVarDeclaration; private: /// Emission of parentheses may change whether the OPRP is enabled or not bool IsOperatorPrecedenceResolutionPassEnabled = false; public: CCodeGenerator(FunctionMetadataCache &Cache, const Binary &Model, const llvm::Function &LLVMFunction, const ASTTree &GHAST, const InstrSetVec &TopScopeVariables, raw_ostream &Out, ptml::PTMLCBuilder &B) : Model(Model), LLVMFunction(LLVMFunction), ModelFunction(*llvmToModelFunction(Model, LLVMFunction)), ParentPrototype(*ModelFunction.prototype(Model).getConst()), GHAST(GHAST), TopScopeVariables(TopScopeVariables), TypeMap(initModelTypes(Cache, LLVMFunction, &ModelFunction, Model, /*PointersOnly=*/false)), Out(Out, DecompiledCCodeIndentation), B(B), SwitchStateVars(), Cache(Cache) { // TODO: don't use a global loop state variable static const char *LoopStateVarName = "_loop_state_var"; LoopStateVar = getVariableLocationReference(LoopStateVarName, ModelFunction, B); LoopStateVarDeclaration = getVariableLocationDefinition(LoopStateVarName, ModelFunction, B); if (LLVMFunction.getMetadata(ExplicitParenthesesMDName)) IsOperatorPrecedenceResolutionPassEnabled = true; } void emitFunction(bool NeedsLocalStateVar, InlineableTypesMap &StackTypes); private: /// Visit a GHAST node and all its children recursively, emitting BBs /// and control flow statements in the process. RecursiveCoroutine emitGHASTNode(const ASTNode *Node); /// Recursively build a C string representing the condition contained /// in an ExprNode (which might be composed by one or more subexpressions). /// Whenever an atomic node is encountered, the associated basic block is /// emitted on-the-fly. RecursiveCoroutine buildGHASTCondition(const ExprNode *E); /// Serialize a basic block into a series of C statements. void emitBasicBlock(const BasicBlock *BB); private: RecursiveCoroutine getToken(const llvm::Value *V) const; RecursiveCoroutine getCallToken(const llvm::CallInst *Call, const llvm::StringRef FuncName, const model::Type *Prototype) const; RecursiveCoroutine getConstantToken(const llvm::Value *V) const; RecursiveCoroutine getInstructionToken(const llvm::Instruction *I) const; RecursiveCoroutine getCustomOpcodeToken(const llvm::CallInst *C) const; RecursiveCoroutine getModelGEPToken(const llvm::CallInst *C) const; RecursiveCoroutine getIsolatedCallToken(const llvm::CallInst *C) const; RecursiveCoroutine getNonIsolatedCallToken(const llvm::CallInst *C) const; private: std::string addParentheses(llvm::StringRef Expr) const; std::string buildDerefExpr(llvm::StringRef Expr) const; std::string buildAddressExpr(llvm::StringRef Expr) const; /// Return a C string that represents a cast of \a ExprToCast to a given /// \a DestType. If no casting is needed between the two expression, the /// original expression is returned. std::string buildCastExpr(StringRef ExprToCast, const model::QualifiedType &SrcType, const model::QualifiedType &DestType) const; private: std::string createTopScopeVarDeclName(const llvm::Instruction *I) { revng_assert(isStackFrameDecl(I) or TopScopeVariables.contains(I)); revng_assert(not TokenMap.contains(I)); std::string VarName = isStackFrameDecl(I) ? std::string(StackFrameVarName) : NameGenerator.nextVarName(); TokenMap[I] = getVariableLocationReference(VarName, ModelFunction, B); return getVariableLocationDefinition(VarName, ModelFunction, B); } std::string createLocalVarDeclName(const llvm::Instruction *I) { revng_assert(isLocalVarDecl(I) or isArtificialAggregateLocalVarDecl(I) or isCallStackArgumentDecl(I)); std::string VarName = NameGenerator.nextVarName(); // This may override the entry for I, if I belongs to a "duplicated" // BasicBlock that is reachable from many paths on the GHAST. TokenMap[I] = getVariableLocationReference(VarName, ModelFunction, B); return getVariableLocationDefinition(VarName, ModelFunction, B); } std::string getVarName(const llvm::Instruction *I) const { revng_assert(isStackFrameDecl(I) or isLocalVarDecl(I) or isArtificialAggregateLocalVarDecl(I) or isCallStackArgumentDecl(I)); revng_assert(TokenMap.contains(I)); return TokenMap.at(I); }; }; std::string CCodeGenerator::addParentheses(llvm::StringRef Expr) const { if (IsOperatorPrecedenceResolutionPassEnabled) return Expr.str(); return addAlwaysParentheses(Expr); } std::string CCodeGenerator::buildDerefExpr(llvm::StringRef Expr) const { using PTMLOperator = ptml::PTMLCBuilder::Operator; return B.getOperator(PTMLOperator::PointerDereference) + addParentheses(Expr); } std::string CCodeGenerator::buildAddressExpr(llvm::StringRef Expr) const { return B.getOperator(ptml::PTMLCBuilder::Operator::AddressOf) + addParentheses(Expr); } std::string CCodeGenerator::buildCastExpr(StringRef ExprToCast, const model::QualifiedType &SrcType, const model::QualifiedType &DestType) const { if (SrcType == DestType or SrcType.UnqualifiedType().empty() or DestType.UnqualifiedType().empty()) return ExprToCast.str(); revng_assert((SrcType.isScalar() or SrcType.isPointer()) and (DestType.isScalar() or DestType.isPointer())); return addAlwaysParentheses(getTypeName(DestType, B)) + " " + addParentheses(ExprToCast); } static std::string getUndefToken(model::QualifiedType UndefType, const ptml::PTMLCBuilder &B) { UndefType = peelConstAndTypedefs(UndefType); revng_assert(UndefType.isPrimitive()); revng_assert(UndefType.Qualifiers().empty()); std::string Result = "_undef_"; Result += UndefType.UnqualifiedType().getConst()->name().str().str() + "()"; return Result; } static std::string getFormattedIntegerToken(const llvm::CallInst *Call, const ptml::PTMLCBuilder &B, const model::Binary &Model) { if (isCallToTagged(Call, FunctionTags::HexInteger)) { const auto Operand = Call->getArgOperand(0); const auto *Value = cast(Operand); return B.getConstantTag(hexLiteral(Value, B, Model)).serialize(); } if (isCallToTagged(Call, FunctionTags::CharInteger)) { const auto Operand = Call->getArgOperand(0); const auto *Value = cast(Operand); return B.getConstantTag(charLiteral(Value)).serialize(); } if (isCallToTagged(Call, FunctionTags::BoolInteger)) { const auto Operand = Call->getArgOperand(0); const auto *Value = cast(Operand); return B.getConstantTag(boolLiteral(Value)).serialize(); } std::string Error = "Cannot get token for custom opcode: " + dumpToString(Call); revng_abort(Error.c_str()); return ""; } RecursiveCoroutine CCodeGenerator::getConstantToken(const llvm::Value *C) const { revng_assert(isCConstant(C)); if (auto *Undef = dyn_cast(C)) rc_return getUndefToken(TypeMap.at(Undef), B); if (auto *Null = dyn_cast(C)) rc_return B.getNullTag().serialize(); if (auto *Const = dyn_cast(C)) { llvm::APInt Value = Const->getValue(); if (Value.isIntN(64)) rc_return B.getNumber(Value).serialize(); else rc_return get128BitIntegerHexConstant(Value, B, Model); } if (auto *Global = dyn_cast(C)) { using namespace llvm; // Check if initializer is a CString auto *Initializer = Global->getInitializer(); StringRef Content = ""; if (auto StringInit = dyn_cast(Initializer)) { // If it's not a C string, bail out if (not StringInit->isCString()) revng_abort(dumpToString(Global).c_str()); // If it's a C string, Drop the terminator Content = StringInit->getAsString().drop_back(); } else { // Zero initializers are always valid c empty strings, in all the // other cases, bail out if (not isa(Initializer)) revng_abort(dumpToString(Global).c_str()); } std::string Escaped; { raw_string_ostream Stream(Escaped); Stream << "\""; Stream.write_escaped(Content); Stream << "\""; } rc_return Escaped; } if (auto *ConstExpr = dyn_cast(C)) { switch (ConstExpr->getOpcode()) { case Instruction::IntToPtr: { const auto *Operand = cast(ConstExpr->getOperand(0)); const QualifiedType &SrcType = TypeMap.at(Operand); const QualifiedType &DstType = TypeMap.at(ConstExpr); // IntToPtr has no effect on values that we already know to be pointers if (SrcType.isPointer()) rc_return rc_recur getConstantToken(Operand); else rc_return buildCastExpr(rc_recur getConstantToken(Operand), SrcType, DstType); } break; default: revng_abort(dumpToString(ConstExpr).c_str()); } } if (isIntegerConstFormatting(C)) rc_return getFormattedIntegerToken(cast(C), B, Model); std::string Error = "Cannot get token for llvm::Constant: "; Error += dumpToString(C).c_str(); revng_abort(Error.c_str()); rc_return ""; } /// Traverse all nested typedefs inside \a QT, skipping const Qualifiers, and /// returns a QualifiedType that represents the full traversal. static RecursiveCoroutine flattenTypedefsIgnoringConst(const QualifiedType &QT) { QualifiedType Result = peelConstAndTypedefs(QT); if (auto *TD = dyn_cast(Result.UnqualifiedType().getConst())) { auto &Underlying = TD->UnderlyingType(); QualifiedType Nested = rc_recur flattenTypedefsIgnoringConst(Underlying); Result.UnqualifiedType() = Nested.UnqualifiedType(); llvm::move(Nested.Qualifiers(), std::back_inserter(Result.Qualifiers())); } rc_return Result; } RecursiveCoroutine CCodeGenerator::getModelGEPToken(const llvm::CallInst *Call) const { revng_assert(isCallToTagged(Call, FunctionTags::ModelGEP) or isCallToTagged(Call, FunctionTags::ModelGEPRef)); revng_assert(Call->arg_size() >= 2); bool IsRef = isCallToTagged(Call, FunctionTags::ModelGEPRef); // First argument is a string containing the base type auto *CurArg = Call->arg_begin(); QualifiedType CurType = deserializeFromLLVMString(CurArg->get(), Model); // Second argument is the base llvm::Value ++CurArg; llvm::Value *BaseValue = CurArg->get(); std::string BaseString = rc_recur getToken(BaseValue); bool UseArrow = false; if (IsRef) { // In ModelGEPRefs, the base value is a reference, and the base type is // its type revng_assert(TypeMap.at(BaseValue) == CurType, "The ModelGEP base type is not coherent with the " "propagated type."); // If there are no further arguments we're just dereferencing the base value if (std::next(CurArg) == Call->arg_end()) { // But dereferencing a reference does not produce any code so we're done rc_return BaseString; } } else { // In ModelGEPs, the base value is a pointer, and the base type is the // type pointed by the base value QualifiedType PointerQt = CurType.getPointerTo(Model.Architecture()); revng_assert(TypeMap.at(BaseValue) == PointerQt, "The ModelGEP base type is not coherent with the " "propagated type."); auto *ThirdArgument = Call->getArgOperand(2); auto *ConstantArrayIndex = dyn_cast(ThirdArgument); // Check if the ModelGEP represents an additional access with square // brackets on the pointer bool HasInitialArrayAccess = not ConstantArrayIndex or not ConstantArrayIndex->isZero(); // If this doesn't have any variadic argument just dereference the base // pointer and we're done. if (Call->arg_size() < 4) { // There are actually various ways to do it. // If we're not using square brackets to dereference the pointer, we just // emit a dereference expression. if (not HasInitialArrayAccess) rc_return buildDerefExpr(BaseString); // Here we have square brackets, that effectively replace the dereference // operator, so we just emit the square brackets with the appropriate // index. std::string IndexExpr; if (auto *Const = dyn_cast(ThirdArgument)) { IndexExpr = B.getNumber(Const->getValue()).serialize(); } else { IndexExpr = rc_recur getToken(ThirdArgument); } rc_return BaseString + "[" + IndexExpr + "]"; } // Here we know that there is at least one variadic argument. if (HasInitialArrayAccess) { // If we're using the square brackets to dereference the base pointer we // have to change the base type so that it represents the "fake" array // being accessed. // We make it with only 1 element because in the following the number of // elements of the array is not actually used for generating the C code, // so we can get away with it. auto LongArray = model::Qualifier::createArray(1); PointerQt.Qualifiers().front() = std::move(LongArray); CurType = PointerQt; } else { // Otherwise, we're not accessing the base pointer as an array. // So we can skip an additional argument. ++CurArg; // But the base type could still be an array. if (CurType.isArray()) { // If the base type is an array the first level of indirection will be // represented by square brackets that want to access elements of the // array. So we have to first dereference the pointer-to-array in order // to be able to access elements via [] in C. BaseString = "(" + buildDerefExpr(BaseString) + ")"; } else { // If CurType is not an array we're going to represent the first level // of the traversal with the `->` operator rather than `.`, so let's // take note of this fact. UseArrow = true; } } } ++CurArg; std::string CurExpr = addParentheses(BaseString); using PTMLOperator = ptml::PTMLCBuilder::Operator; Tag Deref = UseArrow ? B.getOperator(PTMLOperator::Arrow) : B.getOperator(PTMLOperator::Dot); // Traverse the model to decide whether to emit "." or "[]" for (; CurArg != Call->arg_end(); ++CurArg) { CurType = flattenTypedefsIgnoringConst(CurType); auto &Qualifiers = CurType.Qualifiers(); if (not Qualifiers.empty()) { // If it's an array or a pointer, add "[]" // Get the ArrayQualifier out, and drop it. model::Qualifier ArrayQualifier = Qualifiers.front(); revng_assert(model::Qualifier::isArray(ArrayQualifier)); Qualifiers.erase(Qualifiers.begin()); std::string IndexExpr; if (auto *Const = dyn_cast(CurArg->get())) { IndexExpr = B.getNumber(Const->getValue()).serialize(); } else { IndexExpr = rc_recur getToken(CurArg->get()); } CurExpr += "[" + IndexExpr + "]"; } else { // If it's a struct or union, we can only navigate it with fixed // indexes. // TODO: decide how to emit constants auto *FieldIdxConst = cast(CurArg->get()); uint64_t FieldIdx = FieldIdxConst->getValue().getLimitedValue(); CurExpr += Deref.serialize(); // Find the field name const auto *UnqualType = CurType.UnqualifiedType().getConst(); if (auto *Struct = dyn_cast(UnqualType)) { const model::StructField &Field = Struct->Fields().at(FieldIdx); CurExpr += B.getLocationReference(*Struct, Field); CurType = Struct->Fields().at(FieldIdx).Type(); } else if (auto *Union = dyn_cast(UnqualType)) { const model::UnionField &Field = Union->Fields().at(FieldIdx); CurExpr += B.getLocationReference(*Union, Field); CurType = Union->Fields().at(FieldIdx).Type(); } else { CurType.dump(); revng_abort("Unexpected ModelGEP type found: "); } } // Regardless if the base type was a pointer or not, we are now // navigating only references Deref = B.getOperator(PTMLOperator::Dot); } rc_return CurExpr; } RecursiveCoroutine CCodeGenerator::getCustomOpcodeToken(const llvm::CallInst *Call) const { if (isAssignment(Call)) { const llvm::Value *StoredVal = Call->getArgOperand(0); const llvm::Value *PointerVal = Call->getArgOperand(1); rc_return rc_recur getToken(PointerVal) + " " + B.getOperator(ptml::PTMLCBuilder::Operator::Assign) + " " + rc_recur getToken(StoredVal); } if (isCallToTagged(Call, FunctionTags::Copy)) rc_return rc_recur getToken(Call->getArgOperand(0)); if (isCallToTagged(Call, FunctionTags::ModelGEP) or isCallToTagged(Call, FunctionTags::ModelGEPRef)) rc_return rc_recur getModelGEPToken(Call); if (isCallToTagged(Call, FunctionTags::ModelCast)) { // First argument is a string containing the base type auto *CurArg = Call->arg_begin(); QualifiedType CurType = deserializeFromLLVMString(CurArg->get(), Model); // Second argument is the base llvm::Value ++CurArg; llvm::Value *BaseValue = CurArg->get(); // Emit the parenthesized cast expr, and we are done std::string StringToCast = rc_recur getToken(BaseValue); rc_return buildCastExpr(StringToCast, TypeMap.at(BaseValue), CurType); } if (isCallToTagged(Call, FunctionTags::AddressOf)) { // First operand is the type of the value being addressed (should not // introduce casts) QualifiedType ArgType = deserializeFromLLVMString(Call->getArgOperand(0), Model); // Second argument is the value being addressed llvm::Value *Arg = Call->getArgOperand(1); revng_assert(ArgType == TypeMap.at(Arg)); std::string ArgString = rc_recur getToken(Arg); rc_return buildAddressExpr(ArgString); } if (isCallToTagged(Call, FunctionTags::Parentheses)) { std::string Operand0 = rc_recur getToken(Call->getArgOperand(0)); rc_return addAlwaysParentheses(Operand0); } if (isCallToTagged(Call, FunctionTags::StructInitializer)) { // Struct initializers should be used only to pack together return // values of RawFunctionTypes that return multiple values, therefore // they must have the same type as the function's return type auto *StructTy = cast(Call->getType()); revng_assert(Call->getFunction()->getReturnType() == StructTy); revng_assert(LLVMFunction.getReturnType() == StructTy); auto StrucTypeName = getNamedInstanceOfReturnType(ParentPrototype, "", B); std::string StructInit = addAlwaysParentheses(StrucTypeName); // Emit RHS llvm::StringRef Separator = " {"; for (const auto &Arg : Call->args()) { StructInit += Separator.str() + " " + rc_recur getToken(Arg); Separator = ","; } StructInit += " }"; rc_return StructInit; } if (isCallToTagged(Call, FunctionTags::OpaqueExtractValue)) { const llvm::Value *AggregateOp = Call->getArgOperand(0); const auto *Idx = llvm::cast(Call->getArgOperand(1)); const auto *CallReturnsStruct = llvm::cast(AggregateOp); const llvm::Function *Callee = CallReturnsStruct->getCalledFunction(); const auto CalleePrototype = Cache.getCallSitePrototype(Model, CallReturnsStruct); std::string StructFieldRef; if (CalleePrototype.empty()) { // The call returning a struct is a call to a helper function. // It must be a direct call. revng_assert(Callee); StructFieldRef = getReturnStructFieldLocationReference(Callee, Idx ->getZExtValue(), B); } else { const model::Type *CalleeType = CalleePrototype.getConst(); StructFieldRef = getReturnField(*CalleeType, Idx->getZExtValue(), Model) .str() .str(); } rc_return rc_recur getToken(AggregateOp) + "." + StructFieldRef; } if (isCallToTagged(Call, FunctionTags::SegmentRef)) { auto *Callee = Call->getCalledFunction(); const auto &[StartAddress, VirtualSize] = extractSegmentKeyFromMetadata(*Callee); model::Segment Segment = Model.Segments().at({ StartAddress, VirtualSize }); auto Name = Segment.name(); rc_return B.getLocationReference(Segment); } if (isCallToTagged(Call, FunctionTags::Copy)) rc_return rc_recur getToken(Call->getArgOperand(0)); if (isCallToTagged(Call, FunctionTags::OpaqueCSVValue)) { auto *Callee = Call->getCalledFunction(); std::string HelperRef = getHelperFunctionLocationReference(Callee, B); rc_return rc_recur getCallToken(Call, HelperRef, /*prototype=*/nullptr); } using PTMLOperator = ptml::PTMLCBuilder::Operator; if (isCallToTagged(Call, FunctionTags::UnaryMinus)) { auto Operand = Call->getArgOperand(0); std::string ToNegate = rc_recur getToken(Operand); rc_return B.getOperator(PTMLOperator::UnaryMinus) + ToNegate; } if (isCallToTagged(Call, FunctionTags::BinaryNot)) { auto Operand = Call->getArgOperand(0); std::string ToNegate = rc_recur getToken(Operand); rc_return(Operand->getType()->isIntegerTy(1) ? B.getOperator(PTMLOperator::BoolNot) : B.getOperator(PTMLOperator::BinaryNot)) + ToNegate; } if (isCallToTagged(Call, FunctionTags::BooleanNot)) { auto Operand = Call->getArgOperand(0); std::string ToNegate = rc_recur getToken(Operand); rc_return B.getOperator(PTMLOperator::BoolNot) + ToNegate; } if (isCallToTagged(Call, FunctionTags::StringLiteral)) { const auto Operand = Call->getArgOperand(0); std::string StringLiteral = rc_recur getToken(Operand); std::string EscapedHTML; { llvm::raw_string_ostream EscapeHTMLStream(EscapedHTML); llvm::printHTMLEscaped(StringLiteral, EscapeHTMLStream); } rc_return B.getStringLiteral(EscapedHTML).serialize(); } std::string Error = "Cannot get token for custom opcode: " + dumpToString(Call); revng_abort(Error.c_str()); rc_return ""; } RecursiveCoroutine CCodeGenerator::getIsolatedCallToken(const llvm::CallInst *Call) const { // Retrieve the CallEdge const auto &[CallEdge, _] = Cache.getCallEdge(Model, Call); revng_assert(CallEdge); const auto &PrototypePath = Cache.getCallSitePrototype(Model, Call); // Construct the callee token (can be a function name or a function // pointer) std::string CalleeToken; if (not isa(Call->getCalledOperand())) { std::string CalledString = rc_recur getToken(Call->getCalledOperand()); CalleeToken = addParentheses(CalledString); } else { if (not CallEdge->DynamicFunction().empty()) { // Dynamic Function auto &DynFuncID = CallEdge->DynamicFunction(); auto &DynamicFunc = Model.ImportedDynamicFunctions().at(DynFuncID); std::string Location = serializedLocation(ranks::DynamicFunction, DynamicFunc.key()); CalleeToken = B.getTag(ptml::tags::Span, DynamicFunc.name().str()) .addAttribute(attributes::Token, tokens::Function) .addAttribute(attributes::ModelEditPath, model::editPath::customName(DynamicFunc)) .addAttribute(attributes::LocationReferences, Location) .serialize(); } else { // Isolated function llvm::Function *CalledFunc = Call->getCalledFunction(); revng_assert(CalledFunc); const model::Function *ModelFunc = llvmToModelFunction(Model, *CalledFunc); revng_assert(ModelFunc); CalleeToken = B.getTag(ptml::tags::Span, ModelFunc->name().str()) .addAttribute(attributes::Token, tokens::Function) .addAttribute(attributes::ModelEditPath, model::editPath::customName(*ModelFunc)) .addAttribute(attributes::LocationReferences, serializedLocation(ranks::Function, ModelFunc->key())) .serialize(); } } // Build the call expression revng_assert(not CalleeToken.empty()); auto *Prototype = PrototypePath.get(); rc_return rc_recur getCallToken(Call, CalleeToken, Prototype); } RecursiveCoroutine CCodeGenerator::getNonIsolatedCallToken(const llvm::CallInst *Call) const { auto *CalledFunc = Call->getCalledFunction(); revng_assert(CalledFunc and CalledFunc->hasName(), "Special functions should all have a name"); std::string HelperRef = getHelperFunctionLocationReference(CalledFunc, B); rc_return rc_recur getCallToken(Call, HelperRef, /*prototype=*/nullptr); } static bool shouldGenerateDebugInfoAsPTML(const llvm::Instruction &I) { if (!I.getDebugLoc() || !I.getDebugLoc()->getScope()) return false; // If the next instruction in the BB has different DebugLoc, generate the // PTML location now. auto NextInstr = std::next(I.getIterator()); if (NextInstr == I.getParent()->end() || !NextInstr->getDebugLoc() || NextInstr->getDebugLoc() != I.getDebugLoc()) return true; return false; } static std::string addDebugInfo(const llvm::Instruction *I, const std::string &Str, const ptml::PTMLCBuilder &B) { if (shouldGenerateDebugInfoAsPTML(*I)) return B.getTag(ptml::tags::Span, Str) .addAttribute(ptml::attributes::LocationReferences, I->getDebugLoc()->getScope()->getName()) .addAttribute(ptml::attributes::ScopeLocation, I->getDebugLoc()->getScope()->getName()) .serialize(); return Str; } /// Return the string that represents the given binary operator in C static const std::string getBinOpString(const llvm::BinaryOperator *BinOp, const ptml::PTMLCBuilder &B) { const Tag Op = [&BinOp, &B]() { bool IsBool = BinOp->getType()->isIntegerTy(1); using PTMLOperator = ptml::PTMLCBuilder::Operator; switch (BinOp->getOpcode()) { case Instruction::Add: return B.getOperator(ptml::PTMLCBuilder::Operator::Add); case Instruction::Sub: return B.getOperator(ptml::PTMLCBuilder::Operator::Sub); case Instruction::Mul: return B.getOperator(ptml::PTMLCBuilder::Operator::Mul); case Instruction::SDiv: case Instruction::UDiv: return B.getOperator(ptml::PTMLCBuilder::Operator::Div); case Instruction::SRem: case Instruction::URem: return B.getOperator(ptml::PTMLCBuilder::Operator::Modulo); case Instruction::LShr: case Instruction::AShr: return B.getOperator(ptml::PTMLCBuilder::Operator::RShift); case Instruction::Shl: return B.getOperator(ptml::PTMLCBuilder::Operator::LShift); case Instruction::And: return IsBool ? B.getOperator(PTMLOperator::BoolAnd) : B.getOperator(ptml::PTMLCBuilder::Operator::And); case Instruction::Or: return IsBool ? B.getOperator(PTMLOperator::BoolOr) : B.getOperator(ptml::PTMLCBuilder::Operator::Or); case Instruction::Xor: return B.getOperator(ptml::PTMLCBuilder::Operator::Xor); default: revng_abort("Unknown const Binary operation"); } }(); return " " + Op + " "; } /// Return the string that represents the given comparison operator in C static const std::string getCmpOpString(const llvm::CmpInst::Predicate &Pred, const ptml::PTMLCBuilder &B) { using llvm::CmpInst; const Tag Op = [&Pred, &B]() { switch (Pred) { case CmpInst::ICMP_EQ: ///< equal return B.getOperator(ptml::PTMLCBuilder::Operator::CmpEq); case CmpInst::ICMP_NE: ///< not equal return B.getOperator(ptml::PTMLCBuilder::Operator::CmpNeq); case CmpInst::ICMP_UGT: ///< unsigned greater than case CmpInst::ICMP_SGT: ///< signed greater than return B.getOperator(ptml::PTMLCBuilder::Operator::CmpGt); case CmpInst::ICMP_UGE: ///< unsigned greater or equal case CmpInst::ICMP_SGE: ///< signed greater or equal return B.getOperator(ptml::PTMLCBuilder::Operator::CmpGte); case CmpInst::ICMP_ULT: ///< unsigned less than case CmpInst::ICMP_SLT: ///< signed less than return B.getOperator(ptml::PTMLCBuilder::Operator::CmpLt); case CmpInst::ICMP_ULE: ///< unsigned less or equal case CmpInst::ICMP_SLE: ///< signed less or equal return B.getOperator(ptml::PTMLCBuilder::Operator::CmpLte); default: revng_abort("Unknown comparison operator"); } }(); return " " + Op + " "; } /// Returns a pair of QualifiedTypes to which LHS and RHS has to be casted to /// for enabling an == or != comparison in C while preserving semantic. static std::pair getCastTargetTypesForEqualityComparisons(model::QualifiedType LHS, model::QualifiedType RHS) { revng_assert(LHS.isScalar() and RHS.isScalar()); revng_assert(not LHS.isFloat() and not RHS.isFloat()); revng_assert(*LHS.size() == *RHS.size()); // If they are the same we don't have to cast anything. if (LHS == RHS) return { std::move(LHS), std::move(RHS) }; // If they are both pointer we don't have to cast anything. // This could cause UB in case of strict-aliasing, but that's not something // that we're trying to guarantee in decompiled code. if (LHS.isPointer() and RHS.isPointer()) return { std::move(LHS), std::move(RHS) }; // In case only one is a pointer, given that they both have the same size, we // can always cast the non-pointer to the pointer-type. if (bool LHSIsPointer = LHS.isPointer(); LHSIsPointer != RHS.isPointer()) { model::QualifiedType &Pointer = LHSIsPointer ? LHS : RHS; return { Pointer, Pointer }; } // At this point we have 2 non-pointer scalar types. // Given that we've ruled out Float by assertions, we can just leave them as // they are. // Even if they mismatch, they have the same size, and in C we'll get an // implicit reinterpret cast. This might raise some warning, but we'll deal // with those. // TODO: this is definitely sloppy, but doing the right thing would require to // really think thoroughly about what's the best way to treat casts in // general, and we haven't done it yet. // At the moment some casts are emitted as ModelCast on the IR others are // emitted on the fly during c-code-generation. Until we don't solve that // problem systematically, this is a sloppy solution to prevent proliferation // of casts, trading off the fact of not having warnings. So in practice this // works at the cost of disabling more warnings on decompiled C code. Once // we've solved this properly the warning can be re-enabled. return { std::move(LHS), std::move(RHS) }; } RecursiveCoroutine CCodeGenerator::getInstructionToken(const llvm::Instruction *I) const { if (isa(I) or isa(I)) { const llvm::Value *Op0 = I->getOperand(0); const llvm::Value *Op1 = I->getOperand(1); std::string Op0Token = rc_recur getToken(Op0); std::string Op1Token = rc_recur getToken(Op1); const QualifiedType &OpType0 = TypeMap.at(Op0); const QualifiedType &OpType1 = TypeMap.at(Op1); revng_assert(OpType0.isScalar() and OpType1.isScalar()); revng_assert(*OpType0.size() == *OpType1.size()); uint64_t ByteSize = *OpType0.size(); if (auto *ICmp = dyn_cast(I)) { revng_assert(not OpType0.isFloat() and not OpType1.isFloat()); if (ICmp->isEquality()) { // Cast the two operands to a same common type for equality comparison. const auto &[TargetOp0Type, TargetOp1Type] = getCastTargetTypesForEqualityComparisons(OpType0, OpType1); Op0Token = buildCastExpr(Op0Token, OpType0, TargetOp0Type); Op1Token = buildCastExpr(Op1Token, OpType1, TargetOp0Type); } else { // If we're not doing eq or neq, we have to make sure that the // signedness is compatible, otherwise it would break semantics. using model::PrimitiveTypeKind::Signed; using model::PrimitiveTypeKind::Unsigned; auto ICmpKind = ICmp->isSigned() ? Signed : Unsigned; auto TargetType = model::QualifiedType(Model.getPrimitiveType(ICmpKind, ByteSize), {}); if (OpType0.isPointer()) { Op0Token = buildCastExpr(Op0Token, OpType0, TargetType); } else { const model::Type *TheType = peelConstAndTypedefs(OpType0) .UnqualifiedType() .getConst(); revng_assert(isa(TheType) or isa(TheType)); const auto *Primitive = dyn_cast(TheType); if (nullptr == Primitive) { const auto *Enum = cast(TheType); const auto *Underlying = Enum->UnderlyingType().UnqualifiedType().getConst(); Primitive = cast(Underlying); } auto CurrentKind = Primitive->PrimitiveKind(); if (ICmpKind == Signed and CurrentKind != Signed) Op0Token = buildCastExpr(Op0Token, OpType0, TargetType); if (ICmpKind == Unsigned and CurrentKind == Signed) Op0Token = buildCastExpr(Op0Token, OpType0, TargetType); } if (OpType1.isPointer()) { Op1Token = buildCastExpr(Op1Token, OpType1, TargetType); } else { const model::Type *TheType = peelConstAndTypedefs(OpType1) .UnqualifiedType() .getConst(); const auto *Primitive = cast(TheType); auto CurrentKind = Primitive->PrimitiveKind(); if (ICmpKind == Signed and CurrentKind != Signed) Op1Token = buildCastExpr(Op1Token, OpType1, TargetType); if (ICmpKind == Unsigned and CurrentKind == Signed) Op1Token = buildCastExpr(Op1Token, OpType1, TargetType); } } } else { const QualifiedType &ResultType = TypeMap.at(I); Op0Token = buildCastExpr(Op0Token, OpType0, ResultType); Op1Token = buildCastExpr(Op1Token, OpType1, ResultType); } auto *Bin = dyn_cast(I); auto *Cmp = dyn_cast(I); revng_assert(Bin or Cmp); auto OperatorString = Bin ? getBinOpString(Bin, B) : getCmpOpString(Cmp->getPredicate(), B); // TODO: Integer promotion rc_return addDebugInfo(I, addParentheses(Op0Token) + OperatorString + addParentheses(Op1Token), B); } if (isa(I) or isa(I)) { const llvm::Value *Op = I->getOperand(0); std::string ToCast = rc_recur getToken(Op); rc_return addDebugInfo(I, buildCastExpr(ToCast, TypeMap.at(Op), TypeMap.at(I)), B); } switch (I->getOpcode()) { case llvm::Instruction::Call: { auto *Call = cast(I); revng_assert(isCallToCustomOpcode(Call) or isCallToIsolatedFunction(Call) or isCallToNonIsolated(Call)); if (isCallToCustomOpcode(Call)) rc_return addDebugInfo(I, rc_recur getCustomOpcodeToken(Call), B); if (isCallToIsolatedFunction(Call)) rc_return addDebugInfo(I, rc_recur getIsolatedCallToken(Call), B); if (isCallToNonIsolated(Call)) rc_return addDebugInfo(I, rc_recur getNonIsolatedCallToken(Call), B); std::string Error = "Cannot get token for CallInst: " + dumpToString(Call); revng_abort(Error.c_str()); rc_return ""; } break; case llvm::Instruction::Ret: { std::string Result = B.getKeyword(ptml::PTMLCBuilder::Keyword::Return) .serialize(); if (auto *Ret = llvm::cast(I); llvm::Value *ReturnedVal = Ret->getReturnValue()) Result += " " + rc_recur getToken(ReturnedVal); rc_return addDebugInfo(I, Result, B); } break; case llvm::Instruction::Unreachable: rc_return addDebugInfo(I, "__builtin_trap()", B); case llvm::Instruction::Select: { auto *Select = llvm::cast(I); std::string Condition = rc_recur getToken(Select->getCondition()); const llvm::Value *Op1 = Select->getOperand(1); const llvm::Value *Op2 = Select->getOperand(2); std::string Op1String = rc_recur getToken(Op1); std::string Op1Token = buildCastExpr(Op1String, TypeMap.at(Op1), TypeMap.at(Select)); std::string Op2String = rc_recur getToken(Op2); std::string Op2Token = buildCastExpr(Op2String, TypeMap.at(Op2), TypeMap.at(Select)); rc_return addDebugInfo(I, addParentheses(Condition) + " ? " + addParentheses(Op1Token) + " : " + addParentheses(Op2Token), B); } break; default: { std::string Error = "Cannot getToken for llvm::Instruction: " + dumpToString(I); revng_abort(Error.c_str()); } } std::string Error = "Cannot getToken for llvm::Instruction: " + dumpToString(I); revng_abort(Error.c_str()); rc_return ""; } RecursiveCoroutine CCodeGenerator::getToken(const llvm::Value *V) const { revng_log(Log, "getToken(): " << dumpToString(V)); LoggerIndent Indent{ Log }; // If we already have a variable name for this, return it. auto It = TokenMap.find(V); if (It != TokenMap.end()) { revng_assert(isa(V) or isStackFrameDecl(V) or isCallStackArgumentDecl(V) or isLocalVarDecl(V) or isArtificialAggregateLocalVarDecl(V)); revng_log(Log, "Found!"); rc_return It->second; } // We should always have names for stuff that is expected to have a name. revng_assert(not isa(V) and not isStackFrameDecl(V) and not isCallStackArgumentDecl(V) and not isLocalVarDecl(V)); if (isCConstant(V)) rc_return rc_recur getConstantToken(V); if (auto *I = dyn_cast(V)) rc_return rc_recur getInstructionToken(I); std::string Error = "Cannot get token for llvm::Value: "; Error += dumpToString(V).c_str(); revng_abort(Error.c_str()); rc_return ""; } RecursiveCoroutine CCodeGenerator::getCallToken(const llvm::CallInst *Call, const llvm::StringRef FuncName, const model::Type *Prototype) const { std::string Expression = FuncName.str(); if (Call->arg_size() == 0) { Expression += "()"; } else { llvm::StringRef Separator = "("; for (const auto &Arg : Call->args()) { Expression += Separator.str() + rc_recur getToken(Arg); Separator = ", "; } Expression += ')'; } rc_return Expression; } static bool isStatement(const llvm::Instruction *I) { // Return are statements if (isa(I)) return true; // Instructions that are not calls are never statement. auto *Call = dyn_cast(I); if (not Call) return false; // Calls to Assign and LocalVariable are statemements. if (isAssignment(Call) or isLocalVarDecl(Call)) return true; // Calls to isolated functions that require a local variable of artificial // aggregate type (that is not on the model) are statemements. if (isArtificialAggregateLocalVarDecl(Call)) return true; // If the call returns an aggregate, and it needs a top scope declaration, we // have to handle it as if it was an assignment to the local variable declared // in the top scope declaration. // This is due to the fact that AddAssignmentMarkerPass cannot really inject // LocalVariables and Assign/Copy for stuff that has aggregate type on the // LLVM IR (because those types are not on the model), so we need to handle it // now. if (Call->getType()->isAggregateType() and needsTopScopeDeclaration(*Call)) return true; // Calls to isolated functions and helpers that return void are statements. // If they don't return void, they are not statements. They are expressions // that will be assigned to some local variables in some other assign // statements. if (isCallToIsolatedFunction(Call) or isCallToNonIsolated(Call)) return Call->getType()->isVoidTy(); // Stack frame declarations and call stack arguments declarations are // statements. if (isStackFrameDecl(Call) or isCallStackArgumentDecl(Call)) return true; return false; } void CCodeGenerator::emitBasicBlock(const llvm::BasicBlock *BB) { LoggerIndent Indent{ VisitLog }; revng_log(VisitLog, "|__ Visiting BB " << BB->getName()); LoggerIndent MoreIndent{ VisitLog }; revng_log(Log, "--------- BB " << BB->getName()); for (const Instruction &I : *BB) { revng_log(Log, "Analyzing: " << dumpToString(I)); auto *Call = dyn_cast(&I); if (not isStatement(&I)) { revng_log(Log, "Ignoring: non-statement instruction"); } else if (I.getType()->isVoidTy()) { revng_assert(isa(I) or isCallToIsolatedFunction(&I) or isCallToNonIsolated(&I) or isAssignment(&I)); Out << getToken(&I) << ";\n"; } else if (isLocalVarDecl(Call) or isCallStackArgumentDecl(Call)) { // Emit missing local variable declarations std::string VarName = createLocalVarDeclName(Call); revng_assert(not VarName.empty()); Out << getNamedCInstance(TypeMap.at(Call), VarName, B) << ";\n"; } else if (isStackFrameDecl(Call)) { // Stack frame declaration is a statement, but we've handled explicitly // to emit it as the first declaration in this function. So we just // assert and go to the next instruction. revng_assert(TokenMap.contains(Call)); } else if (bool IsTopScopeVariable = TopScopeVariables.contains(Call); IsTopScopeVariable or isArtificialAggregateLocalVarDecl(Call)) { // This is a call but it actually needs an assignment to the associated // variable. The variable has not been declared in the IR with // LocalVariable, because LocalVariable needs a model type, and aggregates // types on the LLVM IR are not on the model. revng_assert(Call->getType()->isAggregateType()); if (not IsTopScopeVariable) { // Create missing local variable declarations std::string VarName = createLocalVarDeclName(Call); const auto &Prototype = Cache.getCallSitePrototype(Model, Call); revng_assert(Prototype.isValid() and not Prototype.empty()); const auto *FunctionType = Prototype.getConst(); Out << getNamedInstanceOfReturnType(*FunctionType, VarName, B) << ";\n"; } std::string VarName = getVarName(Call); revng_assert(not VarName.empty()); // Get the token. If the Call is a call to an isolated function that // returns an aggregate we want to get the token of the call, not of the // local variable. For all the other cases we can just get the regular // token. std::string RHSExpression = isArtificialAggregateLocalVarDecl(Call) ? getIsolatedCallToken(Call) : getToken(Call); // Assign to the local variable Out << VarName << " " << B.getOperator(ptml::PTMLCBuilder::Operator::Assign) << " " << std::move(RHSExpression) << ";\n"; } else { std::string Error = "Cannot emit statement: "; Error += dumpToString(Call).c_str(); revng_abort(Error.c_str()); } if (Call != nullptr and isCallToIsolatedFunction(Call)) { const auto &[CallEdge, _] = Cache.getCallEdge(Model, Call); if (CallEdge->hasAttribute(Model, model::FunctionAttribute::NoReturn)) Out << "// The previous function call does not return\n"; } } } RecursiveCoroutine CCodeGenerator::buildGHASTCondition(const ExprNode *E) { LoggerIndent Indent{ VisitLog }; revng_log(VisitLog, "|__ Visiting Condition " << E); LoggerIndent MoreIndent{ VisitLog }; using NodeKind = ExprNode::NodeKind; switch (E->getKind()) { case NodeKind::NK_Atomic: { revng_log(VisitLog, "(atomic)"); // An atomic node holds a reference to the Basic Block that contains the // condition used in the conditional expression. In particular, the // condition is the value used in the last expression of the basic // block. // First, emit the BB const AtomicNode *Atomic = cast(E); llvm::BasicBlock *BB = Atomic->getConditionalBasicBlock(); revng_assert(BB); emitBasicBlock(BB); // Then, extract the token of the last instruction (must be a // conditional branch instruction) llvm::Instruction *CondTerminator = BB->getTerminator(); llvm::BranchInst *Br = cast(CondTerminator); revng_assert(Br->isConditional()); // Emit code for x != 0 case with cast. auto *I = dyn_cast(Br->getCondition()); if (I) { auto *Cmp = dyn_cast(I); const llvm::Value *Op1 = I->getOperand(1); if (Cmp and Cmp->getPredicate() == llvm::CmpInst::ICMP_NE and dyn_cast(Op1) and dyn_cast(Op1)->isZeroValue()) { const llvm::Value *Op0 = I->getOperand(0); std::string Op0String = rc_recur getToken(Op0); model::QualifiedType BoolTy; using model::PrimitiveTypeKind::Unsigned; BoolTy.UnqualifiedType() = Model.getPrimitiveType(Unsigned, 1); rc_return addDebugInfo(I, buildCastExpr(Op0String, TypeMap.at(Op0), BoolTy), B); } } rc_return rc_recur getToken(Br->getCondition()); } break; case NodeKind::NK_Not: { revng_log(VisitLog, "(not)"); const NotNode *N = cast(E); ExprNode *Negated = N->getNegatedNode(); rc_return B.getOperator(ptml::PTMLCBuilder::Operator::BoolNot) + addAlwaysParentheses(rc_recur buildGHASTCondition(Negated)); } break; case NodeKind::NK_And: case NodeKind::NK_Or: { revng_log(VisitLog, "(and/or)"); const BinaryNode *Binary = cast(E); const auto &[Child1, Child2] = Binary->getInternalNodes(); std::string Child1Token = rc_recur buildGHASTCondition(Child1); std::string Child2Token = rc_recur buildGHASTCondition(Child2); using PTMLOperator = ptml::PTMLCBuilder::Operator; const Tag &OpToken = E->getKind() == NodeKind::NK_And ? B.getOperator(PTMLOperator::BoolAnd) : B.getOperator(PTMLOperator::BoolOr); rc_return addAlwaysParentheses(Child1Token) + " " + OpToken.serialize() + " " + addAlwaysParentheses(Child2Token); } break; default: revng_abort("Unknown ExprNode kind"); } } RecursiveCoroutine CCodeGenerator::emitGHASTNode(const ASTNode *N) { if (N == nullptr) rc_return; revng_log(VisitLog, "|__ GHAST Node " << N->getID()); LoggerIndent Indent{ VisitLog }; auto Kind = N->getKind(); switch (Kind) { case ASTNode::NodeKind::NK_Break: { revng_log(VisitLog, "(NK_Break)"); const BreakNode *Break = llvm::cast(N); using PTMLOperator = ptml::PTMLCBuilder::Operator; if (Break->breaksFromWithinSwitch()) { revng_assert(not SwitchStateVars.empty() and not SwitchStateVars.back().empty()); Out << SwitchStateVars.back() << " " + B.getOperator(PTMLOperator::Assign) + " " + B.getTrueTag() + ";\n"; } }; [[fallthrough]]; case ASTNode::NodeKind::NK_SwitchBreak: { revng_log(VisitLog, "(NK_SwitchBreak)"); Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Break) << ";\n"; } break; case ASTNode::NodeKind::NK_Continue: { revng_log(VisitLog, "(NK_Continue)"); const ContinueNode *Continue = cast(N); // Print the condition computation code of the if statement. if (Continue->hasComputation()) { IfNode *ComputationIfNode = Continue->getComputationIfNode(); rc_recur buildGHASTCondition(ComputationIfNode->getCondExpr()); } // Actually print the continue statement only if the continue is not // implicit (i.e. it is not the last statement of the loop). if (not Continue->isImplicit()) Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Continue) << ";\n"; } break; case ASTNode::NodeKind::NK_Code: { revng_log(VisitLog, "(NK_Code)"); const CodeNode *Code = cast(N); llvm::BasicBlock *BB = Code->getOriginalBB(); revng_assert(BB != nullptr); emitBasicBlock(BB); } break; case ASTNode::NodeKind::NK_If: { revng_log(VisitLog, "(NK_If)"); const IfNode *If = cast(N); std::string CondExpr = rc_recur buildGHASTCondition(If->getCondExpr()); // "If" expression // TODO: possibly cast the CondExpr if it's not convertible to boolean? revng_assert(not CondExpr.empty()); Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::If) << " (" + CondExpr + ") "; { Scope TheScope(Out); // "Then" expression (always emitted) if (nullptr == If->getThen()) Out << B.getLineComment("Empty"); else rc_recur emitGHASTNode(If->getThen()); } // "Else" expression (optional) if (If->hasElse()) { Out << " " + B.getKeyword(ptml::PTMLCBuilder::Keyword::Else) + " "; Scope TheScope(Out); rc_recur emitGHASTNode(If->getElse()); } Out << "\n"; } break; case ASTNode::NodeKind::NK_Scs: { revng_log(VisitLog, "(NK_Scs)"); const ScsNode *LoopBody = cast(N); // Calculate the string of the condition // TODO: possibly cast the CondExpr if it's not convertible to boolean? std::string CondExpr = B.getTrueTag().serialize(); if (LoopBody->isWhile()) { const IfNode *LoopCondition = LoopBody->getRelatedCondition(); revng_assert(LoopCondition); // Retrieve the expression of the condition as well as emitting its // associated basic block CondExpr = rc_recur buildGHASTCondition(LoopCondition->getCondExpr()); revng_assert(not CondExpr.empty()); } if (LoopBody->isDoWhile()) Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Do) << " "; else Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::While) + " (" + CondExpr + ") "; revng_assert(LoopBody->hasBody()); { Scope TheScope(Out); rc_recur emitGHASTNode(LoopBody->getBody()); } if (LoopBody->isDoWhile()) Out << " " + B.getKeyword(ptml::PTMLCBuilder::Keyword::While) + " (" + CondExpr + ");"; Out << "\n"; } break; case ASTNode::NodeKind::NK_List: { revng_log(VisitLog, "(NK_List)"); const SequenceNode *Seq = cast(N); for (const ASTNode *Child : Seq->nodes()) rc_recur emitGHASTNode(Child); } break; case ASTNode::NodeKind::NK_Switch: { revng_log(VisitLog, "(NK_Switch)"); const SwitchNode *Switch = cast(N); // If needed, print the declaration of the switch state variable, which // is used by nested switches inside loops to break out of the loop if (Switch->needsStateVariable()) { revng_assert(Switch->needsLoopBreakDispatcher()); StringToken NewVarName = NameGenerator.nextSwitchStateVar(); std::string SwitchStateVar = getVariableLocationReference(NewVarName, ModelFunction, B); SwitchStateVars.push_back(std::move(SwitchStateVar)); using PTMLOperator = ptml::PTMLCBuilder::Operator; Out << B.tokenTag("bool", ptml::c::tokens::Type) << " " << getVariableLocationDefinition(NewVarName, ModelFunction, B) << " " + B.getOperator(PTMLOperator::Assign) + " " + B.getFalseTag() + ";\n"; } // Generate the condition of the switch StringToken SwitchVarToken; model::QualifiedType SwitchVarType; llvm::Value *SwitchVar = Switch->getCondition(); if (SwitchVar) { // If the switch is not weaved we need to print the instructions in // the basic block before it. if (not Switch->isWeaved()) { llvm::BasicBlock *BB = Switch->getOriginalBB(); revng_assert(BB != nullptr); // This is not a switch dispatcher. emitBasicBlock(BB); } std::string SwitchVarString = getToken(SwitchVar); SwitchVarToken = SwitchVarString; SwitchVarType = TypeMap.at(SwitchVar); } else { revng_assert(Switch->getOriginalBB() == nullptr); revng_assert(!LoopStateVar.empty()); // This switch does not come from an instruction: it's a dispatcher // for the loop state variable SwitchVarToken = LoopStateVar; // TODO: finer decision on the type of the loop state variable using model::PrimitiveTypeKind::Unsigned; SwitchVarType.UnqualifiedType() = Model.getPrimitiveType(Unsigned, 8); } revng_assert(not SwitchVarToken.empty()); if (not SwitchVarType.is(model::TypeKind::PrimitiveType)) { model::QualifiedType BoolTy; // TODO: finer decision on how to cast structs used in a switch using model::PrimitiveTypeKind::Unsigned; BoolTy.UnqualifiedType() = Model.getPrimitiveType(Unsigned, 8); SwitchVarToken = buildCastExpr(SwitchVarToken, SwitchVarType, BoolTy); } // Generate the switch statement Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Switch) + " (" << SwitchVarToken << ") "; { Scope TheScope(Out); using PTMLKeyword = ptml::PTMLCBuilder::Keyword; // Generate the body of the switch (except for the default) for (const auto &[Labels, CaseNode] : Switch->cases_const_range()) { revng_assert(not Labels.empty()); // Generate the case label(s) (multiple case labels might share the // same body) for (uint64_t CaseVal : Labels) { Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Case) + " "; if (SwitchVar) { llvm::Type *SwitchVarT = SwitchVar->getType(); auto *IntType = cast(SwitchVarT); auto *CaseConst = llvm::ConstantInt::get(IntType, CaseVal); // TODO: assigned the signedness based on the signedness of the // condition Out << B.getNumber(CaseConst->getValue()); } else { Out << B.getNumber(CaseVal); } Out << ":\n"; } { Scope InnerScope(Out); // Generate the case body rc_recur emitGHASTNode(CaseNode); } Out << " " + B.getKeyword(PTMLKeyword::Break) + ";\n"; } // Generate the default case if it exists if (auto *Default = Switch->getDefault()) { Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Default) << ":\n"; { Scope TheScope(Out); rc_recur emitGHASTNode(Default); } Out << " " + B.getKeyword(PTMLKeyword::Break) + ";\n"; } } Out << "\n"; // If the switch needs a loop break dispatcher, reset the associated // state variable before emitting the switch statement. if (Switch->needsLoopBreakDispatcher()) { revng_assert(not SwitchStateVars.empty() and not SwitchStateVars.back().empty()); Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::If) + " (" + SwitchStateVars.back() + ")"; { auto Scope = B.getScope(ptml::PTMLCBuilder::Scopes::Scope) .scope(Out, true); auto IndentScope = Out.scope(); Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Break) + ";"; } Out << "\n"; } // If we're done with a switch that generates a state variable to break // out of loops, pop it from the stack. if (Switch->needsStateVariable()) { revng_assert(Switch->needsLoopBreakDispatcher()); SwitchStateVars.pop_back(); } } break; case ASTNode::NodeKind::NK_Set: { revng_log(VisitLog, "(NK_Set)"); const SetNode *Set = cast(N); unsigned StateValue = Set->getStateVariableValue(); revng_assert(!LoopStateVar.empty()); // Print an assignment to the loop state variable. This is an artificial // variable introduced by the GHAST to enable executing certain pieces // of code based on which control-flow branch was taken. This, for // example, can be used to jump to the middle of a loop // instead of at the start, without emitting gotos. Out << LoopStateVar << " " << B.getOperator(ptml::PTMLCBuilder::Operator::Assign) << " " << StateValue << ";\n"; } break; } rc_return; } static std::string getModelArgIdentifier(const model::Type *ModelFunctionType, const llvm::Argument &Argument) { const llvm::Function *LLVMFunction = Argument.getParent(); unsigned ArgNo = Argument.getArgNo(); if (auto *RFT = dyn_cast(ModelFunctionType)) { auto NumModelArguments = RFT->Arguments().size(); revng_assert(ArgNo <= NumModelArguments + 1); revng_assert(LLVMFunction->arg_size() == NumModelArguments or (not RFT->StackArgumentsType().UnqualifiedType().empty() and (LLVMFunction->arg_size() == NumModelArguments + 1))); if (ArgNo < NumModelArguments) { return std::next(RFT->Arguments().begin(), ArgNo)->name().str().str(); } else { return "_stack_arguments"; } } else if (auto *CFT = dyn_cast(ModelFunctionType)) { revng_assert(LLVMFunction->arg_size() == CFT->Arguments().size()); revng_assert(ArgNo < CFT->Arguments().size()); return CFT->Arguments().at(ArgNo).name().str().str(); } revng_abort("Unexpected function type"); return ""; } void CCodeGenerator::emitFunction(bool NeedsLocalStateVar, InlineableTypesMap &StackTypes) { revng_log(Log, "========= Emitting Function " << LLVMFunction.getName()); revng_log(VisitLog, "========= Function " << LLVMFunction.getName()); LoggerIndent Indent{ VisitLog }; auto FunctionTagScope = B.getScope(ptml::PTMLCBuilder::Scopes::FunctionBody) .scope(Out); // Extract user comments from the model and emit them as PTML just before // the prototype. Out << B.getFunctionComment(ModelFunction, Model); // Print function's prototype printFunctionPrototype(ParentPrototype, ModelFunction, Out, B, Model, false); // Set up the argument identifiers to be used in the function's body. for (const auto &Arg : LLVMFunction.args()) { std::string ArgString = getModelArgIdentifier(&ParentPrototype, Arg); TokenMap[&Arg] = getArgumentLocationReference(ArgString, ModelFunction, B); } // Print the function body Out << " "; { Scope BraceScope(Out, ptml::c::scopes::FunctionBody); // We expect just one stack type definition. bool IsStackDefined = false; // Declare the local variable representing the stack frame if (not ModelFunction.StackFrameType().empty()) { revng_log(Log, "Stack Frame Declaration"); const auto &IsStackFrameDecl = [](const llvm::Instruction &I) { return isStackFrameDecl(&I); }; auto It = llvm::find_if(llvm::instructions(LLVMFunction), IsStackFrameDecl); if (It != llvm::instructions(LLVMFunction).end()) { const auto *Call = &cast(*It); std::string VarName = createTopScopeVarDeclName(Call); revng_assert(not VarName.empty()); auto *TheType = ModelFunction.StackFrameType().getConst(); // This will contain the stack types that we can inline, since // there could be a stack type that is being used somewhere else, // so we do not want to inline it. auto TheStackTypes = StackTypes.at(&ModelFunction); if (TheStackTypes.contains(TheType) and !IsStackDefined) { IsStackDefined = true; QualifiedTypeNameMap AdditionalTypeNames; // For all nested types within stack definition we print forward // declarations. for (auto *Type : TheStackTypes) { revng_assert(isCandidateForInline(Type)); printForwardDeclaration(*Type, Out, B); } printDefinition(Log, *cast(TheType), Out, B, TheStackTypes, AdditionalTypeNames, Model, VarName); } else { Out << getNamedCInstance(TypeMap.at(Call), VarName, B) << ";\n"; } } else { revng_log(Log, "WARNING: function with valid stack type has no stack " "declaration: " << LLVMFunction.getName()); } } // Declare all variables that have the entire function as a scope if (not TopScopeVariables.empty()) { revng_log(Log, "Top-Scope Declarations"); for (const llvm::Instruction *VarToDeclare : TopScopeVariables) { revng_log(Log, "VarToDeclare: " + dumpToString(VarToDeclare)); std::string VarName = createTopScopeVarDeclName(VarToDeclare); revng_assert(not VarName.empty()); auto VarTypeIt = TypeMap.find(VarToDeclare); if (VarTypeIt != TypeMap.end()) { Out << getNamedCInstance(VarTypeIt->second, VarName, B) << ";\n"; } else { // The only types that are allowed to be missing from the TypeMap // are LLVM aggregates returned by RawFunctionTypes or by helpers auto *Call = llvm::cast(VarToDeclare); const auto &Prototype = Cache.getCallSitePrototype(Model, Call); if (not Prototype.empty()) { const auto *FunctionType = Prototype.getConst(); Out << getNamedInstanceOfReturnType(*FunctionType, VarName, B) << ";\n"; } else { auto *CalledFunction = Call->getCalledFunction(); revng_assert(CalledFunction); Out << getReturnTypeLocationReference(CalledFunction, B) << " " << VarName << ";\n"; } } } revng_log(Log, "End of Top-Scope Declarations"); } // Emit a declaration for the loop state variable, which is used to // redirect control flow inside loops (e.g. if we want to jump in the // middle of a loop during a certain iteration) if (NeedsLocalStateVar) Out << B.tokenTag("uint64_t", ptml::c::tokens::Type) << " " << LoopStateVarDeclaration << ";\n"; // Recursively print the body of this function emitGHASTNode(GHAST.getRoot()); } Out << "\n"; } static std::string decompileFunction(FunctionMetadataCache &Cache, const llvm::Function &LLVMFunc, const ASTTree &CombedAST, const Binary &Model, const InstrSetVec &TopScopeVariables, bool NeedsLocalStateVar, InlineableTypesMap &StackTypes) { std::string Result; llvm::raw_string_ostream Out(Result); ptml::PTMLCBuilder B; CCodeGenerator Backend(Cache, Model, LLVMFunc, CombedAST, TopScopeVariables, Out, B); Backend.emitFunction(NeedsLocalStateVar, StackTypes); Out.flush(); return Result; } /// Visit the node and all its children recursively, checking if a loop /// variable is needed. // TODO: This could be precomputed and attached to the SCS node in the GHAST. static RecursiveCoroutine needsLoopVar(ASTNode *N) { if (N == nullptr) rc_return false; auto Kind = N->getKind(); switch (Kind) { case ASTNode::NodeKind::NK_Break: case ASTNode::NodeKind::NK_SwitchBreak: case ASTNode::NodeKind::NK_Continue: case ASTNode::NodeKind::NK_Code: rc_return false; break; case ASTNode::NodeKind::NK_If: { IfNode *If = cast(N); if (nullptr != If->getThen()) if (rc_recur needsLoopVar(If->getThen())) rc_return true; if (If->hasElse()) if (rc_recur needsLoopVar(If->getElse())) rc_return true; rc_return false; } break; case ASTNode::NodeKind::NK_Scs: { ScsNode *LoopBody = cast(N); rc_return rc_recur needsLoopVar(LoopBody->getBody()); } break; case ASTNode::NodeKind::NK_List: { SequenceNode *Seq = cast(N); for (ASTNode *Child : Seq->nodes()) if (rc_recur needsLoopVar(Child)) rc_return true; rc_return false; } break; case ASTNode::NodeKind::NK_Switch: { SwitchNode *Switch = cast(N); llvm::Value *SwitchVar = Switch->getCondition(); if (not SwitchVar) rc_return true; for (const auto &[Labels, CaseNode] : Switch->cases()) if (rc_recur needsLoopVar(CaseNode)) rc_return true; if (auto *Default = Switch->getDefault()) if (rc_recur needsLoopVar(Default)) rc_return true; rc_return false; } break; case ASTNode::NodeKind::NK_Set: { rc_return true; } break; } } static bool hasLoopDispatchers(const ASTTree &GHAST) { return needsLoopVar(GHAST.getRoot()); } static InstrSetVec collectTopScopeVariables(const llvm::Function &F) { InstrSetVec TopScopeVars; for (const BasicBlock &BB : F) { for (const Instruction &I : BB) { if (auto *Call = dyn_cast(&I)) { // All the others have already been promoted to LocalVariable Copy and // Assign. if (not Call->getType()->isAggregateType()) continue; if (isCallToNonIsolated(Call) or isCallToIsolatedFunction(Call)) { const auto *Called = Call->getCalledFunction(); revng_assert(not Called or not Called->isTargetIntrinsic()); if (needsTopScopeDeclaration(*Call)) TopScopeVars.insert(Call); } } } } return TopScopeVars; } using Container = revng::pipes::DecompiledCCodeInYAMLStringMap; void decompile(FunctionMetadataCache &Cache, llvm::Module &Module, const model::Binary &Model, Container &DecompiledFunctions) { TypeInlineHelper TheTypeInlineHelper(Model); // Get all Stack types and all the inlinable types reachable from it, // since we want to emit forward declarations for all of them. auto StackTypes = TheTypeInlineHelper.findStackTypesPerFunction(Model); auto T = llvm::make_task_on_set(llvm::make_address_range(FunctionTags::Isolated .functions(&Module)), "decompile"); for (llvm::Function &F : FunctionTags::Isolated.functions(&Module)) { T.advance(&F, llvm::Twine("decompile Function: ") + llvm::Twine(F.getName())); if (F.empty()) continue; llvm::Task T2(3, llvm::Twine("decompile Function: ") + llvm::Twine(F.getName())); // TODO: this will eventually become a GHASTContainer for revng pipeline ASTTree GHAST; // Generate the GHAST and beautify it. { T2.advance("restructureCFG"); restructureCFG(F, GHAST); // TODO: beautification should be optional, but at the moment it's not // truly so (if disabled, things crash). We should strive to make it // optional for real. T2.advance("beautifyAST"); beautifyAST(F, GHAST); } T2.advance("decompileFunction"); if (Log.isEnabled()) { std::string ASTFileName = F.getName().str() + "GHAST-during-c-codegen.dot"; GHAST.dumpASTOnFile(ASTFileName.c_str()); } // Generated C code for F auto TopScopeVariables = collectTopScopeVariables(F); auto NeedsLoopStateVar = hasLoopDispatchers(GHAST); std::string CCode = decompileFunction(Cache, F, GHAST, Model, TopScopeVariables, NeedsLoopStateVar, StackTypes); // Push the C code into MetaAddress Key = getMetaAddressMetadata(&F, "revng.function.entry"); DecompiledFunctions.insert_or_assign(Key, std::move(CCode)); } }