diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index a9d18144a8..6e4bb55ba1 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -37,6 +37,7 @@ src/decompile/datatests/modulo.xml||GHIDRA||||END| src/decompile/datatests/modulo2.xml||GHIDRA||||END| src/decompile/datatests/multiret.xml||GHIDRA||||END| src/decompile/datatests/namespace.xml||GHIDRA||||END| +src/decompile/datatests/nan.xml||GHIDRA||||END| src/decompile/datatests/nestedoffset.xml||GHIDRA||||END| src/decompile/datatests/noforloop_alias.xml||GHIDRA||||END| src/decompile/datatests/noforloop_globcall.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc index 62508298f6..f03793f014 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc @@ -1401,6 +1401,8 @@ void Architecture::resetDefaultsInternal(void) infer_pointers = true; analyze_for_loops = true; readonlypropagate = false; + nan_ignore_all = false; + nan_ignore_compare = true; // Ignore only NaN operations associated with floating-point comparisons by default alias_block_level = 2; // Block structs and arrays by default, but not more primitive data-types split_datatype_config = OptionSplitDatatypes::option_struct | OptionSplitDatatypes::option_array | OptionSplitDatatypes::option_pointer; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh index c2c4d8e3c0..98c5d705c4 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh @@ -177,6 +177,8 @@ public: bool readonlypropagate; ///< true if readonly values should be treated as constants bool infer_pointers; ///< True if we should infer pointers from constants that are likely addresses bool analyze_for_loops; ///< True if we should attempt conversion of \e whiledo loops to \e for loops + bool nan_ignore_all; ///< True if we should ignore NaN operations, i.e. nan() always returns false + bool nan_ignore_compare; ///< True if we should ignore NaN operations protecting floating-point comparisons vector inferPtrSpaces; ///< Set of address spaces in which a pointer constant is inferable int4 funcptr_align; ///< How many bits of alignment a function ptr has uint4 flowoptions; ///< options passed to flow following engine diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc index 9979b81400..e79d76af4f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc @@ -1160,6 +1160,6 @@ ElementId ELEM_VAL = ElementId("val",8); ElementId ELEM_VALUE = ElementId("value",9); ElementId ELEM_VOID = ElementId("void",10); -ElementId ELEM_UNKNOWN = ElementId("XMLunknown",272); // Number serves as next open index +ElementId ELEM_UNKNOWN = ElementId("XMLunknown",273); // Number serves as next open index } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc index a496b4f8e0..c193e2d515 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc @@ -59,6 +59,7 @@ ElementId ELEM_STRUCTALIGN = ElementId("structalign",208); ElementId ELEM_TOGGLERULE = ElementId("togglerule",209); ElementId ELEM_WARNING = ElementId("warning",210); ElementId ELEM_JUMPTABLEMAX = ElementId("jumptablemax",271); +ElementId ELEM_NANIGNORE = ElementId("nanignore",272); /// If the parameter is "on" return \b true, if "off" return \b false. /// Any other value causes an exception. @@ -128,6 +129,7 @@ OptionDatabase::OptionDatabase(Architecture *g) registerOption(new OptionMaxInstruction()); registerOption(new OptionNamespaceStrategy()); registerOption(new OptionSplitDatatypes()); + registerOption(new OptionNanIgnore()); } OptionDatabase::~OptionDatabase(void) @@ -985,4 +987,37 @@ string OptionSplitDatatypes::apply(Architecture *glb,const string &p1,const stri return "Split data-type configuration set"; } +string OptionNanIgnore::apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const + +{ + bool oldIgnoreAll = glb->nan_ignore_all; + bool oldIgnoreCompare = glb->nan_ignore_compare; + + if (p1 == "none") { // Don't ignore any NaN operation + glb->nan_ignore_all = false; + glb->nan_ignore_compare = false; + } + else if (p1 == "compare") { // Ignore only NaN operations protecting floating-point comparisons + glb->nan_ignore_all = false; + glb->nan_ignore_compare = true; + } + else if (p1 == "all") { // Ignore all NaN operations + glb->nan_ignore_all = true; + glb->nan_ignore_compare = true; + } + else { + throw LowlevelError("Unknown nanignore option: "+p1); + } + Action *root = glb->allacts.getCurrent(); + if (!glb->nan_ignore_all && !glb->nan_ignore_compare) { + root->disableRule("ignorenan"); + } + else { + root->enableRule("ignorenan"); + } + if (oldIgnoreAll == glb->nan_ignore_all && oldIgnoreCompare == glb->nan_ignore_compare) + return "NaN ignore configuration unchanged"; + return "Nan ignore configuration set to: " + p1; +} + } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/options.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/options.hh index 9c08babf21..018220a4e8 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/options.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/options.hh @@ -65,6 +65,7 @@ extern ElementId ELEM_STRUCTALIGN; ///< Marshaling element \ extern ElementId ELEM_TOGGLERULE; ///< Marshaling element \ extern ElementId ELEM_WARNING; ///< Marshaling element \ extern ElementId ELEM_JUMPTABLEMAX; ///< Marshaling element \ +extern ElementId ELEM_NANIGNORE; ///< Marshaling element \ /// \brief Base class for options classes that affect the configuration of the Architecture object /// @@ -343,5 +344,11 @@ public: virtual string apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const; }; +class OptionNanIgnore : public ArchOption { +public: + OptionNanIgnore(void) { name = "nanignore"; } ///< Constructor + virtual string apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const; +}; + } // End namespace ghidra #endif diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index ae2e502c19..aee58417cd 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -9437,26 +9437,151 @@ int4 RuleFloatCast::applyOp(PcodeOp *op,Funcdata &data) } /// \class RuleIgnoreNan -/// \brief Treat FLOAT_NAN as always evaluating to false +/// \brief Remove certain NaN operations by assuming their result is always \b false /// -/// This makes the assumption that all floating-point calculations -/// give valid results (not NaN). +/// This rule can be configured to remove either all FLOAT_NAN operations or only those that +/// protect floating-point comparisons. void RuleIgnoreNan::getOpList(vector &oplist) const { oplist.push_back(CPUI_FLOAT_NAN); } +/// \brief Check if a boolean Varnode incorporates a floating-point comparison with the given value +/// +/// The Varnode can either be the direct output of a comparison, or it can be a BOOL_OR or BOOL_AND, +/// combining output from the comparison. +/// \param floatVar is the given value the comparison must take as input +/// \param root is the boolean Varnode +/// \return \b true if the boolean Varnode incorporates the comparison +bool RuleIgnoreNan::checkBackForCompare(Varnode *floatVar,Varnode *root) + +{ + if (!root->isWritten()) return false; + PcodeOp *def1 = root->getDef(); + if (!def1->isBoolOutput()) return false; + if (def1->getOpcode()->isFloatingPointOp()) { + if (def1->numInput() != 2) return false; + if (functionalEquality(floatVar, def1->getIn(0))) + return true; + if (functionalEquality(floatVar, def1->getIn(1))) + return true; + return false; + } + OpCode opc = def1->code(); + if (opc != CPUI_BOOL_AND || opc != CPUI_BOOL_OR) + return false; + for(int4 i=0;i<2;++i) { + Varnode *vn = def1->getIn(i); + if (!vn->isWritten()) continue; + PcodeOp *def2 = vn->getDef(); + if (!def2->isBoolOutput()) continue; + if (!def2->getOpcode()->isFloatingPointOp()) continue; + if (def2->numInput() != 2) continue; + if (functionalEquality(floatVar, def2->getIn(0))) + return true; + if (functionalEquality(floatVar, def2->getIn(1))) + return true; + } + return false; +} + +/// \brief Test if a boolean expression incorporates a floating-point comparison, and remove the NaN data-flow if it does +/// +/// The given PcodeOp takes input from a NaN operation through a specific slot. We look for a floating-point comparison +/// PcodeOp (FLOAT_LESS, FLOAT_LESSEQUAL, FLOAT_EQUAL, or FLOAT_NOTEQUAL) that is combined with the given PcodeOp and +/// has the same input Varnode as the NaN. The data-flow must be combined either through a BOOL_OR or BOOL_AND +/// operation, or the given PcodeOp must be a CBRANCH that protects immediate control-flow to another CBRANCH +/// taking the result of the comparison as input. If a matching comparison is found, the NaN input to the given +/// PcodeOp is removed, assuming the output of the NaN operation is always \b false. +/// Input from an unmodified NaN result must be combined through a BOOL_OR, but a NaN result that has been negated +/// must combine through a BOOL_AND. +/// \param floatVar is the input Varnode to NaN operation +/// \param op is the given PcodeOp to test +/// \param slot is the input index of the NaN operation +/// \param matchCode is BOOL_AND if the NaN result has been negated, BOOL_OR if not +/// \param count is incremented if a comparison is found and the NaN input is removed +/// \param data is the function +/// \return the output of the given PcodeOp if it has an opcode matching \b matchCode +Varnode *RuleIgnoreNan::testForComparison(Varnode *floatVar,PcodeOp *op,int4 slot,OpCode matchCode,int4 &count,Funcdata &data) + +{ + if (op->code() == matchCode) { + Varnode *vn = op->getIn(1-slot); + if (checkBackForCompare(floatVar,vn)) { + data.opSetOpcode(op, CPUI_COPY); + data.opRemoveInput(op, 1); + data.opSetInput(op, vn, 0); + count += 1; + } + return op->getOut(); + } + if (op->code() != CPUI_CBRANCH) + return (Varnode *)0; + BlockBasic *parent = op->getParent(); + bool flowToFromCompare = false; + PcodeOp *lastOp; + int4 outDir = (matchCode == CPUI_BOOL_OR) ? 0 : 1; + if (op->isBooleanFlip()) + outDir = 1 - outDir; + FlowBlock *outBranch = parent->getOut(outDir); + lastOp = outBranch->lastOp(); + if (lastOp != (PcodeOp *)0 && lastOp->code() == CPUI_CBRANCH) { + FlowBlock *otherBranch = parent->getOut(1-outDir); + if (outBranch->getOut(0) == otherBranch || outBranch->getOut(1) == otherBranch) { + if (checkBackForCompare(floatVar, lastOp->getIn(1))) + flowToFromCompare = true; + } + } + if (flowToFromCompare) { + data.opSetInput(op,data.newConstant(1, 0),1); // Treat result of NaN as false + count += 1; + } + return (Varnode *)0; +} + int4 RuleIgnoreNan::applyOp(PcodeOp *op,Funcdata &data) { - if (op->numInput()==2) - data.opRemoveInput(op,1); - - // Treat these operations as always returning false (0) - data.opSetOpcode(op,CPUI_COPY); - data.opSetInput(op,data.newConstant(1,0),0); - return 1; + if (data.getArch()->nan_ignore_all) { + // Treat these NaN operation as always returning false (0) + data.opSetOpcode(op,CPUI_COPY); + data.opSetInput(op,data.newConstant(1,0),0); + return 1; + } + Varnode *floatVar = op->getIn(0); + if (floatVar->isFree()) return 0; + Varnode *out1 = op->getOut(); + int4 count = 0; + list::const_iterator iter1 = out1->beginDescend(); + while(iter1 != out1->endDescend()) { + PcodeOp *boolRead1 = *iter1; + ++iter1; // out1 may be truncated from boolRead1 below, advance iterator now + Varnode *out2; + OpCode matchCode = CPUI_BOOL_OR; + if (boolRead1->code() == CPUI_BOOL_NEGATE) { + matchCode = CPUI_BOOL_AND; + out2 = boolRead1->getOut(); + } + else { + out2 = testForComparison(floatVar, boolRead1, boolRead1->getSlot(out1), matchCode, count, data); + } + if (out2 == (Varnode *)0) continue; + list::const_iterator iter2 = out2->beginDescend(); + while(iter2 != out2->endDescend()) { + PcodeOp *boolRead2 = *iter2; + ++iter2; + Varnode *out3 = testForComparison(floatVar,boolRead2, boolRead2->getSlot(out2), matchCode, count, data); + if (out3 == (Varnode *)0) continue; + list::const_iterator iter3 = out3->beginDescend(); + while(iter3 != out3->endDescend()) { + PcodeOp *boolRead3 = *iter3; + ++iter3; + testForComparison(floatVar, boolRead3, boolRead3->getSlot(out3), matchCode, count, data); + } + } + } + return (count > 0) ? 1 : 0; } /// \class RuleFuncPtrEncoding diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh index 1988db3fb0..2d19dc5feb 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh @@ -1549,6 +1549,8 @@ public: }; class RuleIgnoreNan : public Rule { + static bool checkBackForCompare(Varnode *floatVar,Varnode *root); + static Varnode *testForComparison(Varnode *floatVar,PcodeOp *op,int4 slot,OpCode matchCode,int4 &count,Funcdata &data); public: RuleIgnoreNan(const string &g) : Rule( g, 0, "ignorenan") {} ///< Constructor virtual Rule *clone(const ActionGroupList &grouplist) const { diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/nan.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/nan.xml new file mode 100644 index 0000000000..07eb971eaa --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/nan.xml @@ -0,0 +1,30 @@ + + + + + f30f1efa4883ec18f2 +0f114c2408bf01000000660f2ec07a08 +b8000000000f44f8e88cfffffff20f10 +05230e0000660f2f442408400f97c740 +0fb6ffe898ffffff4883c418c3 + + + 000000000000e83f + + + + + + +read_nan\(NAN\(param_1\)\); +read_compare\(param_2 < 0\.75\); +NAN\(param_2\) + diff --git a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml index 816c0d771b..11c1c94a03 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml +++ b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml @@ -3088,6 +3088,35 @@ + + NaN operations + + + This option determines how the Decompiler treats floating-point NaN + (Not a Number) operations. Many processors automatically perform NaN checks on the operands of + floating-point instructions, and unless specifically configured, these show up in Decompiler output + as NAN() functional tokens. Common floating-point source code operations, like + <, >, and == can generate NAN tokens as a + side effect, even if the original source code was not designed to handle NaN values, and the + tokens can clutter the output. + + + The user can optionally configure some or all of the NaN operations to be ignored, meaning that + inputs to the NaN operation are assumed to be valid floating-point values and the + NAN function is removed, replacing it with the value: false. + The possible settings are: + + + Ignore none - No NaN operations are removed + Ignore with comparisons - NaN operations associated with comparisons are removed + Ignore all - All NaN operations are removed + + + The Decompiler considers a NaN operation to be associated with a floating-point comparison if they both + can be considered boolean clauses of the same if condition. + + + Recover -for- loops diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerOptions.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerOptions.html index 2742292129..a2996b1cd7 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerOptions.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerOptions.html @@ -4,7 +4,7 @@ Decompiler Options - + @@ -241,6 +241,40 @@

