diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/template/TemplateSimplifier.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/template/TemplateSimplifier.java index 9b132973ff..d05f5482bf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/template/TemplateSimplifier.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/template/TemplateSimplifier.java @@ -18,13 +18,14 @@ package ghidra.app.util.template; import ghidra.GhidraOptions; import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; +import ghidra.program.model.symbol.NameTransformer; import ghidra.util.HelpLocation; /** * Class for simplify names with template data. This class can be used with tool options or * as a stand alone configurable simplifier. */ -public class TemplateSimplifier { +public class TemplateSimplifier implements NameTransformer { public static final String SUB_OPTION_NAME = "Templates"; public static final String SIMPLIFY_TEMPLATES_OPTION = @@ -145,6 +146,7 @@ public class TemplateSimplifier { * @param input the input string to be simplified * @return a simplified string */ + @Override public String simplify(String input) { if (!doSimplify) { return input; @@ -192,10 +194,13 @@ public class TemplateSimplifier { } private String doSimplify(String input, int depth) { - StringBuilder builder = new StringBuilder(); int pos = 0; - TemplateString ts; - while ((ts = findTemplateString(input, pos)) != null) { + TemplateString ts = findTemplateString(input, pos); + if (ts == null) { + return input; // Fast fail if no template characters present + } + StringBuilder builder = new StringBuilder(); + do { builder.append(input.substring(pos, ts.start)); String template = ts.getTemplate(); if (depth == 0) { @@ -215,7 +220,9 @@ public class TemplateSimplifier { builder.append(">"); } pos = ts.end + 1; + ts = findTemplateString(input, pos); } + while (ts != null); builder.append(input.substring(pos)); return builder.toString(); } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/callgraph.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/callgraph.cc index 88ae140a38..c7dfbea0b1 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/callgraph.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/callgraph.cc @@ -213,7 +213,7 @@ CallGraphNode *CallGraph::addNode(Funcdata *f) throw LowlevelError("Functions with duplicate entry points: "+f->getName()+" "+node.getFuncdata()->getName()); node.entryaddr = f->getAddress(); - node.name = f->getName(); + node.name = f->getDisplayName(); node.fd = f; return &node; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc index 375708e9f5..3bd9ae6b72 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc @@ -395,6 +395,7 @@ void Symbol::decodeHeader(Decoder &decoder) { name.clear(); + displayName.clear(); category = no_category; symbolId = 0; for(;;) { @@ -447,12 +448,17 @@ void Symbol::decodeHeader(Decoder &decoder) if (decoder.readBool()) flags |= Varnode::volatil; } + else if (attribId == ATTRIB_LABEL) { + displayName = decoder.readString(); + } } if (category == function_parameter) { catindex = decoder.readUnsignedInteger(ATTRIB_INDEX); } else catindex = 0; + if (displayName.size() == 0) + displayName = name; } /// Encode the data-type for the Symbol @@ -532,6 +538,7 @@ FunctionSymbol::FunctionSymbol(Scope *sc,const string &nm,int4 size) consumeSize = size; buildType(); name = nm; + displayName = nm; } FunctionSymbol::FunctionSymbol(Scope *sc,int4 size) @@ -552,7 +559,7 @@ Funcdata *FunctionSymbol::getFunction(void) { if (fd != (Funcdata *)0) return fd; SymbolEntry *entry = getFirstWholeMap(); - fd = new Funcdata(name,scope,entry->getAddr(),this); + fd = new Funcdata(name,displayName,scope,entry->getAddr(),this); return fd; } @@ -575,7 +582,7 @@ void FunctionSymbol::decode(Decoder &decoder) { uint4 elemId = decoder.peekElement(); if (elemId == ELEM_FUNCTION) { - fd = new Funcdata("",scope,Address(),this); + fd = new Funcdata("","",scope,Address(),this); try { symbolId = fd->decode(decoder); } catch(RecovError &err) { @@ -583,6 +590,7 @@ void FunctionSymbol::decode(Decoder &decoder) throw DuplicateFunctionError(fd->getAddress(),fd->getName()); } name = fd->getName(); + displayName = fd->getDisplayName(); if (consumeSize < fd->getSize()) { if ((fd->getSize()>1)&&(fd->getSize() <= 8)) consumeSize = fd->getSize(); @@ -599,6 +607,9 @@ void FunctionSymbol::decode(Decoder &decoder) else if (attribId == ATTRIB_ID) { symbolId = decoder.readUnsignedInteger(); } + else if (attribId == ATTRIB_LABEL) { + displayName = decoder.readString(); + } } decoder.closeElement(elemId); } @@ -727,6 +738,7 @@ LabSymbol::LabSymbol(Scope *sc,const string &nm) { buildType(); name = nm; + displayName = nm; } /// \param sc is the Scope that will contain the new Symbol @@ -766,6 +778,8 @@ void ExternRefSymbol::buildNameType(void) name = s.str(); name += "_exref"; // Indicate this is an external reference variable } + if (displayName.size() == 0) + displayName = name; flags |= Varnode::externref | Varnode::typelock; } @@ -792,12 +806,15 @@ void ExternRefSymbol::decode(Decoder &decoder) { uint4 elemId = decoder.openElement(ELEM_EXTERNREFSYMBOL); - name = ""; // Name is empty + name.clear(); // Name is empty + displayName.clear(); for(;;) { uint4 attribId = decoder.getNextAttributeId(); if (attribId == 0) break; if (attribId == ATTRIB_NAME) // Unless we see it explicitly name = decoder.readString(); + else if (attribId == ATTRIB_LABEL) + displayName = decoder.readString(); } refaddr = Address::decode(decoder); decoder.closeElement(elemId); @@ -1798,8 +1815,10 @@ void ScopeInternal::addSymbolInternal(Symbol *sym) nextUniqueId += 1; } try { - if (sym->name.size() == 0) + if (sym->name.size() == 0) { sym->name = buildUndefinedName(); + sym->displayName = sym->name; + } if (sym->getType() == (Datatype *)0) throw LowlevelError(sym->getName() + " symbol created with no type"); if (sym->getType()->getSize() < 1) @@ -2138,6 +2157,7 @@ void ScopeInternal::renameSymbol(Symbol *sym,const string &newname) multiEntrySet.erase(sym); // The multi-entry set is sorted by name, remove string oldname = sym->name; sym->name = newname; + sym->displayName = newname; insertNameTree(sym); if (sym->wholeCount > 1) multiEntrySet.insert(sym); // Reenter into the multi-entry set now that name is changed @@ -3315,14 +3335,32 @@ void Database::decode(Decoder &decoder) for(;;) { uint4 subId = decoder.openElement(); if (subId != ELEM_SCOPE) break; - string name = decoder.readString(ATTRIB_NAME); - uint8 id = decoder.readUnsignedInteger(ATTRIB_ID); + string name; + string displayName; + uint8 id = 0; + bool seenId = false; + for(;;) { + uint4 attribId = decoder.getNextAttributeId(); + if (attribId == 0) break; + if (attribId == ATTRIB_NAME) + name = decoder.readString(); + else if (attribId == ATTRIB_ID) { + id = decoder.readUnsignedInteger(); + seenId = true; + } + else if (attribId == ATTRIB_LABEL) + displayName = decoder.readString(); + } + if (name.empty() || !seenId) + throw DecoderError("Missing name and id attributes in scope"); Scope *parentScope = (Scope *)0; uint4 parentId = decoder.peekElement(); if (parentId == ELEM_PARENT) { parentScope = parseParentTag(decoder); } Scope *newScope = findCreateScope(id, name, parentScope); + if (!displayName.empty()) + newScope->setDisplayName(displayName); newScope->decode(decoder); decoder.closeElement(subId); } @@ -3356,4 +3394,39 @@ void Database::decodeScope(Decoder &decoder,Scope *newScope) decoder.closeElement(elemId); } +/// Some namespace objects may already exist. Create those that don't. +/// \param decoder is the stream to decode the path from +/// \return the namespace described by the path +Scope *Database::decodeScopePath(Decoder &decoder) + +{ + Scope *curscope = getGlobalScope(); + uint4 elemId = decoder.openElement(ELEM_PARENT); + uint4 subId = decoder.openElement(); + decoder.closeElementSkipping(subId); // Skip element describing the root scope + for(;;) { + subId = decoder.openElement(); + if (subId != ELEM_VAL) break; + string displayName; + uint8 scopeId = 0; + for(;;) { + uint4 attribId = decoder.getNextAttributeId(); + if (attribId == 0) break; + if (attribId == ATTRIB_ID) + scopeId = decoder.readUnsignedInteger(); + else if (attribId == ATTRIB_LABEL) + displayName = decoder.readString(); + } + string name = decoder.readString(ATTRIB_CONTENT); + if (scopeId == 0) + throw DecoderError("Missing name and id in scope"); + curscope = findCreateScope(scopeId, name, curscope); + if (!displayName.empty()) + curscope->setDisplayName(displayName); + decoder.closeElement(subId); + } + decoder.closeElement(elemId); + return curscope; +} + } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/database.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/database.hh index 542a0d7b9c..bdf162d2af 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/database.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/database.hh @@ -176,6 +176,7 @@ class Symbol { protected: Scope *scope; ///< The scope that owns this symbol string name; ///< The local name of the symbol + string displayName; ///< Name to use when displaying symbol in output Datatype *type; ///< The symbol's data-type uint4 nameDedup; ///< id to distinguish symbols with the same name uint4 flags; ///< Varnode-like properties of the symbol @@ -218,6 +219,7 @@ public: Symbol(Scope *sc,const string &nm,Datatype *ct); ///< Construct given a name and data-type Symbol(Scope *sc); ///< Construct for use with decode() const string &getName(void) const { return name; } ///< Get the local name of the symbol + const string &getDisplayName(void) const { return displayName; } ///< Get the name to display in output Datatype *getType(void) const { return type; } ///< Get the data-type uint8 getId(void) const { return symbolId; } ///< Get a unique id for the symbol uint4 getFlags(void) const { return flags; } ///< Get the boolean properties of the Symbol @@ -469,6 +471,7 @@ class Scope { protected: Architecture *glb; ///< Architecture of \b this scope string name; ///< Name of \b this scope + string displayName; ///< Name to display in output Funcdata *fd; ///< (If non-null) the function which \b this is the local Scope for uint8 uniqueId; ///< Unique id for the scope, for deduping scope names, assigning symbol ids static const Scope *stackAddr(const Scope *scope1, @@ -551,6 +554,7 @@ protected: const RangeList &uselim)=0; SymbolEntry *addMap(SymbolEntry &entry); ///< Integrate a SymbolEntry into the range maps void setSymbolId(Symbol *sym,uint8 id) const { sym->symbolId = id; } ///< Adjust the id associated with a symbol + void setDisplayName(const string &nm) { displayName = nm; } ///< Change name displayed in output public: #ifdef OPACTION_DEBUG mutable bool debugon; @@ -559,7 +563,7 @@ public: #endif /// \brief Construct an empty scope, given a name and Architecture Scope(uint8 id,const string &nm,Architecture *g,Scope *own) { - uniqueId = id; name = nm; glb = g; parent = (Scope *)0; fd = (Funcdata *)0; owner=own; + uniqueId = id; name = nm; displayName = nm; glb = g; parent = (Scope *)0; fd = (Funcdata *)0; owner=own; #ifdef OPACTION_DEBUG debugon = false; #endif @@ -732,6 +736,7 @@ public: const Address &addr,const Address &usepoint); const string &getName(void) const { return name; } ///< Get the name of the Scope + const string &getDisplayName(void) const { return displayName; } ///< Get name displayed in output uint8 getId(void) const { return uniqueId; } ///< Get the globally unique id bool isGlobal(void) const { return (fd == (Funcdata *)0); } ///< Return \b true if \b this scope is global @@ -938,7 +943,8 @@ public: const partmap &getProperties(void) const { return flagbase; } ///< Get the entire property map void encode(Encoder &encoder) const; ///< Encode the whole Database to a stream void decode(Decoder &decoder); ///< Decode the whole database from a stream - void decodeScope(Decoder &decoder,Scope *newScope); ///< Register and fill out a single Scope from an XML \ tag + void decodeScope(Decoder &decoder,Scope *newScope); ///< Register and fill out a single Scope from an XML \ tag + Scope *decodeScopePath(Decoder &decoder); ///< Decode a namespace path and make sure each namespace exists }; /// \param sc is the scope containing the new symbol @@ -949,6 +955,7 @@ inline Symbol::Symbol(Scope *sc,const string &nm,Datatype *ct) { scope=sc; name=nm; + displayName = nm; nameDedup=0; type=ct; flags=0; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/database_ghidra.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/database_ghidra.cc index fbe0bf85c7..7e7e59c5fc 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/database_ghidra.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/database_ghidra.cc @@ -42,7 +42,7 @@ ScopeGhidra::~ScopeGhidra(void) /// The Ghidra client reports a \e namespace id associated with /// Symbol. Determine if a matching \e namespace Scope already exists in the cache and build -/// it if it isn't. This may mean creating a new \e namespace Scope. +/// it if it isn't. This may mean creating the \e namespace Scope and its ancestors. /// \param id is the ID associated with the Ghidra namespace /// \return the Scope matching the id. Scope *ScopeGhidra::reresolveScope(uint8 id) const @@ -58,19 +58,7 @@ Scope *ScopeGhidra::reresolveScope(uint8 id) const if (!ghidra->getNamespacePath(id,decoder)) throw LowlevelError("Could not get namespace info"); - Scope *curscope = symboltab->getGlobalScope(); // Get pointer to ourselves (which is not const) - uint4 elemId = decoder.openElement(); - uint4 subId = decoder.openElement(); - decoder.closeElementSkipping(subId); // Skip element describing the root scope - for(;;) { - subId = decoder.openElement(); - if (subId == 0) break; - uint8 scopeId = decoder.readUnsignedInteger(ATTRIB_ID); - curscope = symboltab->findCreateScope(scopeId, decoder.readString(ATTRIB_CONTENT), curscope); - decoder.closeElement(subId); - } - decoder.closeElement(elemId); - return curscope; + return symboltab->decodeScopePath(decoder); } /// The Ghidra client can respond to a query negatively by sending a diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc index c1e640afc4..3b1cb47412 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc @@ -1398,8 +1398,9 @@ void FlowInfo::recoverJumpTables(vector &newTables,vectorgetAddr().printRaw(s1); + string nm = s1.str(); // Prepare partial Funcdata object for analysis if necessary - Funcdata partial(s1.str(),data.getScopeLocal()->getParent(),data.getAddress(),(FunctionSymbol *)0); + Funcdata partial(nm,nm,data.getScopeLocal()->getParent(),data.getAddress(),(FunctionSymbol *)0); for(int4 i=0;igetAddress(); - if (fd->getName().size() != 0) - name = fd->getName(); + if (fd->getDisplayName().size() != 0) + name = fd->getDisplayName(); } } @@ -5130,7 +5130,7 @@ void FuncCallSpecs::deindirect(Funcdata &data,Funcdata *newfd) { entryaddress = newfd->getAddress(); - name = newfd->getName(); + name = newfd->getDisplayName(); fd = newfd; Varnode *vn = data.newVarnodeCallSpecs(this); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.cc index 69ae718d64..13e0606775 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.cc @@ -30,7 +30,7 @@ ElementId ELEM_VARNODES = ElementId("varnodes",119); /// \param addr is the entry address for the function /// \param sym is the symbol representing the function /// \param sz is the number of bytes (of code) in the function body -Funcdata::Funcdata(const string &nm,Scope *scope,const Address &addr,FunctionSymbol *sym,int4 sz) +Funcdata::Funcdata(const string &nm,const string &disp,Scope *scope,const Address &addr,FunctionSymbol *sym,int4 sz) : baseaddr(addr), funcp(), vbank(scope->getArch()), @@ -47,6 +47,7 @@ Funcdata::Funcdata(const string &nm,Scope *scope,const Address &addr,FunctionSym glb = scope->getArch(); minLanedSize = glb->getMinimumLanedRegisterSize(); name = nm; + displayName = disp; size = sz; AddrSpace *stackid = glb->getStackSpace(); @@ -734,9 +735,13 @@ uint8 Funcdata::decode(Decoder &decoder) if (decoder.readBool()) flags |= no_code; } + else if (attribId == ATTRIB_LABEL) + displayName = decoder.readString(); } if (name.size() == 0) throw LowlevelError("Missing function name"); + if (displayName.size() == 0) + displayName = name; if (size == -1) throw LowlevelError("Missing function size"); baseaddr = Address::decode( decoder ); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh index b54590848b..7ea31a4cc8 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh @@ -79,6 +79,7 @@ class Funcdata { Architecture *glb; ///< Global configuration data FunctionSymbol *functionSymbol; ///< The symbol representing \b this function string name; ///< Name of function + string displayName; ///< Name to display in output Address baseaddr; ///< Starting code address of binary data FuncProto funcp; ///< Prototype of this function ScopeLocal *localmap; ///< Local variables (symbols in the function scope) @@ -136,9 +137,10 @@ class Funcdata { static PcodeOp *findPrimaryBranch(PcodeOpTree::const_iterator iter,PcodeOpTree::const_iterator enditer, bool findbranch,bool findcall,bool findreturn); public: - Funcdata(const string &nm,Scope *conf,const Address &addr,FunctionSymbol *sym,int4 sz=0); ///< Constructor + Funcdata(const string &nm,const string &disp,Scope *conf,const Address &addr,FunctionSymbol *sym,int4 sz=0); ///< Constructor ~Funcdata(void); ///< Destructor const string &getName(void) const { return name; } ///< Get the function's local symbol name + const string &getDisplayName(void) const { return displayName; } ///< Get the name to display in output const Address &getAddress(void) const { return baseaddr; } ///< Get the entry point address int4 getSize(void) const { return size; } ///< Get the function body size in bytes Architecture *getArch(void) const { return glb; } ///< Get the program/architecture owning \b this function diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc index a84562e971..e4afcce855 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc @@ -222,7 +222,7 @@ void PrintC::pushSymbolScope(const Symbol *symbol) pushOp(&scope, (PcodeOp *)0); } for(int4 i=scopedepth-1;i>=0;--i) { - pushAtom(Atom(scopeList[i]->getName(),syntax,EmitMarkup::global_color,(PcodeOp *)0,(Varnode *)0)); + pushAtom(Atom(scopeList[i]->getDisplayName(),syntax,EmitMarkup::global_color,(PcodeOp *)0,(Varnode *)0)); } } } @@ -252,7 +252,7 @@ void PrintC::emitSymbolScope(const Symbol *symbol) point = point->getParent(); } for(int4 i=scopedepth-1;i>=0;--i) { - emit->print(scopeList[i]->getName(), EmitMarkup::global_color); + emit->print(scopeList[i]->getDisplayName(), EmitMarkup::global_color); emit->print(scope.print1, EmitMarkup::no_color); } } @@ -285,7 +285,7 @@ void PrintC::pushTypeStart(const Datatype *ct,bool noident) } else { pushOp(tok,(const PcodeOp *)0); - pushAtom(Atom(ct->getName(),typetoken,EmitMarkup::type_color,ct)); + pushAtom(Atom(ct->getDisplayName(),typetoken,EmitMarkup::type_color,ct)); } for(int4 i=typestack.size()-2;i>=0;--i) { ct = typestack[i]; @@ -661,7 +661,7 @@ void PrintC::opConstructor(const PcodeOp *op,bool withNew) if (dt->getMetatype() == TYPE_PTR) { dt = ((TypePointer *)dt)->getPtrTo(); } - string nm = dt->getName(); + string nm = dt->getDisplayName(); pushOp(&function_call,op); pushAtom(Atom(nm,optoken,EmitMarkup::funcname_color,op)); // implied vn's pushed on in reverse order for efficiency @@ -1112,7 +1112,7 @@ void PrintC::opCpoolRefOp(const PcodeOp *op) pushAtom(Atom(rec->getToken(),functoken,EmitMarkup::funcname_color,op,outvn)); pushOp(&comma,(const PcodeOp *)0); pushVn(vn0,op,mods); - pushAtom(Atom(dt->getName(),syntax,EmitMarkup::type_color,op,outvn)); + pushAtom(Atom(dt->getDisplayName(),syntax,EmitMarkup::type_color,op,outvn)); break; } case CPoolRecord::primitive: // Should be eliminated @@ -1163,7 +1163,7 @@ void PrintC::opNewOp(const PcodeOp *op) while (dt->getMetatype() == TYPE_PTR) { dt = ((TypePointer *)dt)->getPtrTo(); } - nm = dt->getName(); + nm = dt->getDisplayName(); } pushOp(&subscript,op); pushAtom(Atom(nm,optoken,EmitMarkup::type_color,op)); @@ -1658,7 +1658,7 @@ bool PrintC::pushPtrCodeConstant(uintb val,const TypePointer *ct, val = AddrSpace::addressToByte(val,spc->getWordSize()); fd = glb->symboltab->getGlobalScope()->queryFunction( Address(spc,val)); if (fd != (Funcdata *)0) { - pushAtom(Atom(fd->getName(),functoken,EmitMarkup::funcname_color,op,fd)); + pushAtom(Atom(fd->getDisplayName(),functoken,EmitMarkup::funcname_color,op,fd)); return true; } return false; @@ -1839,7 +1839,7 @@ void PrintC::pushSymbol(const Symbol *sym,const Varnode *vn,const PcodeOp *op) HighVariable *high = vn->getHigh(); if (high->isUnmerged()) { ostringstream s; - s << sym->getName(); + s << sym->getDisplayName(); SymbolEntry *entry = high->getSymbolEntry(); if (entry != (SymbolEntry *)0) { s << '$' << dec << entry->getSymbol()->getMapEntryPosition(entry); @@ -1850,7 +1850,7 @@ void PrintC::pushSymbol(const Symbol *sym,const Varnode *vn,const PcodeOp *op) return; } } - pushAtom(Atom(sym->getName(),vartoken,tokenColor,op,vn)); + pushAtom(Atom(sym->getDisplayName(),vartoken,tokenColor,op,vn)); } void PrintC::pushUnnamedLocation(const Address &addr, @@ -1984,7 +1984,7 @@ void PrintC::pushMismatchSymbol(const Symbol *sym,int4 off,int4 sz, // We prepend an underscore to indicate a close // but not quite match - string nm = '_'+sym->getName(); + string nm = '_'+sym->getDisplayName(); pushAtom(Atom(nm,vartoken,EmitMarkup::var_color,op,vn)); } else @@ -2057,7 +2057,7 @@ void PrintC::emitStructDefinition(const TypeStruct *ct) emit->tagLine(); emit->print(CLOSE_CURLY); emit->spaces(1); - emit->print(ct->getName()); + emit->print(ct->getDisplayName()); emit->print(SEMICOLON); } @@ -2100,7 +2100,7 @@ void PrintC::emitEnumDefinition(const TypeEnum *ct) emit->tagLine(); emit->print(CLOSE_CURLY); emit->spaces(1); - emit->print(ct->getName()); + emit->print(ct->getDisplayName()); emit->print(SEMICOLON); } @@ -2507,7 +2507,7 @@ void PrintC::emitFunctionDeclaration(const Funcdata *fd) } int4 id1 = emit->openGroup(); emitSymbolScope(fd->getSymbol()); - emit->tagFuncName(fd->getName(),EmitMarkup::funcname_color,fd,(PcodeOp *)0); + emit->tagFuncName(fd->getDisplayName(),EmitMarkup::funcname_color,fd,(PcodeOp *)0); emit->spaces(function_call.spacing,function_call.bump); int4 id2 = emit->openParen(OPEN_PAREN); @@ -3122,7 +3122,7 @@ void PrintC::emitLabel(const FlowBlock *bl) const Scope *symScope = ((const BlockBasic *)bb)->getFuncdata()->getScopeLocal(); Symbol *sym = symScope->queryCodeLabel(addr); if (sym != (Symbol *)0) { - emit->tagLabel(sym->getName(),EmitMarkup::no_color,spc,off); + emit->tagLabel(sym->getDisplayName(),EmitMarkup::no_color,spc,off); return; } } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printjava.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/printjava.cc index 3084ffee21..5885f2d585 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printjava.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printjava.cc @@ -107,7 +107,7 @@ void PrintJava::pushTypeStart(const Datatype *ct,bool noident) pushAtom(Atom(nm,typetoken,EmitMarkup::type_color,ct)); } else { - pushAtom(Atom(ct->getName(),typetoken,EmitMarkup::type_color,ct)); + pushAtom(Atom(ct->getDisplayName(),typetoken,EmitMarkup::type_color,ct)); } for(int4 i=0;igetName(),syntax,EmitMarkup::type_color,op,outvn)); + pushAtom(Atom(dt->getDisplayName(),syntax,EmitMarkup::type_color,op,outvn)); break; } case CPoolRecord::primitive: // Should be eliminated diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc index fa02605a63..30faf0b656 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc @@ -544,6 +544,9 @@ void Datatype::decodeBasic(Decoder &decoder) uint4 val = encodeIntegerFormat(decoder.readString()); setDisplayFormat(val); } + else if (attrib == ATTRIB_LABEL) { + displayName = decoder.readString(); + } } if (size < 0) throw LowlevelError("Bad size for type "+name); @@ -554,6 +557,8 @@ void Datatype::decodeBasic(Decoder &decoder) // Id needs to be unique compared to another data-type with the same name id = hashSize(id, size); } + if (displayName.empty()) + displayName = name; } /// If a type id is explicitly provided for a data-type, this routine is used @@ -3003,6 +3008,7 @@ Datatype *TypeFactory::setName(Datatype *ct,const string &n) nametree.erase( ct ); // Erase any name reference tree.erase(ct); // Remove new type completely from trees ct->name = n; // Change the name + ct->displayName = n; if (ct->id == 0) ct->id = Datatype::hashName(n); // Insert type with new name @@ -3215,7 +3221,7 @@ TypeVoid *TypeFactory::getTypeVoid(void) if (ct != (TypeVoid *)0) return ct; TypeVoid tv; - tv.id = Datatype::hashName(tv.getName()); + tv.id = Datatype::hashName(tv.name); ct = (TypeVoid *)tv.clone(); tree.insert(ct); nametree.insert(ct); @@ -3332,6 +3338,7 @@ TypeCode *TypeFactory::getTypeCode(const string &nm) if (nm.size()==0) return getTypeCode(); TypeCode tmp; // Generic code data-type tmp.name = nm; // with a name + tmp.displayName = nm; tmp.id = Datatype::hashName(nm); tmp.markComplete(); // considered complete return (TypeCode *) findAdd(tmp); @@ -3384,6 +3391,7 @@ Datatype *TypeFactory::getTypedef(Datatype *ct,const string &name,uint8 id,uint4 } res = ct->clone(); // Clone everything res->name = name; // But a new name + res->displayName = name; res->id = id; // and new id res->flags &= ~((uint4)Datatype::coretype); // Not a core type res->typedefImm = ct; @@ -3438,6 +3446,7 @@ TypePointer *TypeFactory::getTypePointer(int4 s,Datatype *pt,uint4 ws,const stri pt = pt->getStripped(); TypePointer tmp(s,pt,ws); tmp.name = n; + tmp.displayName = n; tmp.id = Datatype::hashName(n); return (TypePointer *) findAdd(tmp); } @@ -3485,6 +3494,7 @@ TypeStruct *TypeFactory::getTypeStruct(const string &n) { TypeStruct tmp; tmp.name = n; + tmp.displayName = n; tmp.id = Datatype::hashName(n); return (TypeStruct *) findAdd(tmp); } @@ -3505,6 +3515,7 @@ TypeUnion *TypeFactory::getTypeUnion(const string &n) { TypeUnion tmp; tmp.name = n; + tmp.displayName = n; tmp.id = Datatype::hashName(n); return (TypeUnion *) findAdd(tmp); } @@ -3587,6 +3598,7 @@ TypePointerRel *TypeFactory::getTypePointerRel(int4 sz,Datatype *parent,Datatype { TypePointerRel tp(sz,ptrTo,ws,parent,off); tp.name = nm; + tp.displayName = nm; tp.id = Datatype::hashName(nm); TypePointerRel *res = (TypePointerRel *)findAdd(tp); return res; @@ -3605,6 +3617,7 @@ TypePointer *TypeFactory::getTypePointerWithSpace(Datatype *ptrTo,AddrSpace *spc { TypePointer tp(ptrTo,spc); tp.name = nm; + tp.displayName = nm; tp.id = Datatype::hashName(nm); TypePointer *res = (TypePointer *)findAdd(tp); return res; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh index 957a788d57..0467995dd6 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh @@ -159,12 +159,13 @@ protected: }; friend class TypeFactory; friend struct DatatypeCompare; + uint8 id; ///< A unique id for the type (or 0 if an id is not assigned) int4 size; ///< Size (of variable holding a value of this type) + uint4 flags; ///< Boolean properties of the type string name; ///< Name of type + string displayName; ///< Name to display in output type_metatype metatype; ///< Meta-type - type disregarding size sub_metatype submeta; ///< Sub-type of of the meta-type, for comparisons - uint4 flags; ///< Boolean properties of the type - uint8 id; ///< A unique id for the type (or 0 if an id is not assigned) Datatype *typedefImm; ///< The immediate data-type being typedefed by \e this void decodeBasic(Decoder &decoder); ///< Recover basic data-type properties void encodeBasic(type_metatype meta,Encoder &encoder) const; ///< Encode basic data-type properties @@ -176,8 +177,8 @@ protected: static uint8 hashSize(uint8 id,int4 size); ///< Reversibly hash size into id public: /// Construct the base data-type copying low-level properties of another - Datatype(const Datatype &op) { size = op.size; name=op.name; metatype=op.metatype; submeta=op.submeta; flags=op.flags; - id=op.id; typedefImm=op.typedefImm; } + Datatype(const Datatype &op) { size = op.size; name=op.name; displayName=op.displayName; metatype=op.metatype; + submeta=op.submeta; flags=op.flags; id=op.id; typedefImm=op.typedefImm; } /// Construct the base data-type providing size and meta-type Datatype(int4 s,type_metatype m) { size=s; metatype=m; submeta=base2sub[m]; flags=0; id=0; typedefImm=(Datatype *)0; } virtual ~Datatype(void) {} ///< Destructor @@ -203,6 +204,7 @@ public: uint8 getId(void) const { return id; } ///< Get the type id int4 getSize(void) const { return size; } ///< Get the type size const string &getName(void) const { return name; } ///< Get the type name + const string &getDisplayName(void) const { return displayName; } ///< Get string to use in display Datatype *getTypedef(void) const { return typedefImm; } ///< Get the data-type immediately typedefed by \e this (or null) virtual void printRaw(ostream &s) const; ///< Print a description of the type to stream virtual const TypeField *findTruncation(int4 off,int4 sz,const PcodeOp *op,int4 slot,int4 &newoff) const; @@ -279,7 +281,7 @@ public: /// Construct TypeBase from a size and meta-type TypeBase(int4 s,type_metatype m) : Datatype(s,m) {} /// Construct TypeBase from a size, meta-type, and name - TypeBase(int4 s,type_metatype m,const string &n) : Datatype(s,m) { name = n; } + TypeBase(int4 s,type_metatype m,const string &n) : Datatype(s,m) { name = n; displayName = n; } virtual Datatype *clone(void) const { return new TypeBase(*this); } }; @@ -326,7 +328,7 @@ public: /// Construct from another TypeVoid TypeVoid(const TypeVoid &op) : Datatype(op) { flags |= Datatype::coretype; } /// Constructor - TypeVoid(void) : Datatype(0,TYPE_VOID) { name = "void"; flags |= Datatype::coretype; } + TypeVoid(void) : Datatype(0,TYPE_VOID) { name = "void"; displayName = name; flags |= Datatype::coretype; } virtual Datatype *clone(void) const { return new TypeVoid(*this); } virtual void encode(Encoder &encoder) const; }; diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java index 070723a0d7..aab164f9fd 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java @@ -370,7 +370,7 @@ public class DecompInterface { } compilerSpec = spec; - dtmanage = new PcodeDataTypeManager(prog); + dtmanage = new PcodeDataTypeManager(prog, options.getNameTransformer()); try { decompCallback = new DecompileCallback(prog, pcodelanguage, program.getCompilerSpec(), dtmanage); @@ -642,6 +642,9 @@ public class DecompInterface { */ public synchronized boolean setOptions(DecompileOptions options) { this.options = options; + if (dtmanage != null) { + dtmanage.setNameTransformer(options.getNameTransformer()); + } decompileMessage = ""; // Property can be set before process exists if (decompProcess == null) { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileCallback.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileCallback.java index 037afa23ba..9e46e05f44 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileCallback.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileCallback.java @@ -539,7 +539,7 @@ public class DecompileCallback { */ public void getNamespacePath(long id, Encoder resultEncoder) throws IOException { Namespace namespace = getNameSpaceByID(id); - HighFunction.encodeNamespace(resultEncoder, namespace); + HighFunction.encodeNamespace(resultEncoder, namespace, dtmanage.getNameTransformer()); if (debug != null) { debug.getNamespacePath(namespace); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java index fb64393bd8..80645f95ec 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java @@ -29,16 +29,16 @@ import generic.theme.Gui; import ghidra.GhidraOptions; import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES; import ghidra.app.util.HelpTopics; -import ghidra.framework.options.Options; +import ghidra.app.util.template.TemplateSimplifier; import ghidra.framework.options.ToolOptions; -import ghidra.framework.plugintool.Plugin; -import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramCompilerSpec; import ghidra.program.model.lang.*; import ghidra.program.model.lang.CompilerSpec.EvaluationModelType; import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.ElementId; import ghidra.program.model.pcode.Encoder; +import ghidra.program.model.symbol.NameTransformer; +import ghidra.program.model.symbol.IdentityNameTransformer; import ghidra.util.HelpLocation; /** @@ -397,6 +397,8 @@ public class DecompileOptions { private DecompilerLanguage displayLanguage; // Output language displayed by the decompiler + private NameTransformer nameTransformer; // Transformer applied to data-type/function names + private String protoEvalModel; // Name of the prototype evaluation model public DecompileOptions() { @@ -434,16 +436,17 @@ public class DecompileOptions { payloadLimitMBytes = SUGGESTED_MAX_PAYLOAD_BYTES; maxIntructionsPer = SUGGESTED_MAX_INSTRUCTIONS; cachedResultsSize = SUGGESTED_CACHED_RESULTS_SIZE; + nameTransformer = null; } /** * Grab all the decompiler options from various sources within a specific tool and program * and cache them in this object. - * @param ownerPlugin the plugin that owns the "tool options" for the decompiler + * @param fieldOptions the Options object containing options specific to listing fields * @param opt the Options object that contains the "tool options" specific to the decompiler * @param program the program whose "program options" are relevant to the decompiler */ - public void grabFromToolAndProgram(Plugin ownerPlugin, ToolOptions opt, Program program) { + public void grabFromToolAndProgram(ToolOptions fieldOptions, ToolOptions opt, Program program) { grabFromProgram(program); @@ -491,20 +494,18 @@ public class DecompileOptions { maxIntructionsPer = opt.getInt(MAX_INSTRUCTIONS, SUGGESTED_MAX_INSTRUCTIONS); cachedResultsSize = opt.getInt(CACHED_RESULTS_SIZE_MSG, SUGGESTED_CACHED_RESULTS_SIZE); - grabFromToolOptions(ownerPlugin); + grabFromFieldOptions(fieldOptions); } - private void grabFromToolOptions(Plugin ownerPlugin) { - if (ownerPlugin == null) { + private void grabFromFieldOptions(ToolOptions fieldOptions) { + if (fieldOptions == null) { return; } - PluginTool tool = ownerPlugin.getTool(); - Options toolOptions = tool.getOptions(CATEGORY_BROWSER_FIELDS); - CURSOR_MOUSE_BUTTON_NAMES mouseEvent = - toolOptions.getEnum(CURSOR_HIGHLIGHT_BUTTON_NAME, CURSOR_MOUSE_BUTTON_NAMES.MIDDLE); + fieldOptions.getEnum(CURSOR_HIGHLIGHT_BUTTON_NAME, CURSOR_MOUSE_BUTTON_NAMES.MIDDLE); middleMouseHighlightButton = mouseEvent.getMouseEventID(); + nameTransformer = new TemplateSimplifier(fieldOptions); } /** @@ -531,10 +532,18 @@ public class DecompileOptions { displayLanguage = cspec.getDecompilerOutputLanguage(); } + /** + * @return the default prototype to assume if no other information about a function is known + */ public String getProtoEvalModel() { return protoEvalModel; } + /** + * Set the default prototype model for the decompiler. This is the model assumed if no other + * information about a function is known. + * @param protoEvalModel is the name of the prototype model to set as default + */ public void setProtoEvalModel(String protoEvalModel) { this.protoEvalModel = protoEvalModel; } @@ -542,11 +551,11 @@ public class DecompileOptions { /** * This registers all the decompiler tool options with ghidra, and has the side effect of * pulling all the current values for the options if they exist - * @param ownerPlugin the plugin to which the options should be registered - * @param opt the options object to register with + * @param fieldOptions the options object specific to listing fields + * @param opt the options object specific to the decompiler * @param program the program */ - public void registerOptions(Plugin ownerPlugin, ToolOptions opt, Program program) { + public void registerOptions(ToolOptions fieldOptions, ToolOptions opt, Program program) { opt.registerOption(PREDICATE_OPTIONSTRING, PREDICATE_OPTIONDEFAULT, new HelpLocation(HelpTopics.DECOMPILER, "AnalysisPredicate"), PREDICATE_OPTIONDESCRIPTION); @@ -686,7 +695,7 @@ public class DecompileOptions { "Current variable highlight"); opt.registerOption(CACHED_RESULTS_SIZE_MSG, SUGGESTED_CACHED_RESULTS_SIZE, new HelpLocation(HelpTopics.DECOMPILER, "GeneralCacheSize"), CACHE_RESULTS_DESCRIPTION); - grabFromToolAndProgram(ownerPlugin, opt, program); + grabFromToolAndProgram(fieldOptions, opt, program); } private static void appendOption(Encoder encoder, ElementId option, String p1, String p2, @@ -825,10 +834,17 @@ public class DecompileOptions { encoder.closeElement(ELEM_OPTIONSLIST); } + /** + * @return the maximum number of characters the decompiler displays in a single line of output + */ public int getMaxWidth() { return maxwidth; } + /** + * Set the maximum number of characters the decompiler displays in a single line of output + * @param maxwidth is the maximum number of characters + */ public void setMaxWidth(int maxwidth) { this.maxwidth = maxwidth; } @@ -938,139 +954,296 @@ public class DecompileOptions { return SEARCH_HIGHLIGHT_COLOR; } + /** + * @return the mouse button that should be used to toggle the primary token highlight + */ public int getMiddleMouseHighlightButton() { return middleMouseHighlightButton; } + /** + * @return true if Pre comments are included as part of decompiler output + */ public boolean isPRECommentIncluded() { return commentPREInclude; } + /** + * Set whether Pre comments are displayed as part of decompiler output + * @param commentPREInclude is true if Pre comments are output + */ public void setPRECommentIncluded(boolean commentPREInclude) { this.commentPREInclude = commentPREInclude; } + /** + * @return true if Plate comments are included as part of decompiler output + */ public boolean isPLATECommentIncluded() { return commentPLATEInclude; } + /** + * Set whether Plate comments are displayed as part of decompiler output + * @param commentPLATEInclude is true if Plate comments are output + */ public void setPLATECommentIncluded(boolean commentPLATEInclude) { this.commentPLATEInclude = commentPLATEInclude; } + /** + * @return true if Post comments are included as part of decompiler output + */ public boolean isPOSTCommentIncluded() { return commentPOSTInclude; } + /** + * Set whether Post comments are displayed as part of decompiler output + * @param commentPOSTInclude is true if Post comments are output + */ public void setPOSTCommentIncluded(boolean commentPOSTInclude) { this.commentPOSTInclude = commentPOSTInclude; } + /** + * @return true if End-of-line comments are included as part of decompiler output + */ public boolean isEOLCommentIncluded() { return commentEOLInclude; } + /** + * Set whether End-of-line comments are displayed as part of decompiler output. + * @param commentEOLInclude is true if End-of-line comments are output + */ public void setEOLCommentIncluded(boolean commentEOLInclude) { this.commentEOLInclude = commentEOLInclude; } + /** + * @return true if WARNING comments are included as part of decompiler output + */ public boolean isWARNCommentIncluded() { return commentWARNInclude; } + /** + * Set whether automatically generated WARNING comments are displayed as part of + * decompiler output. + * @param commentWARNInclude is true if WARNING comments are output + */ public void setWARNCommentIncluded(boolean commentWARNInclude) { this.commentWARNInclude = commentWARNInclude; } + /** + * @return true if function header comments are included as part of decompiler output + */ public boolean isHeadCommentIncluded() { return commentHeadInclude; } + /** + * Set whether function header comments are included as part of decompiler output. + * @param commentHeadInclude is true if header comments are output + */ public void setHeadCommentIncluded(boolean commentHeadInclude) { this.commentHeadInclude = commentHeadInclude; } + /** + * @return true if the decompiler currently eliminates unreachable code + */ public boolean isEliminateUnreachable() { return eliminateUnreachable; } + /** + * Set whether the decompiler should eliminate unreachable code as part of its analysis. + * @param eliminateUnreachable is true if unreachable code is eliminated + */ public void setEliminateUnreachable(boolean eliminateUnreachable) { this.eliminateUnreachable = eliminateUnreachable; } + /** + * If the decompiler currently applies transformation rules that identify and + * simplify double precision arithmetic operations, true is returned. + * @return true if the decompiler applies double precision rules + */ public boolean isSimplifyDoublePrecision() { return simplifyDoublePrecision; } + /** + * Set whether the decompiler should apply transformation rules that identify and + * simplify double precision arithmetic operations. + * @param simplifyDoublePrecision is true if double precision rules should be applied + */ public void setSimplifyDoublePrecision(boolean simplifyDoublePrecision) { this.simplifyDoublePrecision = simplifyDoublePrecision; } + /** + * @return true if line numbers should be displayed with decompiler output. + */ public boolean isDisplayLineNumbers() { return displayLineNumbers; } + /** + * @return the source programming language that decompiler output is rendered in + */ public DecompilerLanguage getDisplayLanguage() { return displayLanguage; } + /** + * Retrieve the transformer being applied to data-type, function, and namespace names. + * If no transform is being applied, a pass-through object is returned. + * @return the transformer object + */ + public NameTransformer getNameTransformer() { + if (nameTransformer == null) { + nameTransformer = new IdentityNameTransformer(); + } + return nameTransformer; + } + + /** + * Set a specific transformer to be applied to all data-type, function, and namespace + * names in decompiler output. A null value indicates no transform should be applied. + * @param transformer is the transformer to apply + */ + public void setNameTransformer(NameTransformer transformer) { + nameTransformer = transformer; + } + + /** + * @return true if calling convention names are displayed as part of function signatures + */ public boolean isConventionPrint() { return conventionPrint; } + /** + * Set whether the calling convention name should be displayed as part of function signatures + * in decompiler output. + * @param conventionPrint is true if calling convention names should be displayed + */ public void setConventionPrint(boolean conventionPrint) { this.conventionPrint = conventionPrint; } + /** + * @return true if cast operations are not displayed in decompiler output + */ public boolean isNoCastPrint() { return noCastPrint; } + /** + * Set whether decompiler output should display cast operations. + * @param noCastPrint is true if casts should NOT be displayed. + */ public void setNoCastPrint(boolean noCastPrint) { this.noCastPrint = noCastPrint; } + /** + * Set the source programming language that decompiler output should be rendered in. + * @param val is the source language + */ public void setDisplayLanguage(DecompilerLanguage val) { displayLanguage = val; } + /** + * @return the font that should be used to render decompiler output + */ public Font getDefaultFont() { return Gui.getFont(DEFAULT_FONT_ID); } + /** + * If the time a decompiler process is allowed to analyze a single + * function exceeds this value, decompilation is aborted. + * @return the maximum time in seconds + */ public int getDefaultTimeout() { return decompileTimeoutSeconds; } + /** + * Set the maximum time (in seconds) a decompiler process is allowed to analyze a single + * function. If it is exceeded, decompilation is aborted. + * @param timeout is the maximum time in seconds + */ public void setDefaultTimeout(int timeout) { decompileTimeoutSeconds = timeout; } + /** + * If the size (in megabytes) of the payload returned by the decompiler + * process exceeds this value for a single function, decompilation is + * aborted. + * @return the maximum number of megabytes in a function payload + */ public int getMaxPayloadMBytes() { return payloadLimitMBytes; } + /** + * Set the maximum size (in megabytes) of the payload that can be returned by the decompiler + * process when analyzing a single function. If this size is exceeded, decompilation is + * aborted. + * @param mbytes is the maximum number of megabytes in a function payload + */ public void setMaxPayloadMBytes(int mbytes) { payloadLimitMBytes = mbytes; } + /** + * If the number of assembly instructions in a function exceeds this value, the function + * is not decompiled. + * @return the maximum number of instructions + */ public int getMaxInstructions() { return maxIntructionsPer; } + /** + * Set the maximum number of assembly instructions in a function to decompile. + * If the number exceeds this, the function is not decompiled. + * @param num is the number of instructions + */ public void setMaxInstructions(int num) { maxIntructionsPer = num; } + /** + * @return the style in which comments are printed in decompiler output + */ public CommentStyleEnum getCommentStyle() { return commentStyle; } + /** + * Set the style in which comments are printed as part of decompiler output + * @param commentStyle is the new style to set + */ public void setCommentStyle(CommentStyleEnum commentStyle) { this.commentStyle = commentStyle; } + /** + * Return the maximum number of decompiled function results that should be cached + * by the controller of the decompiler process. + * @return the number of functions to cache + */ public int getCacheSize() { return cachedResultsSize; } + } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileResults.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileResults.java index df900cd542..c5da8d2093 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileResults.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileResults.java @@ -21,6 +21,7 @@ import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.lang.Language; import ghidra.program.model.listing.Function; import ghidra.program.model.pcode.*; +import ghidra.program.model.symbol.IllegalCharCppTransformer; /** * Class for getting at the various structures returned @@ -197,8 +198,9 @@ public class DecompileResults { if (docroot == null) { return null; } - PrettyPrinter printer = new PrettyPrinter(function, docroot); - return printer.print(true); + PrettyPrinter printer = + new PrettyPrinter(function, docroot, new IllegalCharCppTransformer()); + return printer.print(); } private void decodeStream(Decoder decoder) { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/PrettyPrinter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/PrettyPrinter.java index 37fe757b04..59e0435cf7 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/PrettyPrinter.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/PrettyPrinter.java @@ -20,13 +20,16 @@ import java.util.List; import ghidra.app.decompiler.component.DecompilerUtils; import ghidra.program.model.listing.Function; +import ghidra.program.model.symbol.IdentityNameTransformer; +import ghidra.program.model.symbol.NameTransformer; import ghidra.util.StringUtilities; /** - * This class is used to convert a C language - * token group into readable C code. + * This class is used to convert a C/C++ language + * token group into readable C/C++ code. */ public class PrettyPrinter { + /** * The indent string to use when printing. */ @@ -34,29 +37,35 @@ public class PrettyPrinter { private Function function; private ClangTokenGroup tokgroup; - private ArrayList lines = new ArrayList(); + private ArrayList lines = new ArrayList<>(); + private NameTransformer transformer; /** * Constructs a new pretty printer using the specified C language token group. + * The printer takes a NameTransformer that will be applied to symbols, which can replace + * illegal characters in the symbol name for instance. A null indicates no transform is applied. + * @param function is the function to be printed * @param tokgroup the C language token group + * @param transformer the transformer to apply to symbols */ - public PrettyPrinter(Function function, ClangTokenGroup tokgroup) { + public PrettyPrinter(Function function, ClangTokenGroup tokgroup, NameTransformer transformer) { this.function = function; this.tokgroup = tokgroup; + this.transformer = (transformer != null) ? transformer : new IdentityNameTransformer(); flattenLines(); padEmptyLines(); } - private void padEmptyLines() { - for (ClangLine line : lines) { + private void padEmptyLines() { + for (ClangLine line : lines) { ArrayList tokenList = line.getAllTokens(); if (tokenList.size() == 0) { ClangToken spacer = ClangToken.buildSpacer(null, line.getIndent(), INDENT_STRING); - spacer.setLineParent( line ); - tokenList.add(0, spacer); + spacer.setLineParent(line); + tokenList.add(0, spacer); } } - } + } public Function getFunction() { return function; @@ -74,11 +83,9 @@ public class PrettyPrinter { /** * Prints the C language token group * into a string of C code. - * @param removeInvalidChars true if invalid character should be - * removed from functions and labels. * @return a string of readable C code */ - public DecompiledFunction print(boolean removeInvalidChars) { + public DecompiledFunction print() { StringBuffer buff = new StringBuffer(); for (ClangLine line : lines) { @@ -87,30 +94,19 @@ public class PrettyPrinter { for (ClangToken token : tokens) { boolean isToken2Clean = token instanceof ClangFuncNameToken || - token instanceof ClangVariableToken || - token instanceof ClangTypeToken || - token instanceof ClangFieldToken || - token instanceof ClangLabelToken; + token instanceof ClangVariableToken || token instanceof ClangTypeToken || + token instanceof ClangFieldToken || token instanceof ClangLabelToken; //do not clean constant variable tokens if (isToken2Clean && token.getSyntaxType() == ClangToken.CONST_COLOR) { isToken2Clean = false; } - if (removeInvalidChars && isToken2Clean) { - String tokenText = token.getText(); - for (int i = 0 ; i < tokenText.length() ; ++i) { - if (StringUtilities.isValidCLanguageChar(tokenText.charAt(i))) { - buff.append(tokenText.charAt(i)); - } - else { - buff.append('_'); - } - } - } - else { - buff.append(token.getText()); + String tokenText = token.getText(); + if (isToken2Clean) { + tokenText = transformer.simplify(tokenText); } + buff.append(tokenText); } buff.append(StringUtilities.LINE_SEPARATOR); } @@ -119,10 +115,10 @@ public class PrettyPrinter { private String findSignature() { int nChildren = tokgroup.numChildren(); - for (int i = 0 ; i < nChildren ; ++i) { + for (int i = 0; i < nChildren; ++i) { ClangNode node = tokgroup.Child(i); if (node instanceof ClangFuncProto) { - return node.toString()+";"; + return node.toString() + ";"; } } return null; diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java index 27a9213841..03065bcbd7 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java @@ -231,7 +231,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { // Assume docroot has been built. - PrettyPrinter printer = new PrettyPrinter(function, docroot); + PrettyPrinter printer = new PrettyPrinter(function, docroot, null); lines = printer.getLines(); int lineCount = lines.size(); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java index 47b788f529..940214d382 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java @@ -28,6 +28,7 @@ import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import docking.widgets.fieldpanel.support.FieldLocation; import docking.widgets.label.GDHtmlLabel; +import ghidra.GhidraOptions; import ghidra.app.decompiler.DecompileOptions; import ghidra.app.util.viewer.listingpanel.ProgramLocationListener; import ghidra.app.util.viewer.util.CodeComparisonPanel; @@ -88,10 +89,11 @@ public abstract class DecompilerCodeComparisonPanel 0)) { encoder.openElement(ELEM_JUMPTABLELIST); @@ -575,9 +580,11 @@ public class HighFunction extends PcodeSyntaxTree { * from the root (global) namespace up to the given namespace * @param encoder is the stream encoder * @param namespace is the namespace being described + * @param transformer is used to computer the displayed version of each namespace * @throws IOException for errors in the underlying stream */ - static public void encodeNamespace(Encoder encoder, Namespace namespace) throws IOException { + static public void encodeNamespace(Encoder encoder, Namespace namespace, + NameTransformer transformer) throws IOException { encoder.openElement(ELEM_PARENT); if (namespace != null) { ArrayList arr = new ArrayList<>(); @@ -595,7 +602,12 @@ public class HighFunction extends PcodeSyntaxTree { Namespace curScope = arr.get(i); encoder.openElement(ELEM_VAL); encoder.writeUnsignedInteger(ATTRIB_ID, curScope.getID()); - encoder.writeString(ATTRIB_CONTENT, curScope.getName()); + String nm = curScope.getName(); + String altName = transformer.simplify(nm); + if (!nm.equals(altName)) { + encoder.writeString(ATTRIB_LABEL, altName); + } + encoder.writeString(ATTRIB_CONTENT, nm); encoder.closeElement(ELEM_VAL); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionShellSymbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionShellSymbol.java index d371d37a5b..a5c91a8a98 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionShellSymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionShellSymbol.java @@ -61,6 +61,10 @@ public class HighFunctionShellSymbol extends HighSymbol { encoder.openElement(ELEM_FUNCTION); encoder.writeUnsignedInteger(ATTRIB_ID, getId()); encoder.writeString(ATTRIB_NAME, name); + String altName = dtmanage.getNameTransformer().simplify(name); + if (!name.equals(altName)) { + encoder.writeString(ATTRIB_LABEL, altName); + } encoder.writeSignedInteger(ATTRIB_SIZE, 1); AddressXML.encode(encoder, getStorage().getMinAddress()); encoder.closeElement(ELEM_FUNCTION); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java index 47cb61da2e..417e1e26d1 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java @@ -330,14 +330,21 @@ public class LocalSymbolMap { * Encode all the variables in this local variable map to the stream * @param encoder is the stream encoder * @param namespace if the namespace of the function + * @param transformer is used to compute a simplified version of the namespace name * @throws IOException for errors in the underlying stream */ - public void encodeLocalDb(Encoder encoder, Namespace namespace) throws IOException { + public void encodeLocalDb(Encoder encoder, Namespace namespace, NameTransformer transformer) + throws IOException { encoder.openElement(ELEM_LOCALDB); encoder.writeBool(ATTRIB_LOCK, false); encoder.writeSpace(ATTRIB_MAIN, localSpace); encoder.openElement(ELEM_SCOPE); - encoder.writeString(ATTRIB_NAME, func.getFunction().getName()); + String nm = func.getFunction().getName(); + encoder.writeString(ATTRIB_NAME, nm); + String altName = transformer.simplify(nm); + if (!nm.equals(altName)) { + encoder.writeString(ATTRIB_LABEL, altName); + } encoder.openElement(ELEM_PARENT); long parentid = Namespace.GLOBAL_NAMESPACE_ID; if (!HighFunction.collapseToGlobal(namespace)) { @@ -345,7 +352,7 @@ public class LocalSymbolMap { } encoder.writeUnsignedInteger(ATTRIB_ID, parentid); encoder.closeElement(ELEM_PARENT); - encoder.openElement(ELEM_RANGELIST); // Emptry address range + encoder.openElement(ELEM_RANGELIST); // Empty address range encoder.closeElement(ELEM_RANGELIST); encoder.openElement(ELEM_SYMBOLLIST); Iterator iter = symbolMap.values().iterator(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java index 0cd03a5dfa..ff1e6289d9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java @@ -31,6 +31,7 @@ import ghidra.program.model.data.Enum; import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.lang.DecompilerLanguage; import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.NameTransformer; /** * @@ -98,6 +99,7 @@ public class PcodeDataTypeManager { private DataTypeManager progDataTypes; // DataTypes from a particular program private DataTypeManager builtInDataTypes = BuiltInDataTypeManager.getDataTypeManager(); private DataOrganization dataOrganization; + private NameTransformer nameTransformer; private DecompilerLanguage displayLanguage; private boolean voidInputIsVarargs; // true if we should consider void parameter lists as varargs // Some C header conventions use an empty prototype to mean a @@ -107,11 +109,12 @@ public class PcodeDataTypeManager { private VoidDataType voidDt; private int pointerWordSize; // Wordsize to assign to all pointer datatypes - public PcodeDataTypeManager(Program prog) { + public PcodeDataTypeManager(Program prog, NameTransformer simplifier) { program = prog; progDataTypes = prog.getDataTypeManager(); dataOrganization = progDataTypes.getDataOrganization(); + nameTransformer = simplifier; voidInputIsVarargs = true; // By default, do not lock-in void parameter lists displayLanguage = prog.getCompilerSpec().getDecompilerOutputLanguage(); if (displayLanguage != DecompilerLanguage.C_LANGUAGE) { @@ -126,6 +129,14 @@ public class PcodeDataTypeManager { return program; } + public NameTransformer getNameTransformer() { + return nameTransformer; + } + + public void setNameTransformer(NameTransformer newTransformer) { + nameTransformer = newTransformer; + } + /** * Find a base/built-in data-type with the given name and/or id. If an id is provided and * a corresponding data-type exists, this data-type is returned. Otherwise the first @@ -954,7 +965,12 @@ public class PcodeDataTypeManager { ((BuiltIn) type).getDecompilerDisplayName(displayLanguage)); } else { + String name = type.getName(); + String displayName = nameTransformer.simplify(name); encoder.writeString(ATTRIB_NAME, type.getName()); + if (!name.equals(displayName)) { + encoder.writeString(ATTRIB_LABEL, displayName); + } long id = progDataTypes.getID(type); if (id > 0) { encoder.writeUnsignedInteger(ATTRIB_ID, id); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/IdentityNameTransformer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/IdentityNameTransformer.java new file mode 100644 index 0000000000..d0600c7075 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/IdentityNameTransformer.java @@ -0,0 +1,28 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.symbol; + +/** + * A transformer that never alters its input + */ +public class IdentityNameTransformer implements NameTransformer { + + @Override + public String simplify(String input) { + return input; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/IllegalCharCppTransformer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/IllegalCharCppTransformer.java new file mode 100644 index 0000000000..40cefcf9d8 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/IllegalCharCppTransformer.java @@ -0,0 +1,121 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.symbol; + +/** + * Replace illegal characters in the given name with '_'. The transformer treats the name as a + * C++ symbol. Letters and digits are generally legal. '~' is allowed at the start of the symbol. + * Template parameters, surrounded by '<' and '>', allow additional special characters. Certain + * special characters are allowed after the keyword "operator". + */ +public class IllegalCharCppTransformer implements NameTransformer { + + private static int[] legalChars = null; + private static final int AFTER_FIRST_CHAR = 1; // Legal after the first character + private static final int TEMPLATE = 2; // Legal as part of template parameters + private static final int OPERATOR = 4; // Legal after the "operator" keyword + private static final int FIRST_CHAR = 8; // Legal as the first character + + public IllegalCharCppTransformer() { + if (legalChars == null) { + legalChars = new int[128]; + for (int i = 0; i < legalChars.length; ++i) { + legalChars[i] = 0; + } + legalChars['_'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR | FIRST_CHAR; + legalChars['0'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['1'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['2'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['3'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['4'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['5'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['6'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['7'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['8'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['9'] = AFTER_FIRST_CHAR | TEMPLATE | OPERATOR; + legalChars['*'] = TEMPLATE | OPERATOR; + legalChars[':'] = TEMPLATE; + legalChars['('] = TEMPLATE | OPERATOR; + legalChars[')'] = TEMPLATE | OPERATOR; + legalChars['['] = TEMPLATE | OPERATOR; + legalChars[']'] = TEMPLATE | OPERATOR; + legalChars[','] = TEMPLATE; + legalChars['&'] = TEMPLATE | OPERATOR; + legalChars['+'] = OPERATOR; + legalChars['-'] = OPERATOR; + legalChars['|'] = OPERATOR; + legalChars['='] = OPERATOR; + legalChars['!'] = OPERATOR; + legalChars['/'] = OPERATOR; + legalChars['%'] = OPERATOR; + legalChars['^'] = OPERATOR; + legalChars['~'] = TEMPLATE | OPERATOR | FIRST_CHAR; + } + } + + @Override + public String simplify(String input) { + int templateDepth = 0; + char[] transform = null; + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + if (Character.isLetter(c)) { + continue; + } + else if (c == '<') { + templateDepth += 1; + continue; + } + else if (c == '>') { + templateDepth -= 1; + if (templateDepth < 0) { + templateDepth = 0; + } + continue; + } + else if (c < 128) { + int val = legalChars[c]; + if (val != 0) { + if (((val & AFTER_FIRST_CHAR) != 0) && i > 0) { + continue; // Legal after first character + } + if (((val & FIRST_CHAR) != 0) && i == 0) { + continue; // Legal as first character + } + if (((val & TEMPLATE) != 0) && templateDepth > 0) { + continue; // Legal as template parameter + } + if (((val & OPERATOR) != 0) && i >= 8 && i <= 10) { + if (input.startsWith("operator")) { + continue; + } + } + } + } + // If we reach here, the character is deemed illegal + if (transform == null) { + transform = new char[input.length()]; + input.getChars(0, input.length(), transform, 0); + } + transform[i] = '_'; + } + if (transform == null) { + return input; + } + return new String(transform); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/NameTransformer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/NameTransformer.java new file mode 100644 index 0000000000..39259d788d --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/NameTransformer.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.symbol; + +/** + * Interface to transform (shorten, simplify) names of data-types, functions, and name spaces + * for display. + */ +public interface NameTransformer { + + /** + * Return a transformed version of the given input. If no change is made, the original + * String object is returned. + * @param input is the name to transform + * @return the transformed version + */ + public String simplify(String input); +} diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/IllegalCharCppTransformerTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/IllegalCharCppTransformerTest.java new file mode 100644 index 0000000000..2e4032deb5 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/IllegalCharCppTransformerTest.java @@ -0,0 +1,73 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.symbol; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import generic.test.AbstractGenericTest; + +public class IllegalCharCppTransformerTest extends AbstractGenericTest { + + private IllegalCharCppTransformer transformer = new IllegalCharCppTransformer(); + + @Test + public void testTemplateChars() { + assertEquals("foo", simplify("foo")); + assertEquals("foo", simplify("foo")); + assertEquals("map", simplify("map")); + assertEquals("pair", + simplify("pair")); + assertEquals("_basic_string>", + simplify("_basic.string>")); + assertEquals("_________baz", simplify("*()~[]&:,baz")); + assertEquals("foo12______bar__operator", + simplify("foo12 ??_*~bar::operator")); + } + + @Test + public void testOperatorChars() { + assertEquals("operator<<", simplify("operator<<")); + assertEquals("operator>>=", simplify("operator>>=")); + assertEquals("operator++", simplify("operator++")); + assertEquals("operator/=", simplify("operator/=")); + assertEquals("operator%", simplify("operator%")); + assertEquals("operator&&", simplify("operator&&")); + assertEquals("operator!=", simplify("operator!=")); + assertEquals("operator__", simplify("operator.?")); + assertEquals("operator~", simplify("operator~")); + assertEquals("operator^", simplify("operator^")); + assertEquals("myoperator__", simplify("myoperator!=")); + } + + @Test + public void testBadChars() { + assertEquals("~destructor_main", simplify("~destructor.main")); + assertEquals("~Vector_7", simplify("~Vector~7")); + assertEquals("_2foo", simplify("12foo")); + assertEquals("std__foo", simplify("std::foo")); + assertEquals("bar__1", simplify("bar??1")); + assertEquals("_resource_352_", simplify("[resource.352]")); + assertEquals("_val_", simplify("!val%")); + // Foreign language identifiers + assertEquals("\u041d\u0415\u0422", simplify("\u041d\u0415\u0422")); + } + + private String simplify(String in) { + return transformer.simplify(in); + } +}