#include "DictSelectionReader.h" #include "clang/AST/AST.h" #include "ClassSelectionRule.h" #include "SelectionRules.h" #include "TMetaUtils.h" #include "TClassEdit.h" #include "RootMetaSelection.h" #include #include //////////////////////////////////////////////////////////////////////////////// DictSelectionReader::DictSelectionReader(SelectionRules &selectionRules, const clang::ASTContext &C, ROOT::TMetaUtils::TNormalizedCtxt &normCtxt) : fSelectionRules(selectionRules), fIsFirstPass(true), fNormCtxt(normCtxt) { clang::TranslationUnitDecl *translUnitDecl = C.getTranslationUnitDecl(); // Inspect the AST TraverseDecl(translUnitDecl); // Now re-inspect the AST to find autoselected classes (double-tap) fIsFirstPass = false; if (!fTemplateInfoMap.empty() || !fAutoSelectedClassFieldNames.empty() || !fNoAutoSelectedClassFieldNames.empty()) TraverseDecl(translUnitDecl); // Now push all the selection rules for (llvm::StringMap::iterator it = fClassNameSelectionRuleMap.begin(); it != fClassNameSelectionRuleMap.end(); ++it) { fSelectionRules.AddClassSelectionRule(it->second); } } //////////////////////////////////////////////////////////////////////////////// /// If it's not contained by 2 namespaces, drop it. /** * Check that the recordDecl is enclosed in the ROOT::Meta::Selection namespace, * excluding the portion dedicated the definition of the syntax, which is part * of ROOT, not of the user code. * If performance is needed, an alternative approach to string comparisons * could be adopted. One could use for example hashes of strings in first * approximation. **/ bool DictSelectionReader::InSelectionNamespace(const clang::RecordDecl &recordDecl, const std::string &className) { std::list > enclosingNamespaces; ROOT::TMetaUtils::ExtractEnclosingNameSpaces(recordDecl, enclosingNamespaces); const unsigned int nNs = enclosingNamespaces.size(); if (nNs < 3) return false; if (enclosingNamespaces.back().second || // is inline namespace enclosingNamespaces.back().first != "ROOT") return false; enclosingNamespaces.pop_back(); if (enclosingNamespaces.back().second || // is inline namespace enclosingNamespaces.back().first != "Meta") return false; enclosingNamespaces.pop_back(); if (enclosingNamespaces.back().second || // is inline namespace enclosingNamespaces.back().first != "Selection") return false; // Exclude the special names identifying the entities of the selection syntax if (className != "" && (className.find("MemberAttributes") == 0 || className.find("ClassAttributes") == 0 || className.find("Keep") == 0)) return false; return true; } //////////////////////////////////////////////////////////////////////////////// /** * Get the pointer to the template arguments list. Return zero if not available. **/ const clang::TemplateArgumentList * DictSelectionReader::GetTmplArgList(const clang::CXXRecordDecl &cxxRcrdDecl) { const clang::ClassTemplateSpecializationDecl *tmplSpecDecl = llvm::dyn_cast(&cxxRcrdDecl); if (!tmplSpecDecl) return 0; return &tmplSpecDecl->getTemplateArgs(); } //////////////////////////////////////////////////////////////////////////////// /** * Extract the value of the integral template parameter of a CXXRecordDecl when * it has a certain name. If nothing can be extracted, the value of @c zero * is returned. **/ template unsigned int DictSelectionReader::ExtractTemplateArgValue(const T &myClass, const std::string &pattern) { const clang::RecordDecl *rcrdDecl = ROOT::TMetaUtils::GetUnderlyingRecordDecl(myClass.getType()); const clang::CXXRecordDecl *cxxRcrdDecl = llvm::dyn_cast(rcrdDecl); if (!cxxRcrdDecl) return 0; const clang::TemplateArgumentList *tmplArgs = GetTmplArgList(*cxxRcrdDecl); if (!tmplArgs) return 0; if (std::string::npos == cxxRcrdDecl->getNameAsString().find(pattern)) return 0; return tmplArgs->get(0).getAsIntegral().getLimitedValue(); } //////////////////////////////////////////////////////////////////////////////// /// Iterate on the members to see if /// 1) They are transient /// 2) They imply further selection /** * Loop over the class filelds and take actions according to their properties * 1. Insert a field selection rule marking a member transient * 2. Store in a map the name of the field the type of which should be * autoselected. The key is the name of the class and the value the name of the * field. This information is used in the second pass. **/ void DictSelectionReader::ManageFields(const clang::RecordDecl &recordDecl, const std::string &className, ClassSelectionRule &csr, bool autoselect) { std::string pattern = className.substr(0, className.find_first_of("<")); for (auto fieldPtr : recordDecl.fields()) { unsigned int attrCode = ExtractTemplateArgValue(*fieldPtr, "MemberAttributes"); if (attrCode == ROOT::Meta::Selection::kMemberNullProperty) continue; const char *fieldName = fieldPtr->getName().data(); if (attrCode & ROOT::Meta::Selection::kNonSplittable) { if (!autoselect) { fTemplateInfoMap[pattern].fUnsplittableMembers.insert(fieldName); } else { VariableSelectionRule vsr(BaseSelectionRule::kYes); vsr.SetAttributeValue(ROOT::TMetaUtils::propNames::name, fieldName); vsr.SetAttributeValue(ROOT::TMetaUtils::propNames::comment, "||"); csr.AddFieldSelectionRule(vsr); } } if (attrCode & ROOT::Meta::Selection::kTransient) { if (!autoselect) { fTemplateInfoMap[pattern].fTransientMembers.insert(fieldName); } else { VariableSelectionRule vsr(BaseSelectionRule::kYes); vsr.SetAttributeValue(ROOT::TMetaUtils::propNames::name, fieldName); vsr.SetAttributeValue(ROOT::TMetaUtils::propNames::comment, "!"); csr.AddFieldSelectionRule(vsr); } } if (attrCode & ROOT::Meta::Selection::kAutoSelected) fAutoSelectedClassFieldNames[className].insert(fieldName); else if (attrCode & ROOT::Meta::Selection::kNoAutoSelected) fNoAutoSelectedClassFieldNames[className].insert(fieldName); } // end loop on fields } //////////////////////////////////////////////////////////////////////////////// /// Check the traits of the class. Useful information may be there /// extract mothers, make a switchcase: /// 1) templates args are to be skipped /// 2) There are properties. Make a loop. make a switch: /// 2a) Is splittable /** * Manage the loop over the base classes. * Initially, the class attributes are identified and selection rules filled * if: * 1. The class is not splittable * Then we look for the traits pointing to the need of hiding template * arguments. This information is stored in the form of a list of pairs, where * the first argument is the pattern of the template instance to match and * the second one the number of arguments to be skipped. This information is * used during the second pass. **/ void DictSelectionReader::ManageBaseClasses(const clang::CXXRecordDecl &cxxRcrdDecl, const std::string &className, bool &autoselect) { std::string baseName; clang::ASTContext &C = cxxRcrdDecl.getASTContext(); for (auto & base : cxxRcrdDecl.bases()) { if (unsigned int nArgsToKeep = ExtractTemplateArgValue(base, "Keep")) { std::string pattern = className.substr(0, className.find_first_of("<")); // Fill the structure holding the template and the number of args to // skip fTemplateInfoMap[pattern] = TemplateInfo(nArgsToKeep); } // at most one string comparison... if (autoselect) { auto qt = base.getType(); ROOT::TMetaUtils::GetFullyQualifiedTypeName(baseName, qt, C); if (baseName == "ROOT::Meta::Selection::SelectNoInstance") autoselect = false; } } // end loop on base classes } //////////////////////////////////////////////////////////////////////////////// /** * Manage the first pass over the AST, inspecting only nodes which are within * the selection namespace. Selection rules are directly filled as well as * data sructures re-used during the second pass. **/ bool DictSelectionReader::FirstPass(const clang::RecordDecl &recordDecl) { std::string className; ROOT::TMetaUtils::GetQualifiedName( className, *recordDecl.getTypeForDecl(), recordDecl); // Strip ROOT::Meta::Selection className.replace(0, 23, ""); if (!InSelectionNamespace(recordDecl, className)) return true; if (!fSelectedRecordDecls.insert(&recordDecl).second) return true; bool autoselect = true; if (auto cxxRcrdDecl = llvm::dyn_cast(&recordDecl)) { ManageBaseClasses(*cxxRcrdDecl, className, autoselect); } ClassSelectionRule csr(BaseSelectionRule::kYes); const size_t lWedgePos(className.find_first_of("<")); std::string patternName(""); if (lWedgePos != std::string::npos && llvm::isa(recordDecl)) { patternName = PatternifyName(className); csr.SetAttributeValue(ROOT::TMetaUtils::propNames::pattern, patternName); } else { csr.SetAttributeValue(ROOT::TMetaUtils::propNames::name, className); } ManageFields(recordDecl, className, csr, autoselect); if (!autoselect) return true; // Finally add the selection rule fClassNameSelectionRuleMap[patternName.empty() ? className : patternName] = csr; return true; } //////////////////////////////////////////////////////////////////////////////// /** * Second pass through the AST. Two operations are performed: * 1. Selection rules for classes to be autoselected are created. The * algorithm works as follows: the members of the classes matching the name of * the classes which contained autoselected members in the selection namespace * are inspected. If a field with the same name of the one which was * autoselected a selection rule based on its typename is built. * 2. If a class is found which is a @c TemplateSpecialisationDecl its * name is checked to match one of the patterns identified during the first * pass. If a match is found, a property is added to the selection rule with * the number of template arguments to keep in order to percolate this * information down to the @c AnnotatedRecordDecl creation which happens in the * @c RScanner . **/ bool DictSelectionReader::SecondPass(const clang::RecordDecl &recordDecl) { using namespace ROOT::TMetaUtils; // No interest if we are in the selection namespace if (InSelectionNamespace(recordDecl)) return true; std::string className; GetQualifiedName(className, *recordDecl.getTypeForDecl(), recordDecl); // If the class is not among those which have fields the type of which are to // be autoselected or excluded if (0 != fAutoSelectedClassFieldNames.count(className) || 0 != fNoAutoSelectedClassFieldNames.count(className)) { // Iterate on fields. If the name of the field is among the ones the types // of which should be (no)autoselected, add a class selection rule std::string typeName; clang::ASTContext &C = recordDecl.getASTContext(); for (clang::RecordDecl::field_iterator filedsIt = recordDecl.field_begin(); filedsIt != recordDecl.field_end(); ++filedsIt) { const std::string fieldName(filedsIt->getNameAsString()); bool excluded = 1 == fNoAutoSelectedClassFieldNames[className].count(fieldName); bool selected = 1 == fAutoSelectedClassFieldNames[className].count(fieldName); if (!selected && !excluded) continue; ClassSelectionRule aSelCsr(excluded ? BaseSelectionRule::kNo : BaseSelectionRule::kYes); GetFullyQualifiedTypeName(typeName, filedsIt->getType(), C); GetPointeeType(typeName); aSelCsr.SetAttributeValue(propNames::name, typeName); fSelectionRules.AddClassSelectionRule(aSelCsr); } } // If the class is a template instantiation and its name matches one of the // patterns // We don't want anything different from templ specialisations if (auto tmplSpecDecl = llvm::dyn_cast(&recordDecl)) { for (auto & patternInfoPair : fTemplateInfoMap) { const std::string &pattern = patternInfoPair.first; const TemplateInfo &tInfo = patternInfoPair.second; // Check if we have to add a selection rule for this class if (className.find(pattern) != 0) continue; // Take care of the args to keep auto ctd = tmplSpecDecl->getSpecializedTemplate(); if (tInfo.fArgsToKeep != -1 && ctd) { fNormCtxt.AddTemplAndNargsToKeep(ctd->getCanonicalDecl(), tInfo.fArgsToKeep); } // Now we take care of the transient and unsplittable members if (tInfo.fTransientMembers.empty() && tInfo.fUnsplittableMembers.empty()) continue; clang::ASTContext &C = recordDecl.getASTContext(); clang::SourceRange commentRange; // Empty: this is a fake comment std::string userDefinedProperty; userDefinedProperty.reserve(100); for (auto fieldPtr : recordDecl.fields()) { const auto fieldName = fieldPtr->getName().data(); if (tInfo.fTransientMembers.count(fieldName) == 1) { userDefinedProperty = "!"; } else if (tInfo.fUnsplittableMembers.count(fieldName) == 1) { userDefinedProperty = propNames::comment + propNames::separator + "||"; } if (!userDefinedProperty.empty()) { fieldPtr->addAttr(new(C) clang::AnnotateAttr(commentRange, C, userDefinedProperty, 0)); userDefinedProperty = ""; } } } // End loop on template info } return true; } //////////////////////////////////////////////////////////////////////////////// bool DictSelectionReader::VisitRecordDecl(clang::RecordDecl *recordDecl) { if (fIsFirstPass) return FirstPass(*recordDecl); else return SecondPass(*recordDecl); } //////////////////////////////////////////////////////////////////////////////// /** * Transform a name of a class instance into a pattern for selection * e.g. myClass in the selection namespace * will translate into a pattern of the type myClass<*> **/ inline std::string DictSelectionReader::PatternifyName(const std::string &className) { return className.substr(0, className.find_first_of("<")) + "<*>"; } //////////////////////////////////////////////////////////////////////////////// /** * Transform the name of the type eliminating the trailing & and * **/ inline void DictSelectionReader::GetPointeeType(std::string &typeName) { while (typeName[typeName.size() - 1] == '*' || typeName[typeName.size() - 1] == '&') { typeName = typeName.substr(0, typeName.size() - 1); } }