+NaN operations +
+
+

+ This option determines how the Decompiler treats floating-point NaN + (Not a Number) operations. Many processors automatically perform NaN checks on the operands of + floating-point instructions, and unless specifically configured, these show up in Decompiler output + as NAN() functional tokens. Common floating-point source code operations, like + <, >, and == can generate NAN tokens as a + side effect, even if the original source code was not designed to handle NaN values, and the + tokens can clutter the output. +

+

+ The user can optionally configure some or all of the NaN operations to be ignored, meaning that + inputs to the NaN operation are assumed to be valid floating-point values and the + NAN function is removed, replacing it with the value: false. + The possible settings are: +

+
+
    +
  • +Ignore none - No NaN operations are removed
  • +
  • +Ignore with comparisons - NaN operations associated with comparisons are removed
  • +
  • +Ignore all - All NaN operations are removed
  • +
+
+

+ The Decompiler considers a NaN operation to be associated with a floating-point comparison if they both + can be considered boolean clauses of the same if condition. +

+
+
Recover -for- loops
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 d7e7ff9120..0800d12c9a 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 @@ -124,6 +124,37 @@ public class DecompileOptions { private final static boolean SPLITPOINTERS_OPTIONDEFAULT = true; // Must match Architecture::resetDefaultsInternal private boolean splitPointers; + private final static String NANIGNORE_OPTIONSTRING = "Analysis.NaN operations"; + private final static String NANIGNORE_OPTIONDESCRIPTION = + "Specify how much to ignore floating-point NaN operations in decompiler output"; + + public enum NanIgnoreEnum { + + None("none", "Ignore none"), + Compare("compare", "Ignore with comparisons"), + All("all", "Ignore all"); + + private String label; + private String optionString; + + private NanIgnoreEnum(String optString, String label) { + this.label = label; + this.optionString = optString; + } + + public String getOptionString() { + return optionString; + } + + @Override + public String toString() { + return label; + } + } + + private final static NanIgnoreEnum NANIGNORE_OPTIONDEFAULT = NanIgnoreEnum.Compare; // Must match Architecture::resetDefaultsInternal + private NanIgnoreEnum nanIgnore; + private final static String NULLTOKEN_OPTIONSTRING = "Display.Print 'NULL' for null pointers"; private final static String NULLTOKEN_OPTIONDESCRIPTION = "If set, any zero valued pointer (null pointer) will " + @@ -412,6 +443,7 @@ public class DecompileOptions { splitStructures = SPLITSTRUCTURES_OPTIONDEFAULT; splitArrays = SPLITARRAYS_OPTIONDEFAULT; splitPointers = SPLITPOINTERS_OPTIONDEFAULT; + nanIgnore = NANIGNORE_OPTIONDEFAULT; ignoreunimpl = IGNOREUNIMPL_OPTIONDEFAULT; inferconstptr = INFERCONSTPTR_OPTIONDEFAULT; analyzeForLoops = ANALYZEFORLOOPS_OPTIONDEFAULT; @@ -473,6 +505,7 @@ public class DecompileOptions { opt.getBoolean(SPLITSTRUCTURES_OPTIONSTRING, SPLITSTRUCTURES_OPTIONDEFAULT); splitArrays = opt.getBoolean(SPLITARRAYS_OPTIONSTRING, SPLITARRAYS_OPTIONDEFAULT); splitPointers = opt.getBoolean(SPLITPOINTERS_OPTIONSTRING, SPLITPOINTERS_OPTIONDEFAULT); + nanIgnore = opt.getEnum(NANIGNORE_OPTIONSTRING, NANIGNORE_OPTIONDEFAULT); nullToken = opt.getBoolean(NULLTOKEN_OPTIONSTRING, NULLTOKEN_OPTIONDEFAULT); inplaceTokens = opt.getBoolean(INPLACEOP_OPTIONSTRING, INPLACEOP_OPTIONDEFAULT); @@ -592,6 +625,9 @@ public class DecompileOptions { opt.registerOption(SPLITPOINTERS_OPTIONSTRING, SPLITPOINTERS_OPTIONDEFAULT, new HelpLocation(HelpTopics.DECOMPILER, "AnalysisSplitPointers"), SPLITPOINTERS_OPTIONDESCRIPTION); + opt.registerOption(NANIGNORE_OPTIONSTRING, NANIGNORE_OPTIONDEFAULT, + new HelpLocation(HelpTopics.DECOMPILER, "AnalysisNanIgnore"), + NANIGNORE_OPTIONDESCRIPTION); opt.registerOption(NULLTOKEN_OPTIONSTRING, NULLTOKEN_OPTIONDEFAULT, new HelpLocation(HelpTopics.DECOMPILER, "DisplayNull"), NULLTOKEN_OPTIONDESCRIPTION); opt.registerOption(INPLACEOP_OPTIONSTRING, INPLACEOP_OPTIONDEFAULT, @@ -758,6 +794,9 @@ public class DecompileOptions { String p3 = splitPointers ? "pointer" : ""; appendOption(encoder, ELEM_SPLITDATATYPE, p1, p2, p3); } + if (nanIgnore != NANIGNORE_OPTIONDEFAULT) { + appendOption(encoder, ELEM_NANIGNORE, nanIgnore.getOptionString(), "", ""); + } appendOption(encoder, ELEM_READONLY, readOnly ? "on" : "off", "", ""); // Must set language early so that the object is in place before other option changes diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/ElementId.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/ElementId.java index d7a813c678..15c0968f24 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/ElementId.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/ElementId.java @@ -423,5 +423,6 @@ public record ElementId(String name, int id) { public static final ElementId ELEM_SPLITDATATYPE = new ElementId("splitdatatype", 270); public static final ElementId ELEM_JUMPTABLEMAX = new ElementId("jumptablemax", 271); - public static final ElementId ELEM_UNKNOWN = new ElementId("XMLunknown", 272); + public static final ElementId ELEM_NANIGNORE = new ElementId("nanignore", 272); + public static final ElementId ELEM_UNKNOWN = new ElementId("XMLunknown", 273); }